Plugin development
Build a third-party plugin
A plugin is an installable ESM module that activates inside Justflows through a permission-scoped context. It can react to lifecycle events, add UI and blocks, store data, expose routes, and integrate external services.
Start from the supported example
Copy plugins/hello-world in the CE repository. The runtime loads dist/index.js or index.js; it does not execute TypeScript from src.
acme-plugin/
├── justflows.json
├── package.json
├── src/
│ ├── index.ts
│ └── styles/plugin.css
└── dist/
├── index.js
└── styles/plugin.cssimport type { PluginModule } from "@justflows/sdk";
const plugin: PluginModule = {
manifest: {
id: "acme.seo",
name: "Acme SEO",
version: "1.0.0",
license: "GPL-2.0-or-later",
engines: { justflows: ">=0.1.8 <0.2.0" },
permissions: ["content:read"],
main: "index.js",
},
activate(ctx) {
ctx.hooks.action("content.published", ({ contentId }) => {
ctx.logger.info("Content published", { contentId });
});
},
async deleteData(ctx) {
await ctx.data.clear();
},
};
export default plugin;The plugin context
Hooks & capabilities
ctx.hooks registers actions, gates, and filters. ctx.capabilities adds plugin-owned user capabilities while active.Storage & secrets
ctx.data stores plugin-scoped JSON; ctx.secrets stores encrypted credentials; ctx.databases manages prefixed tables.HTTP, jobs & mail
ctx.http exposes /ext/{pluginId} routes. Jobs and mail transports require their manifest permissions.Content & blocks
ctx.content idempotently creates types/pages. ctx.blocks registers namespaced server-rendered blocks.Settings & cache
ctx.settings stores small plugin settings. ctx.cache is namespaced and supports read-through caching.Cookies
ctx.cookies.declare() registers non-essential cookies so consent UI can disclose and enforce them.Permissions are least-privilege
Declare only what you use. The host uses the manifest to decide which context APIs are available and highlights sensitive access during installation.
content:readcontent:createcontent:updatecontent:deletecontent:publishcontent:revisions:readcontent:revisions:restorecontent:revisions:discardmedia:readmedia:uploadmedia:deleteusers:readusers:managesettings:readsettings:manageadmin:extendjobs:registerauth:hooknetwork:outboundmail:transportmail:templatesmail:hookAdd an admin page
Request admin:extend and declare adminMenu. Paths must live below /admin/. A setupPath can open a first-run wizard after activation.
{
"permissions": ["admin:extend"],
"setupPath": "/admin/acme-seo",
"adminMenu": [{
"id": "seo",
"label": "SEO",
"path": "/admin/acme-seo",
"icon": "🔎",
"domain": "extensions"
}]
}Styles that follow the active theme
Read and cache your built stylesheet during activation, then append it through the async theme.css filter. Namespace classes and use public theme variables such as --color-*, --space-*, and --radius-*.
ctx.hooks.filter("theme.css", (current) =>
current.includes(MARKER)
? current
: `${current}\n${MARKER}\n${stylesheet}`
);Cleanup and packaging
Every plugin implements deleteData. Remove plugin data, owned tables, and optionally declared content types according to operator settings. Build before packaging, because the host will not install or compile anything.
pnpm build
COPYFILE_DISABLE=1 tar -czf ../../acme-seo.jfpkg justflows.json dist