Skip to main content
Version: 0.3.1

Developing Plugins

A Shisho plugin is a JavaScript artifact with a manifest. This guide builds a minimal file parser, loads it locally, and tests it in Shisho.

For exact types and edge cases, use the Manifest and Hooks Reference, Host API Reference, and declarations shipped in @shisho/plugin-sdk.

Create a Project​

TypeScript plus a bundler is recommended. Install the SDK as a development dependency so it does not become part of the runtime artifact:

mkdir shisho-example-plugin
cd shisho-example-plugin
npm init -y
npm install --save-dev @shisho/plugin-sdk typescript esbuild
mkdir src dist

A source project may contain any files your toolchain needs. The built plugin directory must have these files at its root:

dist/
manifest.json
main.js

manifest.json declares identity, compatibility, hooks, configuration, and permissions. main.js must create a global object named plugin. Additional runtime files are allowed when your plugin needs them.

Shisho does not provide Node.js APIs, npm package loading, browser DOM APIs, Promises, or timers to plugin code. Bundle dependencies into main.js when they can run in the Shisho runtime, and use the synchronous shisho host API for supported host operations.

An IIFE is a common bundler output pattern, not a runtime requirement by itself. The runtime requirement is that executing main.js creates the global plugin object. Do not depend on a particular ECMAScript version without testing the emitted bundle in Shisho, and do not assume that code accepted by Node.js will be accepted by the plugin runtime.

Add a Manifest​

Create manifest.json in the project root:

{
"manifestVersion": 1,
"id": "example-parser",
"name": "Example Parser",
"version": "0.1.0",
"overview": "Reads titles from Example Book files.",
"description": "A minimal parser used to demonstrate plugin development.",
"capabilities": {
"fileParser": {
"description": "Parses .example files",
"types": ["example"]
}
}
}

The built-in epub, cbz, m4b, and pdf extensions are reserved and cannot be claimed by a file parser. See Manifest Fields for the complete schema.

Implement a Hook​

Create src/main.ts:

import type {
FileParserContext,
ParsedMetadata,
ShishoPlugin,
} from "@shisho/plugin-sdk";

const implementation: ShishoPlugin = {
fileParser: {
parse(context: FileParserContext): ParsedMetadata {
const content = shisho.fs.readTextFile(context.filePath);
const firstLine = content.split("\n")[0]?.trim();

shisho.log.info(`Parsed ${context.filePath}`);
return {
title: firstLine || "Untitled",
authors: [{ name: "Example Author", role: "writer" }],
language: "en",
};
},
},
};

export default implementation;

Build the Runtime Artifact​

Bundle the source and copy the manifest. This esbuild command exposes the default export through a temporary bundle global, then assigns it to the required global plugin:

npx esbuild src/main.ts --bundle --format=iife \
--global-name=ShishoPluginBundle \
--footer:js="var plugin = ShishoPluginBundle.default;" \
--outfile=dist/main.js
cp manifest.json dist/manifest.json

The IIFE is just this build's bundling pattern. Inspect dist/main.js before loading it. It must define a global plugin with the hook object, and it must not rely on require, Node built-ins, unbundled imports, or browser-only globals.

A simple repeatable workflow is:

  1. Type-check source with npx tsc --noEmit using your project's tsconfig.json.
  2. Run unit tests against @shisho/plugin-sdk/testing.
  3. Bundle to dist/main.js and copy manifest.json.
  4. Copy the built artifact into Shisho's local plugin path.
  5. Scan, enable, and exercise it in a real library.

See Testing Plugins for the unit and integration test sequence.

Test in Shisho​

For the example, copy dist/manifest.json and dist/main.js to {pluginDir}/local/example-parser/, then add or scan a file with the declared extension in a test library. Test the Built Artifact in Shisho covers the local plugin location, scanning, enabling, and reloading after changes.

Use shisho.log.debug, info, warn, and error for diagnostic messages. Logs include the plugin scope and ID. Keep secrets out of messages.

Expand the Plugin​

A plugin may implement one or more hooks:

  • inputConverter.convert converts an input into a supported format.
  • fileParser.parse extracts metadata from a file.
  • metadataEnricher.search returns candidate metadata.

Every exported hook must have its matching manifest capability. Permission capabilities must cover every network domain, broad filesystem operation, FFmpeg call, or shell command the plugin uses.

Next steps: