Create a Plugin
Scaffold, build, and run your first plugin.
1. Scaffold It
bun create github.com/Sharkord/plugin-example my-pluginThat 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 component2. Link the SDK
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 linkThe build tooling is a separate repository:
git clone https://github.com/Sharkord/plugin-builder.git
cd plugin-builder
bun install
bun linkThen, in your plugin:
cd my-plugin
bun install
bun link @sharkord/plugin-sdk
bun link @sharkord/plugin-builder3. 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 buildwrites the right one in for you.version: semver,1.2.3or1.2.3-beta.1. The build takes it frompackage.json.homepageandlogoare optional; both must behttporhttpsURLs 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/pluginsThen:
bun run buildThe 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.pathis deleted on every update. Usectx.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 conston your settings definitions, orsettings.getloses the type of what it returns. bun tsc --noEmitreports errors from the linked SDK packages, not from your code. Yours are the ones undersrc/.
Next
- Commands and Actions for behavior
- Components and Client SDK for UI
- Marketplace to publish it