Sail Hub Docs Sail Hub

Making plugins

A plugin is a folder with a plugin.json and a JavaScript file. Sail loads it into the launcher's window and hands it the Sail API.

Only changing how Sail looks?

A layout is easier, needs no code, and anyone can install it safely.

The folder#

my-plugin/
  plugin.json   the manifest
  index.js      your code
  ...           anything else your code uses

plugin.json#

{
  "id": "yourname.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "author": "Your name",
  "description": "What it does, in a sentence.",
  "main": "index.js",
  "platforms": ["win32", "linux"],
  "settings": [
    { "type": "heading", "label": "General" },
    { "id": "greeting", "type": "text", "label": "Greeting", "default": "Hello" },
    { "id": "loud", "type": "toggle", "label": "Shout it", "default": false }
  ]
}
FieldWhat it's for
idA unique id, yourname.plugin-name in lowercase. Needed for other plugins to depend on yours. Never reuse one for something else.
name, version, author, descriptionShown in the Plugin Manager. Bump version for every update so Sail can offer it.
mainThe JavaScript file Sail runs.
platformsOptional. The systems it works on: win32, linux. Sail won't start it anywhere else and says why.
settingsOptional. Options Sail draws in a settings window (see below).
requiresOptional. Other plugins yours needs (see below).

The main script#

Export one function. Sail calls it with the Sail API and your plugin's own context. Return an object with dispose() that undoes everything you did, so your plugin can be switched off and on without restarting Sail.

module.exports = function (SailAPI, context) {
    const button = document.createElement('button');
    button.textContent = context.settings.get('greeting');
    button.addEventListener('click', () => alert(`${context.name} says hi`));
    document.body.append(button);

    // Settings changed in Sail's settings window arrive here.
    const stop = context.settings.onChange((id, value) => {
        if (id === 'greeting') button.textContent = value;
    });

    return {
        dispose() {
            stop();
            button.remove();
        }
    };
};

Plugins run in the launcher's window with Node.js available, so require('fs') and friends work. Use that power carefully: people trust you with their PC.

The context#

MemberWhat it is
context.name, context.versionYour plugin's name and version.
context.folderThe name of your plugin's folder.
context.settings.get(id)A setting's value (its default until the user changes it).
context.settings.getAll()Every setting as an object.
context.settings.set(id, value), reset(id)Change a setting, or put it back to its default.
context.settings.isSet(id)Whether the user has changed it from the default.
context.settings.onChange(callback)Called with (id, value, all) when a setting changes. Returns a function that stops listening.
context.settings.setChoices(id, options)Replace a select setting's choices while Sail runs, for example with things found on the PC.
context.settings.open()Open your plugin's settings window.

Settings#

List your options in settings and Sail draws the window, keeps the values on each PC, and tells you when they change.

TypeExample
toggle{ "id": "colors", "type": "toggle", "label": "Colours", "description": "…", "default": true }
select{ "id": "size", "type": "select", "label": "Size", "default": "m", "options": [{ "value": "s", "label": "Small" }, { "value": "m", "label": "Medium" }] }
number{ "id": "count", "type": "number", "label": "Games shown", "default": 5, "min": 1, "max": 10, "step": 1, "slider": true }
text{ "id": "name", "type": "text", "label": "Nickname", "default": "", "maxLength": 40 }
heading{ "type": "heading", "label": "Appearance" }

Ids start with a letter and use letters, numbers, - and _. A value that doesn't fit its setting falls back to the default.

Depending on another plugin#

"requires": {
  "someone.other-plugin": { "version": "^2.0.0", "hub": "<its Sail Hub item id>" }
}

Versions can be exact (2.0.0), ^2.0.0 (2.x from 2.0.0), ~2.0.1 (2.0.x from 2.0.1) or >=2.0.0. With hub, Sail offers to install the plugin from Sail Hub when it's missing. Your plugin starts after the ones it needs, and waits switched off while any of them is missing, off or too old.

Packaging#

Zip the folder. plugin.json can be at the top of the zip or one folder down. Test it with Import Plugin, then upload it to Sail Hub.

Add requirements while you upload

When you upload a plugin .zip to Sail Hub, the upload form reads its plugin.json and shows a Requirements section. Search Sail Hub for the plugins yours needs and pick them; the form fills in their ID, a version range and their Sail Hub item for you, and can give your plugin an id too. It writes all of that into plugin.json just before uploading, and leaves every other file untouched. A .rar or .7z can't be edited this way, so add requires by hand before packing those.

Last updated 21 October 2026