TypechoBio: Building a Standalone Personal Homepage on Typecho

Published:

TypechoBio is a Typecho plugin for running a personal homepage on a specified domain. It can take over requests for selected hostnames, reuse Typecho’s existing comment system, and bind comments to a chosen article ID.

It is not a regular Typecho frontend theme. Instead, it works as an independent plugin entry that renders a complete page when the current request matches a configured domain. This makes it useful for personal profile pages, bio pages, portfolio landing pages, and standalone static-style homepages that still need Typecho’s login state, configuration storage, posts, and comments.

Installation

Option 1: Install through AB-Store

After installing the AdminBeautify plugin, open AB-Store in the Typecho admin panel, search for TypechoBio, and install it with one click. This method is recommended because it also makes later updates easier.

Option 2: Manual installation

  1. Download the latest Release archive.
  2. Extract it as a folder named TypechoBio.
  3. Upload the folder to Typecho’s usr/plugins/ directory.
  4. Log in to the admin panel, go to ConsolePlugins, and enable TypechoBio.

Option 3: Clone with Git

cd /path/to/typecho/usr/plugins/
git clone https://github.com/lhl77/TypechoBio.git TypechoBio

Available themes

The plugin can install and use several personal homepage themes, including Imsyy, Duckfolio, Zyyo, Dmego, Wexuo, and IAMI.

Adapting a theme for TypechoBio

The following notes are intended for theme developers who want to adapt an existing frontend page or theme source code to TypechoBio. The guidance follows the current plugin behavior and is suitable for these cases:

  • Adding a local theme under usr/plugins/TypechoBio/templates/<theme>/
  • Migrating external HTML pages, static sites, Vite build output, or existing theme source code into TypechoBio
  • Connecting a theme to the plugin’s shared configuration system, comment section, and independent-domain runtime context
  • Packaging a theme as an installable zip for a remote theme marketplace

How TypechoBio runs

TypechoBio is not mounted as the normal Typecho frontend theme. It takes over output only when the current request host matches the plugin’s configured domain list.

The execution flow is roughly as follows:

  1. The plugin checks the current Host against bio_hosts to decide whether TypechoBio should handle the request.
  2. If matched, it first processes theme-specific APIs, then handles static asset requests for the active theme.
  3. Non-asset requests enter usr/plugins/TypechoBio/templates/index.php.
  4. templates/index.php prepares runtime context such as the active theme, theme configuration, user state, and comment data.
  5. Finally, it loads usr/plugins/TypechoBio/templates/<theme>/home.php to output the full page.

This model has a few important consequences:

  • home.php must output a complete HTML document by itself. Do not assume Typecho’s normal frontend templates will wrap it.
  • A theme can access the database, login state, and plugin settings at runtime, but it should preferably read the prepared $typechoBioContext.
  • Assets, comments, and configuration should follow TypechoBio conventions instead of directly inheriting assumptions from another CMS or theme system.

Minimum theme structure

A theme must provide at least the following files before TypechoBio can recognize it:

usr/plugins/TypechoBio/
└── templates/
    └── my-theme/
        ├── home.php
        ├── theme.json
        ├── assets/
        │   ├── app.css
        │   └── app.js
        ├── images/
        └── ...

The discovery rule is strict:

  • templates/<dir>/home.php must exist.
  • templates/<dir>/theme.json must exist.
  • If either file is missing, discoverThemes() will not treat the directory as an available theme.

Recommended practice:

  • Use only letters, numbers, underscores, and hyphens in theme directory names.
  • Keep the directory name consistent with the theme identifier in theme.json.
  • Put all static assets for the theme inside the theme directory instead of scattering files under the plugin root.

What home.php should do

home.php is the real entry file of a TypechoBio theme. It is best treated as the page shell template. At minimum, it should:

  • Output the complete HTML document structure
  • Read $typechoBioContext
  • Render page content based on themeConfig
  • Reference static assets from the current theme correctly
  • Include the shared comment section, proxy interfaces, or shared fragments when needed

A minimal working example:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}

$ctx = isset($typechoBioContext) && is_array($typechoBioContext)
    ? $typechoBioContext
    : array();

$themeConfig = isset($ctx['themeConfig']) && is_array($ctx['themeConfig'])
    ? $ctx['themeConfig']
    : array();

$siteTitle = isset($ctx['siteTitle']) ? (string) $ctx['siteTitle'] : 'TypechoBio';
$themeAssetBase = isset($ctx['themeAssetBase']) ? (string) $ctx['themeAssetBase'] : '';
$pluginRoot = dirname(dirname(__DIR__));
?>
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?php echo htmlspecialchars($siteTitle, ENT_QUOTES, 'UTF-8'); ?></title>
    <link rel="stylesheet" href="<?php echo htmlspecialchars($themeAssetBase . 'assets/app.css', ENT_QUOTES, 'UTF-8'); ?>">
</head>
<body>
    <main>
        <h1><?php echo htmlspecialchars($siteTitle, ENT_QUOTES, 'UTF-8'); ?></h1>
    </main>

    <?php
    $bioCommentOptions = array(
        'enabled' => true,
        'container_class' => 'my-theme-comments',
    );
    require $pluginRoot . '/views/comment-section.php';
    require $pluginRoot . '/views/comment-script.php';
    ?>

    <script src="<?php echo htmlspecialchars($themeAssetBase . 'assets/app.js', ENT_QUOTES, 'UTF-8'); ?>"></script>
</body>
</html>

Runtime context: $typechoBioContext

Before loading home.php, templates/index.php collects shared runtime information into $typechoBioContext. Themes should read values from this array first instead of rebuilding the same data manually.

Common fields include the following.

Basic site information

  • host: the matched domain for the current request
  • siteTitle: site title
  • siteUrl: site URL
  • adminUrl: admin panel URL
  • siteIcon: site icon configured in the plugin settings

User information

  • userLogged: whether the current visitor is logged in
  • userName: logged-in username
  • userGroup: user group

Theme and plugin information

  • themeKey: current theme directory name
  • themeConfig: parsed configuration array for the current theme
  • themeAssetBase: base URL for static assets in the current theme
  • pluginAssetBase: base URL for plugin-level assets
  • pluginName
  • pluginVersion
  • pluginLink

Comments and APIs

  • profileApi: login-state query endpoint
  • commentApi: comment submission endpoint
  • commentTarget: target article information for comments
  • commentList: approved comment list
  • commentResult
  • commentMessage

Other useful data

  • posts: latest posts list; the current implementation defaults to the 10 most recent published posts
  • headPreMeta: custom head snippet configured in the admin panel, inserted before meta content
  • headCustomCss: custom CSS configured in the admin panel
  • headCustomJs: custom JavaScript configured in the admin panel
  • loadStart: request start time

Reading example:

<?php
$ctx = $typechoBioContext;
$config = isset($ctx['themeConfig']) ? $ctx['themeConfig'] : array();
$title = isset($ctx['siteTitle']) ? $ctx['siteTitle'] : '';
$logged = !empty($ctx['userLogged']);
$commentApi = isset($ctx['commentApi']) ? $ctx['commentApi'] : '';

theme.json format

theme.json is the only standard file TypechoBio uses to identify theme metadata and generate configuration forms.

A recommended complete example:

{
  "name": "My Theme",
  "version": "1.0.0",
  "description": "一个适配到 TypechoBio 的主题",
  "fields": [
    {
      "key": "hero_title",
      "label": "首屏标题",
      "type": "text",
      "default": "Hello TypechoBio",
      "description": "显示在首屏的大标题"
    },
    {
      "key": "hero_desc",
      "label": "首屏描述",
      "type": "textarea",
      "default": "这是主题说明",
      "description": "支持多行文本"
    },
    {
      "key": "theme_mode",
      "label": "配色模式",
      "type": "select",
      "default": "system",
      "description": "主题初始配色模式",
      "options": {
        "light": "浅色",
        "dark": "深色",
        "system": "跟随系统"
      }
    },
    {
      "key": "show_comments",
      "label": "启用评论区",
      "type": "checkbox",
      "default": "1",
      "description": "关闭后不渲染评论区"
    }
  ]
}

Top-level fields

  • name: display name of the theme
  • version: theme version, used when the theme marketplace compares updates
  • description: short description
  • fields: array of configuration fields

Supported fields inside fields

Each field object may contain:

  • key: required field key; only letters, numbers, and underscores are allowed
  • label: display name in the admin panel
  • type: field type
  • default: default value
  • description: help text shown in the admin panel
  • options: used only by select

Currently supported type values are limited to:

  • text
  • textarea
  • number
  • select
  • checkbox

Important details:

  • key must not contain spaces, hyphens, dots, or slashes.
  • Nested object configuration structures are not supported. For complex data, serialize it into a string or JSON string.
  • checkbox values are normalized to the strings 1 or 0.
  • select options must be an object. The object keys are actual values, and the object values are the labels shown in the admin panel.

Default value behavior

TypechoBio automatically synchronizes configuration based on theme.json:

  • When a new field is added, its default value is inserted into the configuration JSON.
  • When a field is removed, it is also removed from the configuration JSON and the admin form.
  • When a theme is newly installed, the current page syncs that theme’s default configuration into the theme configuration JSON.

For that reason, default should be filled in carefully. If omitted, the field’s initial value will be an empty string.

How theme configuration is stored

TypechoBio currently keeps two representations of theme configuration, but developers should treat the JSON value as the final structure:

  • Unified JSON: bio_theme_configs_json
  • Admin form field: bio_theme_cfg__{theme}__{key}

In practice:

  • The settings page displays normal form fields.
  • When the form is submitted, values are synchronized back to bio_theme_configs_json.
  • The plugin also normalizes and synchronizes configuration when theme fields are added or removed.

Theme developers should follow these rules:

  • In home.php, read $typechoBioContext['themeConfig'] first.
  • Do not manually assemble bio_theme_cfg__... keys and query them from the database.
  • For complex arrays, lists, or objects, store them in a text or textarea field and decode them inside the theme with json_decode.

Example:

<?php
$config = isset($typechoBioContext['themeConfig']) ? $typechoBioContext['themeConfig'] : array();
$socialLinks = array();
if (!empty($config['social_links'])) {
    $decoded = json_decode((string) $config['social_links'], true);
    if (is_array($decoded)) {
        $socialLinks = $decoded;
    }
}

Static asset handling

TypechoBio intercepts asset requests for the current Typecho frontend theme and serves files directly from the active TypechoBio theme directory.

Recommended asset paths

The most reliable approach is to always build asset URLs with $typechoBioContext['themeAssetBase']:

<link rel="stylesheet" href="<?php echo htmlspecialchars($typechoBioContext['themeAssetBase'] . 'assets/app.css', ENT_QUOTES, 'UTF-8'); ?>">
<script src="<?php echo htmlspecialchars($typechoBioContext['themeAssetBase'] . 'assets/app.js', ENT_QUOTES, 'UTF-8'); ?>"></script>
<img src="<?php echo htmlspecialchars($typechoBioContext['themeAssetBase'] . 'images/avatar.png', ENT_QUOTES, 'UTF-8'); ?>" alt="avatar">

Where to put assets

Keep assets inside the theme directory, for example:

templates/my-theme/
├── home.php
├── theme.json
├── assets/
├── css/
├── js/
├── images/
├── img/
└── font/

Compatibility for root-path assets

The plugin currently resolves these requests back to the active theme directory:

  • Regular request paths under the current theme directory
  • /assets/...
  • /favicon.ico
  • /apple-touch-icon.png
  • /robots.txt

This means that some original themes referencing root-path files such as /assets/app.css may still work under the active TypechoBio theme.

Even so, explicit paths based on themeAssetBase are recommended because they are clearer, do not rely on implicit compatibility behavior, and are easier to migrate, debug, and switch between themes.

Case sensitivity

Linux filesystems are case-sensitive. Asset paths must exactly match the real filenames.

Do not write:

  • pacifico-regular.ttf if the file is actually Pacifico-Regular.ttf
  • images/avatar.png if the directory is actually Images/avatar.png

Relative paths inside CSS must also match the real directory and filename casing.

MIME types

The plugin already maps common asset MIME types, including:

  • css
  • js / mjs
  • json / map
  • svg
  • ico
  • png / jpg / webp / avif
  • woff / woff2 / ttf / otf / eot

So theme build output can usually be placed directly into the theme directory without configuring a separate static server.

Adding the shared comment section

TypechoBio provides a unified comment rendering fragment. When adapting a theme, reuse this shared component whenever possible instead of reimplementing the comment backend for every theme.

Required configuration

The plugin settings must include:

  • 评论文章 ID, namely bio_comment_cid

If this is not configured, the shared comment component will display a message saying that the comment article ID has not been set.

Minimal integration

Add the following to the theme’s home.php:

<?php
$pluginRoot = dirname(dirname(__DIR__));
$config = isset($typechoBioContext['themeConfig']) ? $typechoBioContext['themeConfig'] : array();
$showComments = !isset($config['show_comments']) || in_array((string) $config['show_comments'], array('1', 'true', 'on', 'yes'), true);

$bioCommentOptions = array(
    'enabled' => $showComments,
    'container_tag' => 'section',
    'container_class' => 'my-theme-comment-section',
    'id' => 'bio-comments',
    'title' => '留言板',
    'title_tag' => 'h3',
    'reload_on_success' => true,
    'reload_delay' => 500,
);

require $pluginRoot . '/views/comment-section.php';
require $pluginRoot . '/views/comment-script.php';

Available options

$bioCommentOptions currently supports:

  • enabled
  • container_tag
  • container_class
  • id
  • title
  • title_tag
  • reload_on_success
  • reload_delay
  • target_missing_text
  • empty_list_text

Shared class names

The shared comment template outputs the following class names. Themes only need to style these classes:

  • .bio-comment-heading
  • .bio-comment-status
  • .bio-comment-form
  • .bio-comment-form-row
  • .bio-comment-field
  • .bio-comment-actions
  • .bio-comment-submit
  • .bio-comment-tip
  • .bio-comment-list
  • .bio-comment-item
  • .bio-comment-meta
  • .bio-comment-author
  • .bio-comment-time
  • .bio-comment-reply-label
  • .bio-comment-replyto
  • .bio-comment-text
  • .bio-comment-children
  • .bio-comment-empty

For comment styling, the recommended direction is to make usernames stand out, keep timestamps visually lighter, give the comment body enough emphasis, and make nested replies readable rather than relying only on thin indentation lines.

Remote theme repositories and zip packages

To make a theme installable through TypechoBio’s backend theme marketplace, prepare both:

  • An installable zip package
  • A remote themes.json directory entry

Zip package requirements

During installation, the plugin will:

  1. Download the zip package.
  2. Extract it into a temporary directory.
  3. Search the extracted result for a valid theme directory.
  4. Require the theme directory to contain both home.php and theme.json.
  5. Copy the valid theme to usr/plugins/TypechoBio/templates/<dir>/.

Recommended zip structure:

my-theme.zip
└── my-theme/
    ├── home.php
    ├── theme.json
    ├── assets/
    └── ...

Avoid placing many files directly at the zip root.

Suggested themes.json entry

A remote catalog entry can be structured like this:

{
  "schema_version": 1,
  "themes": [
    {
      "dir": "my-theme",
      "name": "My Theme",
      "description": "主题简介",
      "version": "1.0.0",
      "cover": "https://example.com/cover.jpg",
      "zip_url": "https://example.com/my-theme.zip",
      "github_url": "https://github.com/example/my-theme",
      "demo_url": "https://demo.example.com"
    }
  ]
}

