Getting started
Build, sign, and ship your first Power-Up. We’ll scaffold a right-rail widget that lists every issue on the active board.
Prerequisites
- Node 20 or later
- pnpm 9 or later
- A Planoda workspace on the Business plan or higher
- Read access to your workspace install secret (shown once at install time)
1. Scaffold a new plugin
The CLI generates a runnable starter kit with manifest, HTML host, and a typed RPC client. The name becomes the plugin slug — lowercase letters, digits, and dashes only.
pnpm exec tsx scripts/create-plugin.ts my-first-powerup2. Open the manifest
The generated manifest.json wires the plugin to one extension point. Declare every capability you need — install reviewers will see this list verbatim. Stick to the minimum scopes so users approve the install.
{
"id": "com.example.my-first-powerup",
"name": "My First Power-Up",
"version": "0.1.0",
"description": "Says hello from the right rail.",
"author": { "name": "Your Name" },
"icon": "https://cdn.example.com/icon.svg",
"capabilities": ["issue.read"],
"extensionPoints": [
{
"point": "right-rail-widget",
"entry": "index.html",
"title": "Hello"
}
],
"scopes": ["issue:read"]
}3. Wire the iframe to the host
Plugins talk to Planoda through a postMessage handshake. The host transfers a MessagePort on load — your code listens for it, then exchanges typed RPC envelopes signed with your install secret.
// main.ts (inside the iframe)
let port: MessagePort | null = null;
window.addEventListener("message", (event) => {
if (event.data?.kind === "atlas.handshake") {
port = event.ports[0];
port.onmessage = handleResponse;
parent.postMessage({ kind: "atlas.ready" }, "*");
}
});
async function call(op, args) {
const id = crypto.randomUUID();
const payload = { version: 1, id, op, args };
const signature = await sign(payload, INSTALL_SECRET);
port!.postMessage({
kind: "atlas.rpc.request",
installationId: INSTALLATION_ID,
payload,
signature,
});
return waitFor(id);
}4. Read live data
Once the channel is open, every host op is one async call. The full op catalog is documented in @planoda/plugin-sdk; here we list every issue on the active board.
const issues = await call("issue.list", { boardId: ctx.boardId });
console.log(`${issues.length} issues on this board.`);5. Test against a local install
Install your plugin into a development workspace from Settings → Plugins → Publish a draft. Drafts are visible only to you and skip review — the install sheet still walks you through the scope grant so your QA mirrors the user flow.
6. Submit for review
When you're ready, click Submit for review on the plugin detail page. The team will sanity-check the manifest, sandbox, and CSP claims; approved plugins land in the public marketplace within two business days.
Security model
- The iframe runs with sandbox="allow-scripts allow-forms" — no same-origin escape, no cookies, no storage access to the host.
- The host issues a 5-minute JWT after the iframe sends `atlas.ready`. Re-authenticate by sending `atlas.ready` again to mint a fresh token.
- Every RPC envelope is HMAC-SHA256-signed with your install secret. Rotate the secret from Settings → Plugins if you suspect compromise.
- The host validates origin AND signature on every inbound message. There is no wildcard postMessage anywhere in the protocol.
Next steps
- Browse the full RPC catalog in
src/lib/plugin-sdk/types.ts. - Read about security & isolation for the production guarantees we make to your users.
- File issues, request RPC ops, or propose new extension points on GitHub.