Skip to content

Create a plugin

A plugin is a bundle of files that ships with your novel. It usually includes a script that runs when the novel starts. That script can add new features, or extend and override the behavior of other plugins, including the bundled ones. Anything Inkweaver’s own plugins can do, yours can too.

The quickest way to start is to let the CLI create a plugin for you. Make a folder for your plugin, open a terminal there, and run:

Terminal
ink init-plugin
npm install
my-plugin/
├── .build/
├── .release/
├── assets/
│ └── plugin.json
├── src/
│ ├── commands/
│ │ ├── greet.ts
│ │ └── index.ts
│ ├── selectors/
│ │ └── index.ts
│ ├── index.ts
│ └── plugin-types.ts
├── package.json
└── tsconfig.json

This folder holds the files that ship with your plugin, other than scripts.

It must include plugin.json, and it can hold anything else too, like .psd artwork, .fountain or .fdx screenplays, and audio. Everything in it is available to your novel.

This file sets your plugin’s name, version, and entry point:

{
"name": "my-plugin",
"version": "1.0.0",
"main": "index.js"
}

main is a path relative to the .build folder, not your source folder. See the plugin.json reference for every field.

This is your plugin’s entry point, and it must export a createPlugin function:

import {
bindContext,
ContextKey,
defineInitialState,
Plugin,
} from "@inkweaverdev/inkweaver-sdk";
import * as commands from "./commands";
import * as selectors from "./selectors";
export function createPlugin(key: ContextKey): Plugin {
bindContext(key);
defineInitialState({ greetingCount: 0 });
return {
commands,
selectors,
};
}

createPlugin runs once, when the novel starts. It returns the commands and selectors your plugin provides.

bindContext connects your plugin to its own private state and services. You have to call it before anything else in your plugin uses the SDK.

defineInitialState sets your plugin’s starting state. See State and settings for more.

This file holds the TypeScript types for your plugin’s public API. You declare your commands and selectors here by extending the SDK’s Commands and Selectors interfaces:

import "@inkweaverdev/inkweaver-sdk";
declare module "@inkweaverdev/inkweaver-sdk" {
interface Commands {
greet: (ctx: GreetArgs) => Promise<GreetResult>;
}
interface Selectors {
getGreetingCount: () => GetGreetingCountResult;
}
}
export interface GreetArgs {
name: string;
}
export interface GreetResult {
greeting: string;
}
export interface GetGreetingCountResult {
count: number;
}
export {};

decorateCommand and decorateSelector are typed against these interfaces. Until you declare a command or selector here, no code can decorate it, including your own.

These folders hold your command and selector implementations. Splitting them out is a convention, and it keeps createPlugin tidy as your plugin grows.

This is a standard npm manifest for your plugin.

This is the TypeScript configuration. It sets up how everything in src compiles into the JavaScript your plugin ships.

This temporary folder holds your compiled scripts and a copy of assets, so you can look here to see what your plugin will contain.

This is where your finished .inkb file goes. The file is a zip archive of the .build folder.

Before you can use a plugin in a novel, you need to bottle it. Run:

Terminal
npm run bottle

This compiles your TypeScript and packages your plugin as an .inkb file in the .release folder.

You can try a plugin in a novel without copying it into the novel’s workspace. Bottle your plugin, then add its path to the externals entry in the novel workspace’s .env file:

externals=../../plugins/my-plugin/.release/my-plugin.inkb,../../plugins/my-plugin/.release/another-plugin.inkb

externals is a comma-separated list of paths, relative to the novel’s workspace. Inkweaver includes each one whenever it builds the novel.