Plugin development

A Marginalia plugin (the user interface calls them extensions) is an OSGi bundle - a JAR with a few extra manifest headers - that Marginalia loads at runtime, without a restart. A plugin can add menu items, tabs, panels and settings to the user interface, keep its own data on books, story parts, lorebooks and user settings, call the model, and take part in generation.

This part of the developer guide explains how plugins work and how to write one. It assumes you can build Marginalia from source (Building from source) and know a little Vaadin Flow (User interface).

How a plugin plugs in

flowchart LR
    subgraph Bundle["Plugin JAR (OSGi bundle)"]
        Act["BundleActivator"] -->|registers| Ext["MarginaliaExtension"]
        Ext --> Dec["ExtensionDecorators"]
        Ext --> Lis["generation listeners"]
    end
    subgraph App["Marginalia"]
        Osgi["OsgiService<br/>(Apache Felix)"]
        ES["ExtensionService"]
        UI["@Extendable UI classes<br/>(instrumented)"]
        Gen["StoryGenerationService"]
        Ent["entities<br/>attributes JSON"]
    end
    Osgi -->|onExtensionLoad| Ext
    Dec -->|registerDecorator| ES
    UI -->|method enter / leave| ES
    ES -->|onMethodEnter / onMethodLeave| Dec
    Lis -->|addEventListener| Gen
    Dec -.->|read / write| Ent

There is no plugin API in the usual sense - no list of extension points with stable signatures. Instead, a plugin gets general mechanisms and the whole application to use them on:

Mechanism What it gives you Page
Bundles Loading, unloading, the MarginaliaExtension lifecycle, access to every Spring service through @Configurable. Plugin basics
@Extendable hooks Code that runs before and after any method of an instrumented UI class, with access to its arguments, local variables and fields, and the possibility to replace the return value. @Extendable hooks
Extended attributes A JSON object on every data entity where a plugin keeps its data - saved with the entity, included in backups, no schema change. Extended attributes
Inherited settings A setting of the plugin that books and folders inherit like the prompts: a field in the Prompts tab and the folder dialog, ManuscriptService.resolveExtension for the value (InheritedSettingRegistry). Inherited settings
Other extensions Offer text about a book or a story part to other extensions or ask them for it (IPCService), offer a suite of tools for the model with a human name, items and commands (ToolService), or call one particular extension by reflection, without importing its classes. Sharing data between extensions
Inference providers A model API of the plugin's own: its settings form in the AI dialog and the inference service of its AIs (ExtensionAIFactory). Inference providers from a plugin
Generation events Listeners that read and change the prompt, the lore, the summaries and the streamed answer while a story part is generated. Generation events, Generation pipeline
Commands Commands for the command bar and for agents: commandService.registerCommands(...) with @Command methods (a suite object), unregistered on unload. Arc Outline, Character Tracker, Lore Generator, Name Generator, Name Provider and Place Provider bring theirs. Writing commands

Extending the UI puts the first three together: adding components, settings panels, threads and cleaning up on unload. Generation events shows how a plugin changes the prompt sent to the model, on the bundled Author's Note. Example plugin builds a small plugin from scratch and walks through the bundled Chapter Marker and Author's Note.

The bundled plugins

The twenty plugins in marginalia/plugins/ are complete, working examples. Each is a separate Maven project.

