Skip to content

Plugin Development

The following steps walk you through the development of a Hello World plugin that adds a simple message to the annotation editor.

Before starting, make sure you have the following installed:

  • NodeJS (version 22+ recommended)
  • npm

Step 1: Choose from GitHub Template or Manual Setup

Section titled “Step 1: Choose from GitHub Template or Manual Setup”

There is a GitHub template that you can use to quickly bootstrap your new project. However if you prefer you can build your project from scratch.

Step 1.1.1: Create New Project From Template

Section titled “Step 1.1.1: Create New Project From Template”
  1. In your browser, go to the template repository
  2. Above the file list, click Use this template.
  3. Select Create a new repository.

Use this template button

  1. Use the Owner dropdown menu to select the account you want to own the repository.
  2. Type a name for your repository, and an optional description.

Create repository owner selection

  1. Choose a repository visibility.

  2. Click Create repository.

Step 1.1.2: Clone Repository to Your Local Workspace

Section titled “Step 1.1.2: Clone Repository to Your Local Workspace”

In a terminal window clone the repository that you just created:

Terminal window
git clone https://github.com/[Your Repository].git

Change your directory to the cloned repository and run:

Terminal window
npm install

Open your package.json file and name your plugin. It should match the name you created above (i.e. @performant/plugin-hello-world).

{
"name": "@recogito/plugin-template",
"name": "@performant/plugin-hello-world"
"version": "0.1.0",
"description": "A template to boot-strap a new Recogito Studio plugin project.",
"description": "My new Recogito Studio Plugin"
...
}

You can now proceed to Step 2. You will find that some of the proceeding steps have been taken care of by the template.

The entry point is a TypeScript file (src/index.ts) that exports an Astro Integration and registers a Plugin. Here’s a sample implementation:

import type { AstroIntegration } from "astro";
import { Plugin, registerPlugin } from "@recogito/studio-sdk";
const HelloWorldPlugin: Plugin = {
name: "My Hello World Plugin",
module_name: '@performant/plugin-hello-world',
description: "An example Hello World plugin.",
author: "Performant Software",
homepage: "https://www.performantsoftware.com/",
};
const plugin = (): AstroIntegration => ({
name: "plugin-hello-world",
hooks: {
"astro:config:setup": ({ config, logger }) => {
registerPlugin(HelloWorldPlugin, config, logger);
},
},
});
export default plugin;

Step 3: Install the Test Application Template

Section titled “Step 3: Install the Test Application Template”

Run the following command to set up the test application:

Terminal window
npx copy-template

Add a development script to your package.json. (Note that this may already be there if you cloned the template repository.)

{
"scripts": {
"dev": "npm start --prefix .dev/"
}
}

Update .dev/package.json to include your plugin:

{
"dependencies": {
"@performant/plugin-hello-world": "file:../"
}
}

Modify .dev/astro.config.mjs:

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import node from '@astrojs/node';
import HelloWorldPlugin from '@performant/plugin-hello-world';
export default defineConfig({
integrations: [
react(),
HelloWorldPlugin()
],
// ... other configurations
});

Update the test application’s dependencies:

Terminal window
cd .dev
npm install

Create a React component in src/extensions/HelloWorldMessage.tsx:

./src/extensions/HelloWorldMessage.tsx
export const HelloWorldMessage = () => {
return <div>Hello World</div>;
};

Update the index.ts file to register the component as a UI extension:

import type { AstroIntegration } from 'astro';
import { Plugin, registerPlugin } from '@recogito/studio-sdk';
const HelloWorldPlugin: Plugin = {
name: 'My Hello World Plugin',
module_name: '@performant/plugin-hello-world',
description: 'An example Hello World plugin.',
author: 'Performant Software',
homepage: 'https://www.performantsoftware.com/',
extensions: [{
name: 'hello-world-message',
component_name: 'HelloWorldMessage',
extension_point: 'annotation:*:annotation-editor'
}]
};
const plugin = (): AstroIntegration => ({
name: 'plugin-hello-world',
hooks: {
'astro:config:setup': ({ config, logger }) => {
registerPlugin(HelloWorldPlugin, config, logger);
}
}
});
export default plugin;

In the package.json, add an export for the UI extension:

{
"exports": {
".": "./dist/index.js",
"./HelloWorldMessage": "./dist/extensions/HelloWorldMessage.js"
}
}
  1. Build the plugin package:
Terminal window
npm run build
  1. Run the development process in your project root folder:
Terminal window
npm install
npm run dev
  1. Open browser to http://localhost:4321/

