UI Extensions

An extension is a package of your own code that Charon loads into the editor at runtime. It can replace the editor of a single field, replace the whole document form with a graph or a node editor, add entries to Charon’s menus, and add pages of its own to the side navigation.

Extensions are Web Components packaged as NPM modules. Nothing about them is framework-specific - React, Angular, Vue, or plain JavaScript all work - and they are installed per project, so everyone working on that project gets the same set.


What an Extension Can Add

Kind

What it replaces or adds

Declared in package.json

Property editor

The input for one field of a document form.

config.customEditors, type: ["Property"]

Grid editor

The cell editor for the same property in the collection grid.

config.customEditors, type: ["Grid"]

Schema editor

The entire editing view of a document - see Implementing a Schema Editor.

config.customEditors, type: ["Schema"]

Custom action

A menu entry in one of four places in the UI - see Custom Actions.

config.customActions

Custom page

A routed, bookmarkable page of its own - see Custom Pages.

config.customPages

A property editor is the smallest of these and the usual starting point. The colour picker below is one - the field is an ordinary Text property, and the extension supplies the swatch and the picker popup:

../../_images/extension_custom_field.png

Whatever the kind, the extension gets the host’s services along with it: game data queries, dialogs and wizards, notifications, navigation, and persisted UI state. See Host Services.


How Extensions Are Declared

Everything Charon needs to know about a package is in its package.json, under config:

{
  "name": "charon-color-picker",
  "version": "1.0.24",
  "main": "main.js",
  "config": {
    "customEditors": [
      {
        "id": "ext-color-picker-hex",
        "selector": "ext-color-picker-editor",
        "name": "Color Picker (Hex)",
        "specification": "format=hex",
        "type": ["Property", "Grid"],
        "dataTypes": ["Text", "Integer"]
      }
    ]
  }
}
  • id is how the editor is referenced from a schema or property, and must be unique across every extension installed in the project.

  • selector is the custom element name the package registers with customElements.define.

  • name is what the user sees when picking the editor.

  • dataTypes limits which data types the editor is offered for.

  • specification is extra configuration in URLSearchParams form. It is merged into the property’s Specification when the editor is picked, which is how one custom element can back three entries - format=hex, format=hsla, format=rgba.

  • icon is optional and takes a Charon icon reference, <set>/<name>: svg/ for Charon’s own icons, emoji/ for an emoji by its short name (emoji/speech_balloon), material/ for a Material icon. The default is svg/extensions.

The full JSON schema for the config section, including custom actions and custom pages, is package.json.schema.json in the examples repository. Point your editor at it for completion and validation while writing.

Choosing an Editor for a Property

Installed editors appear in the Data Type picker of the property form, nested under the data types they declared in dataTypes. Picking one writes editor=<id> into the property’s Specification, together with whatever the extension declared in its own specification field.

A Schema editor is selected the same way but on the schema, by putting editor=<id> into the schema’s Specification. An extension that ships a schema editor usually creates the schema for you through a custom action instead of asking you to type that in.


Installing an Extension

Open Project SettingsExtensions. Each row is an NPM package name and a version; leave the version empty to track the latest published one. The name field completes against the configured registry as you type.

Press Update to save the list. The status icon at the end of each row says what happened:

Status

Meaning

Loaded

The package was downloaded and its code is running. The tooltip names the exact version.

Extension not declared in project settings

The package is present on the server but not in this project’s list.

Extension package not found

No such package in the registry.

No compatible version available

The requested version does not exist, or is not compatible with this Charon build.

Failed to download from registry

The registry was unreachable or rejected the request.

Package exceeds size limit

The archive is larger than the server allows.

Invalid or corrupted package archive

The .tgz could not be unpacked.

Re-Check asks the server whether newer versions of the installed packages are available. Upload NPM Package… takes a .tgz built locally with npm pack and installs it into the server’s extension folder - convenient while developing, but the upload is per installation, so other users of a shared project will not get it. Publish to a registry for anything the team relies on.

The list itself is stored in the project settings inside the game data, so it travels with the data and applies to everyone who opens the project.



When an Extension Fails to Load

A failure is reported in the notifications area and does not stop the editor:

../../_images/extension_failed_to_load_notification.png

The browser console (F12) carries the actual error. Common causes:

  • the package could not be fetched or unpacked - the row’s status icon on the settings page says which of those it was;

  • an id or selector collision - two installed packages declare the same editor id, the same element selector, or the same page id. Charon refuses to load the second one and logs which package it conflicted with;

  • the entry point threw while registering its custom elements;

  • a missing export - a custom action naming a functionName that the bundle does not export is skipped, with a warning naming the function;

  • a dangling page reference - a custom action naming a pageId that is not in the same package’s own customPages produces a link that does not resolve.

If nothing loads at all, check that the uiExtensions feature is enabled for your installation and that the consent prompt was not answered with Don’t Load.


Packaging and Publishing

Build the package, then produce an archive:

npm run build
npm pack

That writes a .tgz next to the build output, ready for Upload NPM Package…. To publish for the whole team instead:

npm publish

Remove "private": true first, and make sure main points at the built entry file. Any .css file listed in the package’s files array is fetched and injected by Charon along with the code, which is how a bundled stylesheet reaches the page.

Versions are resolved when the project loads, so publishing a new version rolls out to everyone with an empty version field the next time they open the editor. Pin a version in the settings row when you need that not to happen.


Examples

The charon-extensions repository holds the type definitions and four complete example packages:

Package

What it demonstrates

charon-extensions

The API contract itself - every interface described in these pages.

charon-logical-toggle

The simplest property editor, in React.

charon-color-picker

A property editor in Angular, including the zoneless setup.

charon-conversation-editor

A full schema editor with a node graph, plus a custom action that creates its schema.

charon-schema-graph

A custom page with a side-navigation entry.

Two scaffolders generate a ready-to-build property editor package:

node src/create-charon-react-extension/index.js my-extension
node src/create-charon-angular-extension/index.js my-extension

See also