Skip to content

Plugin format

A plugin is a folder holding a manifest.json and the files that manifest names. This page explains every field. The lists of accepted values (contribution types, placements, hook points, context keys, permissions) are on the plugin API page, which Whittl generates from its own code; where this page and that one ever disagree, that one is right.

New to plugins? Start with the hello world.

The manifest

{
  "name": "word_count",
  "api_version": 1,
  "version": "1.0.0",
  "description": "Count the words in a piece of text. Use when the user asks how long something is.",
  "permissions": [],
  "contributions": [
    { "type": "tool", "entry": "plugin.py", "...": "..." }
  ]
}
Field Required What it is
name yes Lowercase letters, digits and underscores, starting with a letter, 2 to 40 characters. Whittl keys everything by it: the checkbox you tick, your copy replacing a shipped plugin, the tool name the AI sees. Two plugins with the same name in the same folder are both refused.
api_version yes 1. A plugin written for a newer version than this Whittl understands is refused, with the reason shown.
version no Your own version string, shown in the Plugins dialog.
description yes Up to 4,000 characters. For a tool this is the only thing the AI reads when deciding whether to call it, so say what it does and when to use it. Every tool in the plugin is offered with this same description.
permissions no What the plugin says it does: any of files, network, hardware, subprocess. Shown to you; not enforced (see why).
contributions yes A list of what the plugin adds. Each item has a type and the fields below.

A folder that fails any of these rules is still listed in the Plugins dialog, with the reason, rather than silently missing.

A tool

A function the AI can call while it works. It runs in its own process, never inside Whittl.

{
  "type": "tool",
  "entry": "plugin.py",
  "parameters": {
    "type": "object",
    "properties": {
      "text": { "type": "string", "description": "The text to count." }
    },
    "required": ["text"]
  },
  "context": [],
  "examples": [
    { "name": "counts three words", "args": { "text": "a b c" }, "expect_contains": "3" }
  ],
  "timeout": 15
}
def run(args):
    return f"{len(args.get('text', '').split())} words"
Field What it is
entry The Python file holding run(args), or run(args, context) when the tool asks for context. It returns a string, which is what the AI reads back (up to 100,000 characters).
name The tool's name for the AI. Defaults to the plugin's name; give each tool its own when a plugin has several. It may not reuse the name of one of Whittl's own tools or another plugin's.
parameters A JSON Schema object describing args, as for any AI tool. Up to 20,000 characters.
context What the tool receives besides args: any of project_dir (the project's folder), files (every file of the project, as text) and target (the project's kind). Ask only for what the tool needs.
examples Sample calls. Each has args, an optional name, an optional context (so the example answers the same with no project open) and an optional expect_contains (a piece of text the result must include). They are run before the AI may call the tool; see how Whittl checks a plugin.
timeout Seconds before the call is stopped: default 30, at most 120. A stopped tool is killed with everything it started.

A panel

A widget shown inside Whittl. It runs inside Whittl, with Whittl's own access.

{ "type": "panel", "entry": "plugin.py", "title": "Hello", "placement": "statusbar" }
from PySide6.QtWidgets import QLabel


def create_panel(context):
    return QLabel("Hello from my first plugin")
Field What it is
entry The Python file holding create_panel(context), which returns a Qt widget.
title The tab's label (or the dialog's title), up to 24 characters. Code, Assets, Archives, Terminal and Help are Whittl's own and cannot be used.
placement tab (the default): a tab beside Code. dialog: a window opened from Edit > Plugins. statusbar: a small readout at the right of the status bar, at most 320 pixels wide.
order Where a tab goes among the plugin tabs, lower first. Default 100.

context is how a panel reads Whittl: colours, the project's files, the usage log, the terminal, events to subscribe to. It is listed in full on the plugin API page. Take colours from context.colors() and subscribe to theme, or the panel stays in the old colours when the theme changes. Your first panel covers choosing a placement.

A menu action

An item under Edit > Plugins that runs a function when clicked.

{ "type": "menu_action", "entry": "actions.py", "function": "export_report", "label": "Export the report" }
Field What it is
entry The Python file holding the function.
function The function's name. It receives the same context a panel gets. Keep it quick: the window waits for it. Return a string and it is shown in the status bar.
label The menu item's text.

A hook

A function Whittl calls when something happens.

{ "type": "hook", "entry": "hooks.py", "function": "before_generation", "point": "generation.before" }
Point When The payload holds
generation.before A generation is about to start message, target, project_id, files
generation.after A generation finished, successfully or not the same, and cancelled
project.opened A different project is now open project_id, name, target
run.before You pressed Run project_id, files

The function receives one dict, the payload. A generation.before hook may return a string, which is added to the prompt as guidance; every other return value is ignored. Hooks run on a helper thread while the window waits for up to 3 seconds, so never touch a widget from a hook: record what you saw and let a panel show it. A hook that raises three times, or does not return within 3 seconds, is switched off for the session.

A skill

A markdown skill file that joins the skill library while the plugin is enabled, and leaves it when the plugin is switched off.

{ "type": "skill", "file": "my-skill.md" }

Several contributions in one plugin

contributions is a list, so one plugin can add a tool, a tab and a hook together, each naming its own file or sharing one. The Project Pulse template does everything at once: four tools, a tab, a status bar readout, a dialog, two menu actions, four hooks and a skill. File > New Project > Plugin, then Template, shows it.

Older manifests

A manifest written before v2.6.0 contributions existed has kind and entry at the top level instead of a contributions list. It is read as a single contribution and keeps working.