Skip to content
DevelopersExtensionsQuick Start (Manual Setup)

Quick Start (Manual Setup) ​

Besides using the scaffold, you can also write a minimal working SJMCL extension by hand.

1. Create the Folder ​

text
org.example.hello/
├─ sjmcl.ext.json
├─ icon.png
└─ frontend/
   └─ index.js

The folder name can match the extension identifier for easier debugging and packaging.

2. Write sjmcl.ext.json ​

json
{
  "identifier": "org.example.hello",
  "name": "Hello Extension",
  "description": "My first SJMCL extension.",
  "version": "0.1.0",
  "minimalLauncherVersion": "0.8.0",
  "frontend": {
    "entry": "frontend/index.js"
  }
}

Key points:

  • identifier must be namespaced
  • name must not be empty
  • version should be semver
  • minimalLauncherVersion should match your real requirement
  • frontend.entry is relative to the extension root

3. Write the Frontend Entry ​

js
(function registerHelloExtension(factory) {
  const token = document.currentScript?.dataset?.extensionToken || "";

  if (!token) {
    throw new Error("Missing extension activation token");
  }

  window.registerExtension(factory, token);
})(function (api) {
  const React = api.React;
  const { Badge, Button, HStack, Text, VStack } = api.ChakraUI;

  function HelloWidget() {
    const host = api.getHostContext();
    const hostData = api.useHostData();
    const [count, setCount] = host.state.useExtensionState("count", 0);

    return React.createElement(
      VStack,
      { align: "stretch", spacing: 3 },
      React.createElement(
        HStack,
        { justify: "space-between", align: "center" },
        React.createElement(Text, { fontWeight: "bold" }, "Hello Extension"),
        React.createElement(
          Badge,
          { colorScheme: "green", variant: "subtle" },
          hostData.selectedInstance?.name || "No Instance"
        )
      ),
      React.createElement(
        Text,
        { fontSize: "sm" },
        "Current player: ",
        hostData.selectedPlayer?.name || "None"
      ),
      React.createElement(
        Button,
        {
          size: "sm",
          onClick: function () {
            setCount((value) => value + 1);
          },
        },
        "Clicked ",
        String(count),
        " times"
      )
    );
  }

  return {
    homeWidget: {
      title: "Hello",
      defaultWidth: 320,
      minWidth: 260,
      Component: HelloWidget,
    },
  };
});

This covers the basic rules:

  • read the activation token from document.currentScript
  • call window.registerExtension(factory, token)
  • build UI with injected React and ChakraUI
  • use api.useHostData() for reactive host-owned data
  • use host.state.useExtensionState() for extension-owned state

Besides the home widget, you can also create custom pages and standalone windows. See UI Contributions.

4. Optionally Add a Settings Page ​

js
function SettingsPage() {
  const React = api.React;
  const { Box, Text } = api.ChakraUI;

  return React.createElement(
    Box,
    null,
    React.createElement(Text, null, "Hello from extension settings.")
  );
}

return {
  homeWidget: {
    title: "Hello",
    Component: HelloWidget,
  },
  settingsPage: {
    Component: SettingsPage,
  },
};

If settingsPage is returned, SJMCL exposes a dedicated settings entry for the extension.

5. Package It ​

SJMCL accepts both .sjmclx and .zip. They are both zip archives in practice.

bash
zip -r org.example.hello.sjmclx org.example.hello

You can also generate org.example.hello.zip, which the launcher can recognize as well.

6. Import It into SJMCL ​

  1. Open the launcher.
  2. Go to Settings.
  3. Open the Extensions page.
  4. Click Add and select the package.

Newly added extensions are enabled by default. If your extension provides a home widget or page, it will load automatically after the extension list is refreshed.

Debugging ​

  • log through host.actions.logger
  • start with one homeWidget
  • keep persistent UI state in useExtensionState()
  • resolve assets with api.resolveAssetUrl()
  • call host.actions.reloadSelf() when you need a clean reload

If the extension import fails, check these items first:

  • sjmcl.ext.json exists at the extension root
  • identifier is valid
  • frontend.entry points to an existing path
  • minimalLauncherVersion is not higher than the launcher version
  • the entry script actually calls window.registerExtension(...)