RaisFastRaisFast
Plugins

Manifest Reference

Complete manifest.toml schema — plugin info, permissions, hooks, routes, cron, dependencies.

Every plugin has a manifest.toml that defines its identity, permissions, hooks, routes, and cron jobs.

[plugin] — Plugin Identity

[plugin]
id = "com.example.my-plugin"
name = "My Plugin"
version = "0.1.0"
description = "Does something useful"
author = "Alice"
license = "MIT"
runtime = "js"
entry = "main.js"
FieldRequiredDefaultDescription
idyesUnique plugin ID (reverse-domain format recommended)
nameyesDisplay name
versionyesSemantic version
descriptionno""Description
authornoAuthor name
licensenoLicense identifier
runtimeno"wasm"Runtime: js / lua / rhai / wasm
languageno"rust"Language identifier
entryno"index.js"Entry file name
wasmno"plugin.wasm"WASM file path (runtime = wasm only)
sdk_versionno"v1"SDK version

Auto-detection: if runtime = "lua" and entry is not set, the loader looks for init.lua. If runtime = "rhai", it looks for init.rhai.

[permissions] — Security

Access is denied unless explicitly declared.

[permissions]
max_memory_mb = 16
timeout_ms = 5000
database = ["read:products", "write:orders", "categories"]
http = ["api.example.com", "*.github.com"]
config = ["app.*", "jwt.*"]
filesystem = ["read-write"]
FieldDefaultDescription
max_memory_mb32 (config)Memory limit per instance
timeout_msconfig defaultHook execution timeout
database[] (denied)Database table access
http[] (denied)HTTP whitelist
config[] (denied)Config key whitelist
filesystem[] (denied)VFS access level

Database Permissions

FormatAccess
"read:TABLE"Read-only
"write:TABLE"Write-only
"TABLE"Full read + write
"*"All tables (except protected)

Protected tables are always blocked regardless of permissions: users, roles, permissions, audit_log, plugin_storage, options, rbac_roles, rbac_permissions, rbac_role_permissions, tenants.

SQL safety: DDL is blocked (CREATE/DROP/ALTER/TRUNCATE). UNION, JOIN, and subqueries are detected and blocked in permission checks.

HTTP Whitelist

  • Exact domain: api.example.com
  • Wildcard subdomain: *.github.com
  • Path wildcard: api.example.com/v1/*

Built-in SSRF protection: blocks localhost, 127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x, ::1.

Filesystem Permissions

ValueEnables
"read"vfsRead, vfsExists, vfsList, vfsStat
"write"vfsWrite, vfsDelete
"read-write"All VFS operations
"*"All VFS operations

[hooks.<name>] — Hook Registration

[hooks.on-content-created]
priority = 50

[hooks.on-content-updating]
priority = 100
match = "product"

[hooks.on-content-updating]
priority = 100
content_types = ["product", "course"]

[hooks.render-markdown]
priority = 10
FieldDefaultDescription
priority100Execution order (lower = first)
matchMatch by content type name
content_types[] (all)Only fire for specified content types

Hook names use kebab-case in TOML (on-content-created), auto-converted to snake_case internally (on_content_created).

[[routes]] — Custom API Endpoints

[[routes]]
method = "GET"
path = "/api/v1/plugins/my-plugin/stats"
handler = "getStats"
auth = "public"

[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/contacts/:contactId"
handler = "updateContact"
auth = "admin"
description = "Update a contact"
FieldRequiredDefaultDescription
methodyesHTTP method
pathyesRoute path, supports :param placeholders
handleryesFunction name in the plugin
authnodefaultpublic / member / admin
descriptionnoDescription
permissionnoExtra permission requirement

Handlers receive input with { path, method, body, headers, params }. Return data directly — the framework wraps it in { code: 0, data: ... }.

Route Input Parameters

[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/deals"
handler = "createDeal"

[[routes.input]]
name = "title"
type = "string"
in = "body"
required = true
description = "Deal title"

[[routes.input]]
name = "page"
type = "integer"
in = "query"
default = 1
description = "Page number"
FieldRequiredDefaultDescription
nameyesParameter name
inno"query"Location: query / body / path / header
typeno""Type hint
requirednofalseWhether required
descriptionnoDescription
defaultnoDefault value

Route Output

[routes.output]
description = "Deal list"

[[routes.output.fields]]
name = "id"
type = "string"
description = "Deal ID"

[[routes.output.fields]]
name = "title"
type = "string"
description = "Deal title"

[[cron]] — Scheduled Tasks

[[cron]]
label = "Daily Cleanup"
job_type = "daily_cleanup"
cron_expr = "0 0 * * *"
payload = """{"type": "full"}"""
enabled = true
FieldRequiredDefaultDescription
labelyesTask name
job_typeyesTask type (passed to on_cron_tick)
cron_expryesCron expression (seven segments including seconds)
payloadnoJSON payload string
enablednotrueWhether enabled

Cron entries are synced to the database on plugin load and removed on unload.

[[content_types]] — Bundle Content Types

[[content_types]]
file = "content_types/contact.toml"

Plugins can ship their own Content Type TOML files. They are auto-loaded when the plugin is installed.

[[admin_pages]] — Admin UI Pages

[[admin_pages]]
path = "/admin/plugins/crm"
label = "CRM"
icon = "users"
component = "CrmDashboard"
FieldRequiredDescription
pathyesPage path
labelyesDisplay name
iconnoIcon name
componentnoFrontend component name

[dependencies] — Plugin Dependencies

[dependencies]
"com.example.base" = "1.0.0"

Plugins are loaded in topological sort order to ensure dependencies initialize first.

Full Example

[plugin]
id = "com.example.crm"
name = "CRM API"
version = "0.1.0"
description = "Sales pipeline and contact management"
author = "Alice"
license = "MIT"
runtime = "js"
entry = "main.js"

[permissions]
max_memory_mb = 16
timeout_ms = 5000
database = ["crm_contacts", "crm_companies", "crm_deals"]
http = ["api.example.com"]
config = ["app.*"]
filesystem = ["read-write"]

[hooks.on-content-created]
priority = 50

[hooks.on-content-updating]
priority = 100
content_types = ["contact"]

[hooks.render-markdown]
priority = 10

[[routes]]
method = "GET"
path = "/api/v1/plugins/crm/pipeline"
handler = "getPipeline"
auth = "public"

[[routes]]
method = "GET"
path = "/api/v1/plugins/crm/pipeline/:dealId"
handler = "getDealDetail"
auth = "member"

[[cron]]
label = "Daily Stats"
job_type = "daily_stats"
cron_expr = "0 0 * * *"
payload = """{"type": "full"}"""

[dependencies]
"com.example.base" = "1.0.0"

On this page