Writing plugins
A plugin is an npm package that adds blocks. The built-in blocks are a plugin too, so yours work the same way.
Plugin blocks show up in the IDE’s palette, get a settings form and are checked by the Problems panel, just like the built-in ones. The IDE loads plugins in a separate process, so plugin code never runs inside the editor itself.
A block has two halves
Section titled “A block has two halves”- Metadata (
defineNodeMeta): its type, title, category, output ports and settings. The IDE builds the settings form from it. Keep it free of discord.js and Node APIs so the editor can load it safely. - The run function (
defineNode): what the block does. It only runs inside the bot.
Example: a dice block
Section titled “Example: a dice block”import { defineNode, defineNodeMeta, definePlugin, VARIABLE_FIELD_FORMAT,} from "@makecord/plugin-sdk";import { z } from "zod";
const roll = defineNodeMeta({ type: "dice.roll", title: "Roll Dice", description: "Rolls a die and saves the result in a variable.", category: "data", outputs: ["next", "jackpot"], fields: z.object({ sides: z.coerce.number().int().min(2).default(6).meta({ title: "Sides" }), variable: z .string() .regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/) .default("roll") .meta({ title: "Save as variable", format: VARIABLE_FIELD_FORMAT }), }), locales: { tr: { title: "Zar At", description: "Zar atar ve sonucu bir değişkene kaydeder.", fields: { sides: { title: "Yüz sayısı" }, variable: { title: "Değişkene kaydet" } }, outputs: { next: "devam", jackpot: "en yüksek" }, }, },});
export default definePlugin({ id: "makecord-plugin-dice", version: "1.0.0", engines: { makecord: "^0.1.0" }, nodes: [ defineNode(roll, (ctx, { sides, variable }) => { const value = 1 + Math.floor(Math.random() * sides); ctx.vars.set(variable, value); return value === sides ? "jackpot" : "next"; }), ],});A setting can hold a variable like {{options.sides}}, which is filled in as text before the block runs, so use z.coerce.number() for numbers.
Settings and the form
Section titled “Settings and the form”The settings form is built from the zod object. Each field’s .meta() controls how it looks:
| Schema | Form control |
|---|---|
z.string() |
One-line text box |
z.string().meta({ multiline: true }) |
Multi-line text box |
z.boolean() |
Checkbox |
z.enum([...]) |
Dropdown |
z.number() / z.coerce.number() |
Number |
.meta({ format: VARIABLE_FIELD_FORMAT }) |
The name of a variable the block saves. Other blocks get {{vars.<name>}} in autocomplete. Add alsoSets: ["Status"] if the block also saves <name>Status |
.meta({ format: MESSAGE_FIELD_FORMAT }) |
A picker for message designs from messages/. Send the design with ctx.message(name) |
title and description in .meta() are the label and the hint under it. Fields without a .default() are required, and leaving one empty shows up in the Problems panel.
- Namespaced types. A block’s
typestarts with the plugin name withoutmakecord-plugin-and without an npm scope.makecord-plugin-dicegetsdice.*.definePluginthrows if a type doesn’t match. Never change a type after release, because flows refer to it. - Ports. Return the name of the output port to follow, or nothing to follow
next. Declare extra ports withoutputs: ["true", "false"]. - Where it can be used.
needsInteraction: truelimits a block to flows that can answer someone: commands, buttons, menus and forms.events: ["autocomplete"]limits it to flows for those events.contexts: ["command"]or["event"]limits it to one kind of flow. The palette hides blocks that can’t be used in the open flow, and the Problems panel flags them if they’re there anyway. - Bot permissions. List the Discord permissions the block needs, like
permissions: ["ManageRoles"]. The invite helper ticks them, and the bot warns at startup about servers where it’s missing them. - Palette group.
group: "members"puts an action under that heading in the palette (interaction,messages,membersorserver). Any other name makes a heading of its own; translate it withlocales.<lang>.group. - Compatibility.
engines.makecordis the runtime range your plugin supports. - Translations.
title,descriptionand field titles are the English base. Add other languages underlocales.
What a block can use
Section titled “What a block can use”The first argument of the run function is the context:
| Property | What it is |
|---|---|
ctx.client |
The logged-in discord.js Client |
ctx.interaction |
The interaction to answer, in command, button, menu and form flows |
ctx.eventArgs |
The raw discord.js event arguments |
ctx.scope |
The values behind {{…}} variables |
ctx.vars |
The flow’s variables, a Map |
ctx.store |
The project’s stored data: get, set, delete, increment, keys |
ctx.message(name) |
A message design from messages/, ready to pass to send(), reply() or edit() |
ctx.nodeId |
This block’s ID, handy as a key for per-block state |
ctx.secret(name) |
A secret’s value, if the plugin asked for it |
ctx.log(level, message) |
Writes to the Run panel or the server console |
Throwing an error stops the flow and reports it, with the block, in the Run panel.
Secrets
Section titled “Secrets”Ask for the secrets you need in permissions:
definePlugin({ id: "makecord-plugin-weather", permissions: ["secrets:WEATHER_API_KEY"], // …});ctx.secret("WEATHER_API_KEY") returns the value. Anything not listed returns undefined, and DISCORD_TOKEN is never available to plugins.
Publishing to the marketplace
Section titled “Publishing to the marketplace”The marketplace in Settings → Plugins lists npm packages with the makecord-plugin keyword. Add it to your package.json and publish:
{ "name": "makecord-plugin-dice", "type": "module", "keywords": ["makecord-plugin"], "exports": { ".": "./dist/index.js" }}Export your plugin as the package’s default export, and keep a plain default (or require) condition in exports so the runtime can resolve it.
Trying a plugin while you write it
Section titled “Trying a plugin while you write it”In Settings → Plugins, choose Install from folder… and pick your plugin’s folder. The IDE installs it into the open project and loads its blocks. After you change the plugin’s code, install it again to reload it.
Using a plugin in a project
Section titled “Using a plugin in a project”From the IDE, open Settings → Plugins (or File → Plugins…) and press Install. That runs npm install in the project and adds the plugin to makecord.json.
By hand, it’s the same two steps:
- Install it in the project:
npm install makecord-plugin-dice. - Add it to
makecord.json:"plugins": ["makecord-plugin-dice"].
If a plugin can’t be loaded, the Problems panel says why.