Getting Started
Plugin Development
Write your first RaisFast plugin in minutes — JS, Lua, or Rhai.
A plugin is a folder with a manifest.toml and an entry file. That's it.
Your First Plugin
raisfast plugin new my-plugin --runtime jsextensions/plugins/my-plugin/
├── manifest.toml
└── main.jsWrite a hook handler in main.js:
import { logInfo } from 'sdk';
export function on_content_created(input) {
const data = JSON.parse(input);
logInfo("new content: " + data.id);
}Start the server — the plugin loads automatically. Every time content is created, you'll see the log.
Three Languages, Same Plugin
JavaScript
import { dbQuery, ok, logInfo } from 'sdk';
export function on_post_creating(input) {
const data = JSON.parse(input);
if (!data.slug) {
data.slug = data.title.toLowerCase().replace(/ /g, '-');
}
return JSON.stringify(data);
}
export function getProducts() {
return ok(dbQuery("SELECT * FROM products"));
}- QuickJS (ES2024, async/await)
import { ... } from 'sdk'
Lua
local sdk = require("sdk")
Plugin = {}
Plugin.on_post_creating = function(input)
local data = sdk.extractJson(input, "body")
if not data.slug then
data.slug = string.lower(data.title):gsub(" ", "-")
end
return sdk.ok(data)
end
Plugin.getProducts = function(input)
return sdk.ok(sdk.dbQuery("SELECT * FROM products"))
end- Lua 5.4 (sandboxed)
local sdk = require("sdk")
Rhai
fn on_post_creating(input) {
if input.slug == "" {
input.slug = to_lower(input.title);
input.slug = replace(input.slug, " ", "-");
}
input
}
fn get_products(input) {
let rows = db_query("SELECT * FROM products", "");
rows
}- Rhai (Rust-native, no SDK import needed)
- Host functions injected into global scope
Real Example: SEO Optimizer
Auto-generate slugs and cache posts to the virtual filesystem.
[plugin]
id = "com.example.seo-js"
name = "SEO Optimizer"
version = "1.0.0"
runtime = "js"
entry = "main.js"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10import { ok, logInfo, vfsWrite, vfsExists, vfsRead } from 'sdk';
export function on_post_creating(input) {
const data = JSON.parse(input);
if (!data.slug) {
data.slug = data.title.toLowerCase().replace(/ /g, '-');
}
logInfo("[seo] slug: " + data.slug);
return JSON.stringify(data);
}
export function on_post_created(input) {
const data = JSON.parse(input);
vfsWrite("cache/" + data.slug + ".json", JSON.stringify(data));
}
export function seoStats() {
const count = vfsExists("stats.json") ? vfsRead("stats.json") : "0";
return ok({ total: parseInt(count) });
}[plugin]
id = "com.example.seo-lua"
name = "SEO Optimizer (Lua)"
version = "1.0.0"
runtime = "lua"
entry = "init.lua"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10local sdk = require("sdk")
Plugin = {}
Plugin.on_post_creating = function(input)
local data = sdk.extractJson(input, "body")
if not data.slug then
data.slug = string.lower(data.title):gsub(" ", "-")
end
sdk.logInfo("[seo] slug: " .. data.slug)
return sdk.ok(data)
end
Plugin.on_post_created = function(input)
local data = sdk.extractJson(input, "body")
sdk.vfsWrite("cache/" .. data.slug .. ".json", Host.jsonEncode(data))
end[plugin]
id = "com.example.seo-rhai"
name = "SEO Optimizer (Rhai)"
version = "1.0.0"
runtime = "rhai"
entry = "init.rhai"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10fn on_post_creating(input) {
if input.slug == "" {
input.slug = to_lower(input.title);
input.slug = replace(input.slug, " ", "-");
}
log("info", "[seo] slug: " + input.slug);
input
}
fn on_post_created(data) {
let key = "cache/" + data.slug + ".json";
vfsWrite(key, to_json(data));
}Hooks
[hooks.on-post-creating] # Before create (can modify data)
priority = 10
[hooks.on-post-created] # After create (side effects)
priority = 50| Hook | Type | When |
|---|---|---|
on-content-creating | filter | Before content creation (modify data) |
on-content-created | action | After content creation |
on-content-updating | filter | Before content update |
on-content-updated | action | After content update |
on-content-deleted | action | After content deletion |
on-post-creating | filter | Before post creation |
on-post-created | action | After post creation |
render-markdown | filter | Override markdown rendering |
on-login | action | After user login |
on-cron-tick | action | Cron job fires |
Custom Routes
[[routes]]
method = "GET"
path = "/api/v1/plugins/my-plugin/stats"
handler = "getStats"
[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/orders"
handler = "createOrder"
auth = "admin"Handlers receive input with { path, method, body, headers, params }. Return data directly — the framework wraps it in { code: 0, data: ... }.
Cron Jobs
[[cron]]
label = "Daily Cleanup"
job_type = "daily_cleanup"
cron_expr = "0 0 * * *"Handled by on_cron_tick, filtered by job_type.
Permissions
Access is denied unless explicitly declared:
[permissions]
database = ["products", "write:orders"]
http = ["api.example.com"]
filesystem = ["read-write"]Host API
| Function | What it does |
|---|---|
dbQuery(sql, params) | SELECT |
dbExec(sql, params) | INSERT / UPDATE / DELETE |
dbBegin/Commit/Rollback() | Transactions |
httpGet(url) / httpPost(url, body) | HTTP requests |
getConfig(key) | Read config |
getData/setData(key, value) | Plugin KV store |
vfsRead/Write/Delete/Exists | Virtual filesystem |
getPost(slug) | Get post by slug |
log(level, msg) | Logging |
emitEvent(type, data) | Fire event |
