Skip to main content

Creating Your First Plugin

Now that you understand the pieces, building one is refreshingly mechanical — because you don't start from scratch. The intended path is to copy the default plugin and adapt it, then let the plugin loader do the wiring.

The five steps

cp -r plugins/default_plugin plugins/my_thing
  1. Copy the default plugin to plugins/{your_slug}/.
  2. Edit manifest.ts — change the slug, entity name, description, workspace statuses, route prefix, and menu.
  3. Edit migrations/001_initial.sql — rename the tables and adjust the columns to your domain.
  4. Build out api/, fe/, and tests/ for your entity — using the copied default-plugin code as your starting point.
  5. Start the API. The loader discovers plugins/my_thing/, validates it, registers the entity, seeds the statuses, and mounts the routes.

That's it — no symlinks, no manual CI/CD changes, no wiring beyond editing files. (The one exception today is frontend route/sidebar registration, which is still added by hand in App.tsx / Layout.tsx.)

A few conventions to get right

  • entity_slug is lowercase snake_case (my_thing, not My_Thing or my-thing). It prefixes your tables and drives your migration path.
  • plugins/ sits at the repo root, a peer of api/ and frontend/; everything about your plugin lives under plugins/{slug}/.
  • The build is already set up to include plugin code: the API and frontend configs pick up plugins/*/api/ and plugins/*/fe/, and the migration script scans plugins/*/migrations/.

Why copying is the right move

The default plugin is a complete, working reference — a manifest with all four extension points, the full table pattern, workspace statuses, frontend pages, and a test suite. Copying it means you start from something that already runs and already respects the contract, then change it into your domain a piece at a time. It's a boilerplate to copy, not a library to import from — so once you've copied it, it's entirely yours.

When your first run doesn't behave, the troubleshooting guide covers the usual culprits — most of which trace back to the loader's validation checks.