What’s built in
| Group | Plugins |
|---|---|
| Security | password_auth, totp, api_keys, jwt, session_expiry, csrf, cors, net_guard, rate_limit |
| Data hygiene | validation, slugify, timestamps, computed, cascade, versioning, soft_delete, tenant_scope, organizations |
| Search & AI | search, vector_search, ai_proxy, mcp |
| Integrations | file_storage, cache, cache_client, email, webhooks, stripe, feature_flags, audit_log |
How plugins fit in
Plugins hook into the runtime at well-defined points:- Inspect or modify a request before the handler runs (rate limit, CORS, CSRF, tenant_scope)
- Inspect or modify data before write (validation, slugify, timestamps, computed)
- React to a write after it lands (webhooks, audit_log, cascade)
- Add new HTTP routes (api_keys, totp, stripe webhooks)
- Replace a built-in subsystem (file_storage swaps the storage backend, email swaps the sender)
Enabling a plugin
Inpylon.manifest.json:
crates/plugin/src/builtin/. Configuration shape varies per plugin — see each plugin page. Plugins missing config use sensible defaults.
TypeScript-side configuration
Some plugins are easier to configure from TS than from JSON — they accept callbacks (computed field functions, custom validation rules, webhook handlers). Configure those in your app entry file:
pylon codegen picks these up and merges into the manifest before the runtime boots.
Plugin order
When multiple plugins observe the same event, they run in manifest declaration order. Most plugins commute (rate limiting first, then validation, then write — order doesn’t change the result). Two cases where order matters:tenant_scopeshould come before any plugin that filters rows. It injectsorgId == auth.tenantIdinto queries; running it after another filter could narrow the wrong set.audit_logshould come last. You want to record what actually landed, not what was proposed pre-validation.
Performance
Plugins run in-process. There’s no IPC, no per-request allocation overhead beyond what each plugin’s logic does. The hot-path plugins (rate_limit, validation, timestamps) are written to be ~microseconds per call. Audit log and webhooks defer work to a background queue so they don’t block the response.Writing your own
If a built-in doesn’t fit, write a plugin in Rust (~50 lines for a typical shape). See Writing your own plugin. For TypeScript-only logic — custom validation, custom computed fields, custom webhook handlers — you don’t need a Rust plugin. Use thevalidation, computed, webhooks, or cascade plugins with TS-side configuration.
Plugins vs functions
| Plugins | Functions |
|---|---|
| Implicit — fire on every relevant op | Explicit — called by name |
| Configured in manifest | Defined in TS files |
| Can mutate data in-flight | Run inside a transaction; return a result |
| Cross-cutting concerns (audit, rate limit, validation) | Business logic |
| Compiled into the binary (built-ins) or loaded at boot | Hot-reloaded by the dev server |
Disabling a plugin
Remove the entry frommanifest.plugins. Hot-reload picks it up on the next request — no restart needed. The plugin’s tables (e.g. AuditLog) are not deleted; data persists in case you re-enable.
To purge tables created by a plugin, drop them via pylon migrate apply after editing the manifest.