Plugin What it adds Shows how to
Anthropic Claude Anthropic's Claude models as an inference provider, over the Anthropic Java SDK. Add a provider (ExtensionAIFactory) the same way as the Google plugin, with thinking blocks that must travel with the calls and a stream that is cancelled by closing it.
Arc Outline The arcs of the book and their chapters, each planned, current or finished, in the story sidebar (a chapter can show its note under the description); the model can write an arc from the lore, the chapters of an arc, or an arc with chapters from a range of the active branch of the story. Add a tab to the story editor and a section to the user settings, keep a tree in a book's attributes, stream a structured (JSON schema) answer into the UI with PartialJson, read the messages of the active branch (ChatMessageService.getBranchFromLeaf) and show how many tokens a prompt has (TokenizerService.countTokensApprox, TokenLimits), name tools from another plugin (call it directly), offer data to other plugins with IPCService, plugin-only CSS classes.
Author's Note A per-book note inserted into the prompt of every generation, at a chosen depth and role. Listen to generation events and change the payload sent to the model; add a tab to the story editor.
Beta Readers Personas and a chat about the book with them, alone or in a group. Add a tab to the story editor and its phone twin; register inherited settings; read a model's reply while it streams (InferenceService.stream with a JSON schema); keep per-book data in the attributes of the book.
Chapter Marker Story parts that start with a Markdown heading are shown as chapters in the story sidebar. Read method arguments and locals, change a component the method built. The smallest one - start here.
Character Tracker The characters of the book with their relationships, imported from lore entries if wanted, and a relationship map, in the story sidebar. Add a tab to the story editor, add a button to the fields of the new turn form (reading fields of an @Extendable object through the one the decorator gets), keep a list in a book's attributes, draw a graph with the application's GraphView (Custom components), upload and scale images.
Gemma 4 tools fix Sends the results of function calls to Gemma 4 models with the role tool_responses. Change how a service talks to the model without UI code: register an InferenceServices.ToolRoleResolver on load, remove it on unload.
Google Gemini Google's Gemini models as an inference provider, over the Google Gen AI Java SDK. Add a provider (ExtensionAIFactory), embed a library only the plugin needs, stream with cancellation, call functions.
Image Generator Makes an image for a part of the story: the model writes the prompt, a backend (ComfyUI workflow, OpenAI-style images API) draws it, the user rerolls and accepts it. Add an item to the menu of a part (StoryMenuItem.card), a settings panel on UserPart, a dialog that streams from the model and works in a thread of its own, store images with ResourceService, call HTTP servers around the work (hooks), keep a secret with protectSecret.
Instruction Exporter Copies the instructions, scene settings and characters of the active branch to the clipboard, from the story settings menu. Add an item to the settings menu of the story editor, read the story through a service, write to the clipboard.
Lore Generator Writes a lorebook entry with the model from the existing lore: a generation, then a compaction, optional attachments. Add a column to a grid, open a dialog that streams from the model (with LLMAttachments), keep prompts in the user settings, give the model the functions of another extension (a direct call), let the user choose the model with ModelProtocolPicker.
Lorebook VCS Revision history of lorebook entries, export and import of the history. Insert panels into a view, store data in a lorebook's attributes, replace a field of the view.
Manga Generator Adapts parts of the story into a manga script and image tags, in one large window opened from the story settings menu. Add an item to the settings menu of the story editor that opens a dialog, activate lore for arbitrary text with LorebookActivationService.evaluateAll, fit it to the context with TokenLimits, stream several answers side by side, keep data in a book's attributes.
Name generator list_name_categories and generate_names: names from Markov chains trained at build time on lists of names. Write functions for the model (@LLMFunction), train data at build time with Java programs run by exec-maven-plugin, ship it as a resource.
Name provider list_real_name_lists and pick_real_names: real forenames and surnames of countries picked at random, as pairs of the romanized and the original name. Write functions for the model whose answer is a list of records, ship data imported at build time (a CSV of a repository, cloned and converted by a Java program) as a resource.
Place provider list_place_types, search_places, places_near and distance_between (also as commands): real places from Natural Earth by name, type and country, around a point, and the distance of two. Ship a dataset reduced at build time by a Maven profile (-Pimport-places, committed result), write functions and commands over an in-memory index.
Reviewer AI reviews of a story part with configurable reviewer profiles (a book can pick its own profile). Add a sub-menu to every part, add a settings panel, stream an answer from the model.
Side Query A chat panel next to the story for questions about the book. Add a tab to the story editor, keep per-book sessions, build a prompt from lore and parts, use the data other extensions offer through IPCService (Sharing data).
Converter A tab next to the story with a SillyTavern chat, or the parts of another book (a backup, or a book picked from the list), and the turn details the model prepares from it. Add a tab to the story editor, keep data in a book's attributes, stream a structured (JSON schema) answer, fill the new turn form of the story with NewTurnForm.restoreState.
Chinese localization Simplified Chinese as a language of the user interface. Add a language with ExtensionHasLocalization - no UI code at all.

Users install them as described in Managing extensions.

Reading order

  1. Plugin basics - project layout, the activator and the extension, building and loading.
  2. Example plugin - build one, load it, see it work.
  3. @Extendable hooks - the decorator API in detail and its limits.
  4. Extended attributes - storing data.
  5. Extending the UI - patterns for menus, tabs and settings, threads and unloading.
  6. Generation events - changing the prompt and the answer during generation.
  7. Sharing data between extensions - IPCService, ToolService and calling another extension directly.
  8. Inference providers from a plugin - bringing a model API: factory, settings form, inference service.
  9. Inherited settings - settings of a plugin that books and folders inherit.