WASM plugins can register custom HTTP endpoints to provide REST APIs or serve dynamic content. This is useful for:
Export an http_endpoints() function that returns a JSON array of endpoint definitions:
// assembly/index.ts
export function http_endpoints(): string {
return `[
{
"method": "GET",
"path": "/api/data",
"handler": "handle_get_data"
},
{
"method": "POST",
"path": "/api/update",
"handler": "handle_post_update"
}
]`
}
Handler functions receive a request context and return an HTTP response:
export function handle_get_data(requestPtr: usize, requestLen: usize): string {
// 1. Decode request from WASM memory
const requestBytes = new Uint8Array(i32(requestLen))
for (let i: i32 = 0; i < i32(requestLen); i++) {
requestBytes[i] = load<u8>(requestPtr + <usize>i)
}
const requestJson = String.UTF8.decode(requestBytes.buffer)
// 2. Parse request (contains method, path, query, params, body, headers)
// Simple example: extract query parameter
let filter = ''
const filterIndex = requestJson.indexOf('"filter"')
if (filterIndex >= 0) {
// Extract the filter value from JSON
// (In production, use proper JSON parsing)
}
// 3. Process request and build response data
const data = {
items: [
{ id: 1, value: 'Item 1' },
{ id: 2, value: 'Item 2' }
],
count: 2
}
const bodyJson = JSON.stringify(data)
// 4. Escape JSON for embedding in response string
const escapedBody = bodyJson
.replaceAll('"', '\\"')
.replaceAll('\n', '\\n')
.replaceAll('\r', '\\r')
// 5. Return HTTP response (status, headers, body)
return `{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "${escapedBody}"
}`
}
export function handle_post_update(
requestPtr: usize,
requestLen: usize
): string {
const requestBytes = new Uint8Array(i32(requestLen))
for (let i: i32 = 0; i < i32(requestLen); i++) {
requestBytes[i] = load<u8>(requestPtr + <usize>i)
}
const requestJson = String.UTF8.decode(requestBytes.buffer)
// Process POST body and update state
// ...
return `{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\\"success\\":true}"
}`
}
The request context is a JSON object with:
{
"method": "GET",
"path": "/api/logs",
"query": {
"lines": "100",
"filter": "error"
},
"params": {},
"body": null,
"headers": {
"user-agent": "Mozilla/5.0...",
"accept": "application/json"
}
}
Handler functions must return a JSON string with:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"Cache-Control": "no-cache"
},
"body": "{\"data\": \"value\"}"
}
Important Notes:
body field must be a JSON-escaped string\\" not "/plugins/your-plugin-id/api/.../plugins/your-plugin-id/api/logsThe server uses the AssemblyScript loader for automatic string handling:
For plugin metadata (id, name, schema, http_endpoints):
__getString()For HTTP handlers:
(requestPtr: usize, requestLen: usize) - raw memory pointer__getString()Why manual decoding for handlers? The request is passed as raw UTF-8 bytes for efficiency, but the response is returned as an AssemblyScript string (UTF-16LE) which the loader decodes automatically.
# Test GET endpoint
curl http://localhost:3000/plugins/my-plugin/api/data?filter=test
# Test POST endpoint
curl -X POST http://localhost:3000/plugins/my-plugin/api/update \
-H "Content-Type: application/json" \
-d '{"value": 123}'