JustFlows

Documentation is English-only for now. The rest of the site follows your language.

Plugins

Write a plugin in plugins/<name>/, then install, activate, and manage .jfpkg packages. Manifest, permissions, and the SDK context.

8 min read

Write a plugin

If you are developing against the CE source, create a folder under `plugins/` and start there. One folder per plugin. Copy plugins/hello-world (it listens to content.published). The pnpm workspace includes plugins/*, and the server scans that directory when you run from source. Import types from @justflows/sdk only. Build to dist/index.js — the runtime does not load TypeScript.

  1. 1

    Copy the example

    cp -R plugins/hello-world plugins/acme-seo

  2. 2

    Set the id

    Use a namespaced id in justflows.json and src/index.ts (acme.seo). Declare a GPL-compatible license.

  3. 3

    Write and build

    Edit src/. Then pnpm --filter acme.seo build so dist/index.js exists.

Note

Do not put plugin source under packages/. That tree is platform code. Site owners never copy folders into plugins/ — they install a .jfpkg in Admin.

Install a packaged plugin

Admin → Plugins. Drop a .jfpkg on the page or use justflows plugin install <path>. Then activate. Core is MIT; plugins keep the license in their manifest (example: GPL-2.0-or-later).

Lifecycle APIs

ActionHTTPCLI
ListGET /api/pluginsjustflows plugin list
InstallPOST /api/pluginsjustflows plugin install <path>
ActivatePOST /api/plugins/:id/activatejustflows plugin activate <id>
DeactivatePOST /api/plugins/:id/deactivatejustflows plugin deactivate <id>
DeleteDELETE /api/plugins/:id

Actions fired: plugin.installed, plugin.activated, plugin.deactivated, plugin.uninstalled. Runtime loads active plugins in plugin-runtime before deferred routes register.

Minimal plugin

ts
import type { PluginModule } from "@justflows/sdk";

const plugin: PluginModule = {
  manifest: {
    id: "acme.welcome",
    name: "Acme Welcome",
    version: "1.0.0",
    license: "GPL-2.0-or-later",
    permissions: [],
    main: "index.js",
  },
  activate(ctx) {
    ctx.hooks.action("content.published", (event) => {
      ctx.logger.info("Published", { contentId: event.contentId });
    });
  },
};

export default plugin;

Manifest rules

  • id — dot-separated namespace, e.g. acme.my-plugin.
  • version — semver.
  • license — required; the plugin's own license. Must be a GPL-compatible SPDX identifier (GPL, MIT, BSD, ISC, …). Not inherited from MIT core.
  • permissions — declared up front; sensitive ones are network:outbound, users:manage, settings:manage, auth:hook.
  • main — entry file, default index.js.
  • Optional minJustflowsVersion / maxJustflowsVersion, description, author, homepage.

Plugin permissions

content:read|create|update|delete|publish, media:read|upload|delete, users:read|manage, settings:read|manage, network:outbound, admin:extend, jobs:register, auth:hook.

Context (`activate(ctx)`)

  • hooks — typed action / gate / filter registration (see Hooks).
  • cache — namespaced jf-cache (plugin:{id}:…). remember, get, set, delete, invalidate.
  • settings — plugin settings store.
  • logger — attributed to your plugin id.
  • pluginId, version, permissions set.

Good to know

You do not unregister hooks on deactivate — the loader disposes registrations when the plugin stops. Import types from @justflows/sdk only.