The .dev/ harness mounts your extension in isolation, which is ideal for iterating on the component itself. But it renders the extension in a bare container—it does not reproduce the surrounding Recogito Studio UI. Behavior that depends on that context (e.g. layout inside the annotation editor, styles inherited from the client) will only show up in the real client. For those cases, test your unpublished, local plugin against a full recogito-client checkout:

  1. Build the plugin. The client imports the compiled dist/, not your source, so build first (and rebuild after every change):

    Terminal window
    npm run build
  2. Add the checkout as a local file: dependency so the client resolves it by package name. In the client’s package.json, point the dependency at your plugin directory, then npm install:

    "dependencies": {
    // ...
    "@performant/plugin-hello-world": "file:../plugin-hello-world"
    }
  3. Register it. List the package name in INSTALLED_PLUGINS in the client’s .env, then restart the dev server:

    Terminal window
    INSTALLED_PLUGINS="@performant/plugin-hello-world"
  4. Run the client and enable the plugin per project from Project Settings → Plugins.

When you’re done, revert the temporary changes: remove the file: dependency from the client’s package.json (run npm install again), and drop the package from INSTALLED_PLUGINS.

An extension’s extension_point decides where its component mounts. The available points are:

  • admin — the instance admin settings area
  • project:document-actions — the document card actions menu
  • annotation:<view>:annotation-editor — inside the annotation editor
  • annotation:<view>:annotator — alongside the annotator instance
  • annotation:<view>:toolbar — the annotation view toolbar
  • annotation:<view>:taglist — the annotation tag list

<view> is image, text, or * (any view), and each segment accepts * as a wildcard — so annotation:*:annotation-editor targets the editor in every annotation view.

Components receive typed props from the SDK based on their extension point. All extend ExtensionComponentProps (which provides the plugin, extension, and any per-project settings), with richer interfaces for specific points — for example, AnnotationEditorExtensionProps adds the current annotation and an onUpdateAnnotation callback, and DocumentCardActionsExtensionProps adds the document, context, and a closeDialog callback.

Once your plugin is publishable (to the npm registry, or installable from a Git URL), it’s added to a Recogito Studio instance by listing its package name in the INSTALLED_PLUGINS environment variable and rebuilding the client. A Project Admin then enables it per project from Project Settings → Plugins.

See the Plugins guide for the full install-and-enable flow.

The Recogito Studio Client utilizes Radix UI primitives for building accessible and consistent interface components. We recommend that plugin developers also adopt Radix when developing extension components.​

To make it easier to build visually consistent extensions that match Recogito Studio’s native look and feel, the SDK provides a set of pre-defined CSS classes for common UI primitives. The following primitives and classes are currently available:

  • Accordion. Base styles for the Radix Accordion primitive.
  • Avatar. The Recogito-themed user avatar.
  • Button. A range of button variant styles (primary, minimal, unstyled, danger, etc.)
  • Checkbox. Base styles for the Radix checkbox primitive.
  • Dialog. Default Radix popup dialog styles.
  • Dropdown. Base styles for Recogito-themed dropdown menus.
  • Form. Minimal styles for form input elements.
  • Popover. Base styles for the Radix popover primitive.
  • Radio. Base styles for the Radix Radio primitive.
  • Select. Base styles for the Radix Select primitive.
  • Switch. Base styles for the Radix Switch primitive.
  • Tooltip. Styles for Recogito-themed mouse hover tooltips.

To apply SDK-provided theming, all that’s needed is to add the appropriate CSS classes to your components. For example:

// Use Radix Accordion in your extension component
import * as Accordion from '@radix-ui/react-accordion';
// ...
<Accordion.Root
// Apply Recogito-provided `accordion-root` class
className="accordion-root"
type="multiple">
<Accordion.AccordionItem
value="item-1"
// Recogito-provided `accordion-item` class
className="accordion-item">
<Accordion.Header
// Recogito-provided `accordion-header` class
className="accordion-header">
<Accordion.Trigger
// Recogito-provided `accordion-trigger` class
className="accordion-trigger">
Accordion Item 1 Trigger
</Accordion.Trigger>
</Accordion.Header>
<Accordion.AccordionContent
// Recogito-provided `accordion-content` class
className="accordion-content">
Accordion Item 1 Content
</Accordion.AccordionContent>
</Accordion.AccordionItem>
</Accordion.Root>

Styled example:

Styled accordion example

For details, classes and other components, check the stylesheet files directly.

Here are the existing plugins available for Recogito Studio: