Plugin Development Guide

Ting Reader plugin development starts with declaring capabilities and permissions in plugin.yml, then calling system capabilities through HostGateway from plugin code, with runtime-specific bridge and packaging differences handled last.

How Plugins Call System Capabilities

Plugins should not read the database, build media paths, or bypass user permissions directly. When a plugin needs books, chapters, progress, media URLs, library files, cache, or tasks, it calls HostGateway. HostGateway validates manifest permissions and the current user context.

  • Capability registers a plugin capability and determines when the system calls the plugin.
  • HostGateway lets a plugin access the host system and determines how it safely reads or operates on system data.
  • JavaScript backend methods call Ting.host.invoke(method, params).
  • web_container UI sends method: "host.invoke" with postMessage; the client forwards it to /api/v1/plugin-host/invoke.
  • WASM calls ting_env.host_invoke, then reads the JSON result with host_response_size and host_read_body.
  • Native libraries receive the Host API through plugin_set_host_api, then call host_invoke(method, params_json, result_json).

Manifest: Declare Capabilities and Permissions

capabilities define the entries a plugin exposes, while permissions define which system capabilities it can call. HostGateway rejects calls that do not have matching permissions.

admin_only: true is a plugin-level manifest field and defaults to false. When enabled, the plugin is visible, installable, and callable only by administrators; regular users will not see it in plugin lists, capability discovery, or client UI entries, and cannot invoke its capabilities or protected routes directly.

id: assistant-tools
name: Assistant Tools
version: 1.0.0
min_core_version: 1.4.8
runtime: javascript
entry_point: plugin.js
author: Your Name
description:
  zh: 提供客户端入口、工具和后台任务
  en: Provides client entries, tools, and background tasks
# Optional: set true for plugins that should be visible and callable only by admins.
admin_only: false

capabilities:
  - id: assistant.panel
    kind: ui_extension
    invoke: openAssistant
    slot: global.floating_action
    title: { zh: AI 助手, en: AI Assistant }
    render:
      mode: web_container
      entry: ui/index.html

  - id: metadata.search
    kind: metadata_provider
    invoke: searchMetadata
    auto_scrape: true
    search_fields:
      - key: title
        label: { zh: 书名, en: Title }
        required: true
        default_from: book.title
    result_fields:
      - key: title
        label: { zh: 标题, en: Title }
      - key: cover_url
        label: { zh: 封面, en: Cover }

  - id: books.tools
    kind: tool_provider
    invoke: invokeTool
    tools:
      - name: books.search
        description: Search books the current user can access

permissions:
  - type: network_access
    value: api.example.com
  - type: books_read
  - type: progress_read
  - type: cache_read
  - type: cache_write

Capability and HostGateway Overview

  • Capabilities live in capabilities; they declare what the plugin exposes, who can call it, which runtime function is invoked, and whether client UI is shown.
  • HostGateway is the only safe way for plugins to access host data, such as books, chapters, progress, media URLs, library files, cache, playlists, and user settings.
  • The runtime file does not define the plugin type. capabilities[].kind does.
  • Every HostGateway call needs the matching permissions entry; missing permissions, missing user context, and out-of-scope access are rejected.

For detailed kinds, slots, render modes, HTTP routes, tasks, events, and UI icon rules, see the “Plugin Capability Declarations” page. For HostGateway method parameters, response shapes, Web container bridge details, WASM/Native error codes, and permission tables, see the “HostGateway Reference” page.

Plugin Configuration

When a plugin needs API keys, endpoints, model names, switches, enum options, or numeric parameters, declare config_schema in plugin.yml. For form rendering, sensitive-field encryption, defaults, runtime reads, and common pitfalls, see the “Plugin Configuration: config_schema” page.

Choose a Runtime

JavaScript Runtime

Best for API integrations, metadata processing, tools, plugin-store sources, and lightweight UI backend logic.

WASM Runtime

Best for cross-platform Rust logic, content processing, and compute-heavy tasks.

Native Runtime

Best for format handling, streaming decryption, system libraries, and platform binaries.

Download trpack and Package Plugins

trpack is the Ting Reader plugin development and packaging CLI. It initializes plugin projects, validates plugin.yml, builds .tr packages, generates signing keys, inspects packages, and unpacks packages for debugging.

After download, rename the file to trpack or trpack.exe and put it on PATH. On Linux and macOS, run chmod +x trpack first.

CommandPurpose
initCreate a plugin project from templates: metadata, format, ui, route, content, or tool.
validateValidate the plugin directory and plugin.yml/plugin.yaml before packaging.
build / packBuild a .tr package; use --include for extra files and --json for machine-readable output.
keygenGenerate an Ed25519 release key so future upgrades keep a stable publisher identity.
signAdd or replace a signature on an existing .tr package.
inspectInspect package metadata, file table, and signature summary; supports --json.
verifyVerify package structure, manifest, file table, and signature status.
unpackUnpack a package into a directory for debugging.
is-trQuickly check whether a file is a .tr plugin package.
trpack init my-plugin --template ui --id my-plugin --name "My Plugin"
trpack validate my-plugin
trpack build my-plugin --output dist/my-plugin.tr --json
trpack inspect dist/my-plugin.tr --json
trpack unpack dist/my-plugin.tr --output unpacked
trpack is-tr dist/my-plugin.tr

Plugins are released as .tr packages. Before publishing, run at least validate, build, and verify to check the manifest, file table, signature metadata, plugin dependencies, and minimum server version.

trpack validate my-plugin
trpack build my-plugin --output dist/my-plugin.tr
trpack verify dist/my-plugin.tr

For public releases, use a stable signing key and keep the private key out of source control. Use trpack sign when an existing package needs to be signed again. The current installer only accepts valid .tr packages: unsigned packages are rejected, while valid but untrusted signatures require user confirmation before installation.

trpack keygen --key-id my-plugin-release --output keys/private.json --public-output keys/public.json
trpack build . --output dist/my-plugin.tr --sign-key keys/private.json
trpack sign dist/my-plugin.tr --key keys/private.json --output dist/my-plugin.signed.tr
trpack verify dist/my-plugin.signed.tr

Do not publish by copying a source directory directly into the server plugin installation directory. Installing a .tr package writes .trpack/package.json and .trpack/signature.json; plugin discovery verifies those files, package file sizes, and sha256 hashes on startup. Editing installed files directly can fail signature verification or cause the plugin to be skipped.

RuntimePackaging notes
JavaScriptUsually package plugin.yml, the entry script, optional ui/ assets, and local modules.
WASMCompile the .wasm file first, then package it together with plugin.yml.
NativeBuild .dll/.so/.dylib per platform and use the matching trpack build for each .tr package.