Getting started
This guide builds a plugin that lines the selected layers up in a row or a column with even gaps, then shows how to install, run and iterate on it.
1. Create the files
Make a folder with two files:
tidy-up/
├── manifest.json
└── main.js
manifest.json
{
"id": "yourname.tidy-up",
"name": "Tidy Up",
"version": "1.0.0",
"description": "Lines the selected layers up in a row or column, 16px apart.",
"author": "Your Name",
"main": "main.js",
"permissions": ["read", "write"],
"commands": [
{ "id": "row", "label": "In a row" },
{ "id": "column", "label": "In a column" }
]
}main.js
/// <reference types="@popcraft/plugins" />
const GAP = 16
;(async () => {
const selection = await popcraft.getSelection()
if (selection.length < 2) {
await popcraft.closePlugin('Select two or more layers first')
return
}
const row = popcraft.command === 'row'
// Keep the order they already have on the canvas.
const sorted = [...selection].sort((a, b) => (row ? a.x - b.x : a.y - b.y))
let at = row ? sorted[0].x : sorted[0].y
const line = row ? Math.min(...sorted.map(n => n.y)) : Math.min(...sorted.map(n => n.x))
for (const node of sorted) {
await popcraft.updateNode(node.id, row ? { x: at, y: line } : { x: line, y: at })
at += (row ? node.width : node.height) + GAP
}
await popcraft.closePlugin(`Tidied ${sorted.length} layers`)
})()Things to notice:
- Every API call is asynchronous —
awaitthem. popcraft.commandtells you which command the user picked.- Call
popcraft.closePlugin()when you're done. The optional message is shown as a notification.
For editor autocomplete, install the SDK — npm i -D @popcraft/plugins — and keep that /// <reference>
line at the top of main.js. VS Code and other TypeScript-aware editors pick it up with no other setup, and
the types come from the same source as the runtime your plugin actually calls, so they are never out of date.
Working examples to copy from: getpopcraft/plugins.
2. Package it
PopCraft installs either format:
-
A
.zipof the folder.manifest.jsonmay be at the top level of the zip or inside a single top-level folder. Files referenced bymainanduiare resolved relative to the manifest. -
A single
.jsonbundle, handy when you generate the plugin from a build step:{ "manifest": { "id": "…", "name": "…", "version": "…", "main": "main.js", "permissions": [] }, "main": "/* the contents of main.js */", "ui": "<!-- the contents of ui.html, if the manifest has one -->" }
On macOS or Linux:
cd tidy-up && zip -r ../tidy-up.zip . -x '.*'Or build a JSON bundle with Node:
// pack.mjs — usage: node pack.mjs tidy-up
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
const dir = process.argv[2]
const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8'))
const bundle = { manifest, main: readFileSync(join(dir, manifest.main), 'utf8') }
if (manifest.ui) bundle.ui = readFileSync(join(dir, manifest.ui), 'utf8')
writeFileSync(`${manifest.id}-${manifest.version}.json`, JSON.stringify(bundle))3. Install and run
Description
A brainstorm board with its sticky notes selected. From ⋯, Manage plugins… opens the plugin manager, and Install from file… installs Arrange in Grid from its package: it is listed with its commands and the permissions it asks for. Its Grid command is clicked and the permissions are allowed; its panel asks for the gap and the columns, 4 is typed, and Arrange lays the stickies out in a tidy grid.
- Open any file in PopCraft.
- Open the main menu (top-left) → Manage plugins… → Installed → Install from file… and pick your
.zipor.json. - Run it from the main menu (Tidy Up: In a row) or with the command buttons in the plugin manager.
- The first run asks the user to approve the plugin's permissions.
4. Iterate
- Reinstalling a plugin with the same
idreplaces it. If the new version asks for the same (or fewer) permissions, the earlier approval is kept; otherwise the user is asked again. - Each run starts the plugin fresh. Running a plugin that's already running restarts it.
- Errors thrown in your script (including unhandled promise rejections) appear as error notifications.
- The plugin is sandboxed, but
console.logworks — open your browser's developer tools and look for the plugin's frame in the console context picker.
Next steps
- Add a panel: Plugin UI
- Put something interactive on the canvas: Widgets
- Share it: Publishing