AstroBaaS

For developers

Extend it without
asking permission.

One command scaffolds a shippable plugin package. One environment variable loads it into any install. The API surface is typed, versioned, and refuses incompatibility with a sentence instead of a stack trace.

Sixty seconds, honestly measured

A plugin is a file
the host imports.

Scaffold the shippable shape

A standalone npm package: factory entry, its own build, zero runtime imports from the host — the host hands your factory its own helpers, so your bundle runs inside any install regardless of versions.

npx astrobaas plugin package acme-stamps
cd acme-stamps && npm install && npm run build

Load it with one variable

A path or a package name. Activation is separate, so an operator installs once and switches modules in the admin. A module that fails to load is reported with its reason — on boot, and by the deep health check a deploy script asserts.

ASTROBAAS_PLUGINS=./acme-stamps/dist/index.mjs \
ASTROBAAS_PLUGINS_ACTIVATE=acme-stamps npm run dev

Persist from a route, import nothing

Handlers receive a store already namespaced to your plugin. This exact route runs in the platform test suite — two calls count 1, then 2.

handler: async ({ store }) => {
  const n = ((await store.get('counters', 'hits'))?.n ?? 0) + 1;
  await store.put('counters', 'hits', { n });
  return new Response(JSON.stringify({ ok: true, hits: n }));
}

Declare what you need, get refused politely

An incompatible module is refused at load — “requires core ^9.0.0, but this host provides plugin API 1.0.0” — instead of loading and failing somewhere deep at request time.

requiresCore: '^1.0.0'

The surface

What a plugin
can put its hands on.

API routes
Under /api/plugin/<id>/ with declared access: public, staff or admin — enforced by the middleware before your handler runs. A route may declare csrf exemptions only inside its own namespace; a plugin cannot widen a core gate. Core paths always win: a plugin claiming /api/settings/get simply does not get it.
Admin screens
Server-rendered pages under /admin/plugin/<id>/, in the sidebar automatically, admin-only unless you declare roles. Authorisation rides the same longest-prefix table as core screens.
Storage
A namespaced store handed to your route handlers as ctx.store and to your migrations — one plugin cannot read another’s records, as a property of the API rather than a rule to remember. Migrations run at bootstrap, stamped per step.
Payments
Gateways and manual methods through two hooks. A provider is only offered when its declared env vars exist, and a manual method can never shadow a gateway id — a shop cannot accept as unpaid what it believes was charged.
Content types
Register custom collections with schemas; they get REST endpoints, visibility rules and admin CRUD without you writing routes.
Themes
Design tokens with a dark palette, slot overrides for the public views, whole-page patterns, and a customizer the admin already knows how to drive.

Hooks, the honest sample

Twenty-one hooks.
Here are the interesting five.

post_content
(html, post) => htmlPost body HTML. Re-sanitized after filters — you cannot inject script, by construction.
product_price
(cents, product, { qty }) => centsFinal unit price at checkout. Sales rules, volume discounts, member pricing. Integer cents.
order_line_extras
(result, { line, product }) => resultThe vertical’s hook: validate a line, refuse the order, or freeze extra data onto it. How the optical module checks prescriptions.
payment_providers
(providers) => providersContribute a payment gateway. A forged webhook answers 401 if your error class ends in VerificationError.
commerce_schema
(schema, { name }) => schemaPublish a machine-readable form schema so a storefront renders your domain without hard-coding it.
… and 16 more
PLUGIN_HOOKS in astrobaas/coreAll 21 documented, with payload contracts and the reduce-order rules that keep a refusal from being overwritten.