Getting Started
Functions API
Serverless functions that run on Cloudflare Workers. Call built-in utilities or deploy your own custom functions with optional SQLite databases.
Overview
Visimade Functions are serverless endpoints that run on Cloudflare Workers. Each function has a unique slug and is accessible at /api/functions/:slug. Functions can be called by anyone (no auth needed) or managed by their owner (auth required).
Architecture: When you call a function, the Visimade API looks up its Worker URL in the database, proxies your request to the Cloudflare Worker, and returns the response. Rate limiting and access control happen at the proxy layer. Functions can optionally include a SQLite database for persistent storage.
Built-in Functions
| Function | Description |
|---|---|
unfurl-url | Fetch a URL and extract Open Graph metadata (title, description, image, favicon) |
Calling Functions
Call any public function by sending a POST request with JSON parameters. No authentication required.
# Unfurl a URL to get its Open Graph metadata
curl -X POST \
-H "Content-Type: application/json" \
-d '{"url": "https://github.com"}' \
https://visimade.com/api/functions/unfurl-urlResponse
{
"success": true,
"data": {
"url": "https://github.com",
"title": "GitHub: Let's build from here",
"description": "GitHub is where over 100 million developers shape the future of software.",
"image": "https://github.githubassets.com/assets/hero-og-image.jpg",
"siteName": "GitHub",
"type": "website",
"favicon": "https://github.com/favicon.ico"
}
}Client-Side SDK
All Visimade pages have window.PageFunctions available automatically. Use it to call functions from your page's JavaScript:
// Generic call — works with any function
const result = await PageFunctions.call('unfurl-url', { url: 'https://github.com' });
// Convenience wrapper for unfurl-url
const result = await PageFunctions.unfurl('https://github.com');
if (result.success) {
console.log(result.data.title); // "GitHub: Let's build from here"
console.log(result.data.image); // "https://github.githubassets.com/..."
console.log(result.data.description); // "GitHub is where over 100 million..."
console.log(result.data.favicon); // "https://github.com/favicon.ico"
}Tip: Use unfurl-url to build link preview cards, social embeds, or bookmark-style displays in your pages.
Endpoints
/api/functions
List all public functions
/api/functions/:slug
Get function metadata
/api/functions/:slug
Call/invoke a function (no auth)
/api/functions
Create a new function (auth, functions:write)
/api/functions/:slug
Update function code or metadata (owner only)
/api/functions/:slug
Delete a function (owner only)
/api/functions/:slug/database
Execute SQL on function's SQLite database (owner only)
GET /api/functions
List all public functions. No authentication required.
curl https://visimade.com/api/functions
Response
{
"functions": [
{
"slug": "unfurl-url",
"displayName": "Unfurl URL",
"description": "Fetch a URL and extract Open Graph metadata",
"owner": "cosmic",
"hasDatabase": false,
"createdAt": "2026-02-10T06:49:00Z",
"updatedAt": "2026-02-10T06:49:00Z"
}
]
}POST /api/functions/:slug
Call a function. The request body is forwarded to the Worker and the response is returned. No authentication required for public functions. Rate limited to 60 calls/minute per IP.
unfurl-url Parameters
| Field | Type | Description |
|---|---|---|
url | string | The URL to unfurl (required) |
unfurl-url Response Fields
| Field | Type | Description |
|---|---|---|
url | string | Canonical URL (from og:url or final redirect) |
title | string | null | Page title (og:title > twitter:title > <title>) |
description | string | null | Page description (og:description > meta description) |
image | string | null | Preview image URL (og:image > twitter:image) |
siteName | string | Site name (og:site_name or hostname) |
type | string | null | Content type (og:type, e.g., "website", "article") |
favicon | string | null | Favicon URL |
POST /api/functions
Create a new function. Your JavaScript code is deployed as a Cloudflare Worker. Optionally create a SQLite database for persistent storage, or share another function's database. Requires authentication with functions:write scope.
Request Body
| Field | Type | Description |
|---|---|---|
slug | string | URL identifier (lowercase, hyphens, max 100 chars). Must be unique. |
code | string | JavaScript source code (ES modules format, max 1MB) |
displayName | string? | Human-readable name |
description | string? | What the function does |
enableDatabase | boolean? | Set to true to create a new SQLite database bound as env.DB |
useDatabase | string? | Slug of another function whose SQLite database to share (must be your own function). Mutually exclusive with enableDatabase. |
# Create a function without a database
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"slug": "hello-world",
"displayName": "Hello World",
"description": "Returns a greeting",
"code": "export default { async fetch(request) { const { name } = await request.json(); return Response.json({ success: true, data: { message: \"Hello, \" + (name || \"World\") + \"!\" } }); } };"
}' \
https://visimade.com/api/functions
# Create a function WITH a new database
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"slug": "todo-api",
"displayName": "Todo API",
"description": "A simple todo list with persistent storage",
"enableDatabase": true,
"code": "export default { async fetch(request, env) { await env.DB.prepare(\"CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER DEFAULT 0)\").run(); if (request.method === \"POST\") { const { title } = await request.json(); const r = await env.DB.prepare(\"INSERT INTO todos (title) VALUES (?)\").bind(title).run(); return Response.json({ success: true, data: { id: r.meta.last_row_id } }); } const { results } = await env.DB.prepare(\"SELECT * FROM todos\").all(); return Response.json({ success: true, data: results }); } };"
}' \
https://visimade.com/api/functions
# Create a function that SHARES another function's database
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"slug": "todo-stats",
"displayName": "Todo Stats",
"description": "Read-only stats for the todo database",
"useDatabase": "todo-api",
"code": "export default { async fetch(request, env) { const { results } = await env.DB.prepare(\"SELECT COUNT(*) as total, SUM(done) as completed FROM todos\").all(); return Response.json({ success: true, data: results[0] }); } };"
}' \
https://visimade.com/api/functionsShared databases: Use useDatabase to bind the same SQLite database to multiple functions. Both functions get full read/write access via env.DB. The database is only deleted when the last function using it is deleted. You can only share databases from functions you own.
Response (201)
{
"slug": "todo-api",
"displayName": "Todo API",
"description": "A simple todo list with persistent storage",
"workerUrl": "https://vm-fn-todo-api.visimade.workers.dev",
"isPublic": true,
"hasDatabase": true,
"createdAt": "2026-02-10T12:00:00Z"
}PATCH /api/functions/:slug
Update a function's code, metadata, or visibility. Only the function owner can update. If code is provided, the Worker is redeployed (database bindings are preserved automatically). Requires functions:write scope.
# Update just the description
curl -X PATCH \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{"description": "Returns a personalized greeting"}' \
https://visimade.com/api/functions/hello-world
# Redeploy with new code
curl -X PATCH \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{"code": "export default { async fetch(request) { return Response.json({ success: true, data: { message: \"Hello v2!\" } }); } };"}' \
https://visimade.com/api/functions/hello-worldDELETE /api/functions/:slug
Delete a function. Removes the Cloudflare Worker, its SQLite database (if any), and the database record. Only the function owner can delete. Requires functions:write scope. Returns 204.
curl -X DELETE \ -H "Authorization: Bearer vm_your_token_here" \ https://visimade.com/api/functions/hello-world
Database (SQLite)
Functions can optionally include a SQLite database for persistent storage. Use enableDatabase: true to create a new database, or useDatabase: "other-slug" to share an existing function's database. Both approaches bind the database to your Worker as env.DB.
How it works: The database is a full SQLite instance running on Cloudflare's edge network. Your Worker code accesses it via env.DB using the SQLite client API. You can also run queries externally via the POST /api/functions/:slug/database endpoint. Multiple functions can share the same database — the database is only deleted when the last function using it is removed.
SQLite Client API (inside your Worker)
| Method | Description |
|---|---|
env.DB.prepare(sql) | Create a prepared statement |
.bind(p1, p2, ...) | Bind parameters to the statement |
.run() | Execute a write query (INSERT, UPDATE, DELETE, CREATE TABLE) |
.all() | Execute a read query and return all rows |
.first() | Execute a read query and return the first row |
Tip: Use CREATE TABLE IF NOT EXISTS at the start of your fetch handler to ensure tables exist on every request. This is idempotent and fast.
POST /api/functions/:slug/database
Execute SQL against a function's SQLite database. Only the function owner can use this endpoint. Useful for schema setup, migrations, data inspection, and seeding. Requires functions:write scope.
Request Body
| Field | Type | Description |
|---|---|---|
sql | string | SQL statement to execute (max 100KB) |
params | array? | Bind parameters for the SQL statement |
# Create a table
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"sql": "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TEXT DEFAULT (datetime('\''now'\'')))"
}' \
https://visimade.com/api/functions/todo-api/database
# Insert a row
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{"sql": "INSERT INTO items (name) VALUES (?)", "params": ["Buy groceries"]}' \
https://visimade.com/api/functions/todo-api/database
# Query rows
curl -X POST \
-H "Authorization: Bearer vm_your_token_here" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM items"}' \
https://visimade.com/api/functions/todo-api/databaseResponse
{
"success": true,
"results": [
{ "id": 1, "name": "Buy groceries", "created_at": "2026-02-14 12:00:00" }
],
"meta": {
"changes": 0,
"duration": 0.5,
"last_row_id": 0,
"rows_read": 1,
"rows_written": 0
}
}Writing Worker Code
Functions run as Cloudflare Workers using the ES modules format. Your code must export a default object with a fetch handler that accepts a Request and returns a Response. If your function has a database, the second argument (env) contains the SQLite binding.
Basic function (no database)
// Minimal function template
export default {
async fetch(request) {
// Only accept POST
if (request.method !== 'POST') {
return Response.json({ success: false, error: 'Method not allowed' }, { status: 405 });
}
// Parse input
const params = await request.json();
// Your logic here
const result = { greeting: 'Hello, ' + (params.name || 'World') + '!' };
// Return result
return Response.json({ success: true, data: result });
},
};Function with SQLite database
// Function with SQLite database (created with enableDatabase: true)
export default {
async fetch(request, env) {
// Create table on first run (idempotent)
await env.DB.prepare(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`).run();
// POST — insert a new item
if (request.method === 'POST') {
const { name } = await request.json();
const result = await env.DB.prepare(
'INSERT INTO items (name) VALUES (?)'
).bind(name).run();
return Response.json({
success: true,
data: { id: result.meta.last_row_id }
});
}
// GET — list all items
const { results } = await env.DB.prepare('SELECT * FROM items').all();
return Response.json({ success: true, data: results });
},
};Rules: Workers must respond within 30 seconds. Code must be under 1MB. Use Response.json() to return JSON. Always return { success: true, data: {...} } or { success: false, error: "..." } for consistency.
Calling your function: Once created, anyone can call it with PageFunctions.call('your-slug', params) from any Visimade page, or via POST /api/functions/your-slug from any HTTP client.
On this page
- Overview
- Calling Functions
- Client-Side SDK
- GET - List Functions
- GET - Function Info
- POST - Call Function
- POST - Create Function
- PATCH - Update Function
- DELETE - Delete Function
- Database (SQLite)
- POST - Query Database
- Writing Worker Code