Field meanings:

  • dir: local installation directory name
  • name: display name
  • description: description
  • version: remote version number
  • cover: cover image
  • zip_url: zip download URL
  • github_url: source repository or introduction page
  • demo_url: online demo

Recommended consistency checks:

  • dir should match the theme directory name inside the zip.
  • version should match the version value in theme.json.
  • cover and zip_url should use stable, reachable URLs.

Migrating an existing frontend project

When moving an existing HTML, Vue, React, or static theme project into TypechoBio, a staged process is usually safer.

First: make the page shell run

  • Put the final deployable HTML structure into home.php.
  • Move CSS, JavaScript, fonts, and images into the theme directory.
  • Confirm that the page opens correctly on the TypechoBio independent domain.

Second: connect the plugin context

  • Replace the site title with $typechoBioContext['siteTitle'].
  • Move social links, navigation, descriptions, and other variable content into themeConfig.
  • Replace hardcoded asset URLs with paths based on themeAssetBase.

Third: expose configurable fields in the backend

  • Declare fields in theme.json.
  • Open the plugin settings page and confirm the fields appear.
  • Switch to the theme and confirm its configuration items are visible.
  • Edit and save the fields, then confirm home.php can read the new values.

Fourth: add comments and APIs last

  • If the theme needs comments, configure show_comments first and then include the shared comment fragments.
  • If remote APIs are needed, prefer a same-origin proxy rather than letting the browser directly call unstable third-party endpoints.

Common issues and checks

Theme does not appear in the backend theme list

Check first:

  • Whether templates/<theme>/home.php exists
  • Whether templates/<theme>/theme.json exists
  • Whether theme.json is valid JSON

Theme appears, but configuration fields do not

Check first:

  • Whether the top level of theme.json contains fields
  • Whether fields is an array
  • Whether every field has a valid key
  • Whether each key contains only letters, numbers, and underscores

Default values are not applied

Check first:

  • Whether the field has a default value
  • Whether a checkbox default is written as 1 or 0
  • Whether a complex JSON string default is itself valid JSON

Assets return 404

Check first:

  • Whether paths are based on themeAssetBase
  • Whether filename casing exactly matches the real files
  • Whether relative paths inside CSS are correct
  • Whether the assets are actually inside the currently active theme directory

Comment section does not show

Check first:

  • Whether 评论文章 ID has been filled in the plugin settings
  • Whether show_comments is disabled in the theme configuration
  • Whether home.php really requires both views/comment-section.php and views/comment-script.php

A theme depending on a third-party API errors as soon as the page opens

Check first:

  • Whether the browser is still directly calling a cross-origin API
  • Whether the upstream service has TLS, certificate, or rate-limit problems
  • Whether the request should be changed to a local plugin proxy
  • Whether fallback data with the same shape is provided when the proxy fails

Adaptation checklist

Before considering a new TypechoBio theme ready, verify at least the following:

  • The theme directory contains both home.php and theme.json.
  • The page opens normally under the independent domain.
  • All static assets load successfully.
  • The backend can recognize and display the theme.
  • The backend can switch to the theme.
  • Theme configuration fields appear correctly.
  • Default values enter themeConfig properly.
  • After configuration changes are saved, the frontend can read the new values.
  • The comment section can be enabled or disabled through theme configuration.
  • If third-party APIs are used, they have been changed to same-origin proxies or have fallback behavior.
  • After installing from a zip package, the current page can display configuration fields and default values without needing a manual refresh.

Suggested directory template

A clean theme structure can look like this:

templates/my-theme/
├── home.php
├── theme.json
├── assets/
│   ├── app.css
│   ├── app.js
│   └── vendor/
├── images/
├── font/
├── partials/
│   ├── hero.php
│   └── footer.php
└── README.md

In this layout:

  • home.php assembles the full page.
  • partials/ stores internal reusable theme fragments.
  • assets/ stores build output.
  • README.md may include theme-specific notes, while the plugin-wide conventions should still follow the rules above.