Sharkord
Plugins

Create a Plugin

Scaffold, build, and run your first plugin.

1. Scaffold It

bun create github.com/Sharkord/plugin-example my-plugin

That gives you a working plugin: a slash command, a server action called from a React component, a setting, an event listener, persistent storage, and a server-to-client push.

my-plugin/
  manifest.json       the plugin's identity, and the SDK version it targets
  build.ts            builds the bundle, and installs it if you point it at a server
  publish.ts          cuts a GitHub release for the marketplace
  src/types.ts        the contract both halves share
  src/server/index.ts runs in the Sharkord server process
  src/client/index.ts declares which components render where
  src/client/home.tsx a component

The SDK is not published to npm yet, so link it from the Sharkord repository:

git clone https://github.com/Sharkord/sharkord.git
cd sharkord
bun install
cd packages/plugin-sdk && bun link

The build tooling is a separate repository:

git clone https://github.com/Sharkord/plugin-builder.git
cd plugin-builder
bun install
bun link

Then, in your plugin:

cd my-plugin
bun install
bun link @sharkord/plugin-sdk
bun link @sharkord/plugin-builder

3. Fill In the Manifest

{
  "id": "my-plugin",
  "name": "My Plugin",
  "author": "Me",
  "description": "Does something useful.",
  "homepage": "https://example.com",
  "logo": "https://placehold.co/100x100/white/black",
  "sdkVersion": 2,
  "version": "0.0.1"
}
  • id: lowercase letters, numbers, and dashes, up to 64 characters. It must match the folder the plugin is installed into.
  • sdkVersion: the SDK version the plugin targets. It must equal the server's, or the plugin refuses to load. bun run build writes the right one in for you.
  • version: semver, 1.2.3 or 1.2.3-beta.1. The build takes it from package.json.
  • homepage and logo are optional; both must be http or https URLs when present.

4. Declare the Contract

One type describes what your plugin exposes, and both halves import it. See Contract.

// src/types.ts
type TPlugin = {
  actions: { roll: { payload: { sides: number }; response: number } };
  commands: { roll: { args: { sides: number }; response: string } };
};

export type { TPlugin };

5. Write the Server Half

// src/server/index.ts
import type { PluginContext, UnloadPluginContext } from "@sharkord/plugin-sdk";
import type { TPlugin } from "../types";

const onLoad = async (ctx: PluginContext<TPlugin>) => {
  ctx.logger.log("my-plugin loaded");

  const settings = await ctx.settings.register([
    {
      key: "maxSides",
      name: "Maximum sides",
      type: "number",
      defaultValue: 20,
    },
  ] as const);

  const roll = (sides: number) =>
    1 + Math.floor(Math.random() * Math.min(sides, settings.get("maxSides")));

  ctx.commands.register({
    name: "roll",
    description: "Rolls a die.",
    args: [{ name: "sides", type: "number", required: true }],
    executes: async (invoker, args) => `You rolled ${roll(args.sides)}.`,
  });

  ctx.actions.register({
    name: "roll",
    executes: async (invoker, payload) => roll(payload.sides),
  });
};

const onUnload = async (ctx: UnloadPluginContext) => {
  ctx.logger.log("my-plugin unloaded");
};

export { onLoad, onUnload };

Anything you need to keep on disk goes in ctx.dataPath, not ctx.path: installing an update deletes and rewrites the plugin folder, while the data folder survives updates and is removed with the plugin.

Start with one command or one action. Once it loads cleanly, add settings, UI, hooks, or voice.

6. Build

Point SHARKORD_PLUGINS_PATH in .env at your server's plugins folder:

# development server
SHARKORD_PLUGINS_PATH=<sharkord repo>/apps/server/data/plugins
# installed server
SHARKORD_PLUGINS_PATH=~/.config/sharkord/plugins

Then:

bun run build

The bundle lands in dist/<plugin-id>/, with manifest.json, server/index.js, and client/index.js inside, and build.ts copies it into the folder you configured. That copy is most of the edit-build-reload loop; toggle the plugin off and on to pick up a rebuild.

7. Install It

See Installation. In short: put the built folder in plugins/, enable plugins in the server settings, then enable your plugin.

Things That Will Bite You

  • ctx.path is deleted on every update. Use ctx.dataPath.
  • Tailwind classes mostly do not work. Sharkord compiles Tailwind from its own sources, so a class no Sharkord file uses is not in the stylesheet. Use inline styles with the host's CSS variables (var(--foreground), var(--primary), var(--radius)) so your UI follows the user's theme.
  • Keep as const on your settings definitions, or settings.get loses the type of what it returns.
  • bun tsc --noEmit reports errors from the linked SDK packages, not from your code. Yours are the ones under src/.

Next

On this page