Platform Functions
Platform functions are standalone serverless endpoints scoped to your project. Unlike component functions which are attached to a specific component, platform functions are independent — they receive HTTP requests and return HTTP responses, making them ideal for webhooks, API endpoints, integrations, and custom backend logic.
Overview
Each platform function consists of a name and one or more versioned expressions (JavaScript code). Functions run inside a secure sandbox with full access to the platform SDK and utility libraries.
Key characteristics:
- HTTP-native — receive request body, query params, headers, form data, and file uploads
- Must return a
responseobject — supports JSON, HTML, XML, redirects, and custom status codes - Versioned — separate dev and public versions for safe iteration
- Public access — optionally expose functions without authentication via signed URLs
- Environment variables — per-function and per-environment configuration
Creating a Function
API
A new function is created with an empty expression and a default dev version of 0.1.0.
SDK
import { Functions } from "@ptkl/sdk"
const functions = new Functions()
const list = await functions.list()
const fn = await functions.get("send_welcome_email")
Signature
A platform function can declare a typed signature that describes its expected input and output. When set, $input.body is validated against signature.input before the expression executes — if the caller sends data that doesn't match, the call is rejected immediately with a structured error.
{
"signature": {
"input": {
"amount": { "type": "number", "required": true },
"currency": { "type": "string", "required": true },
"discount": { "type": "number", "required": false }
},
"output": {
"type": "object",
"fields": {
"net": { "type": "number" },
"vat": { "type": "number" },
"gross": { "type": "number" }
}
}
}
}
The signature is edited in the Signature tab inside the platform function editor. Types use the same TypeDef structure as component IDLs.
Validation is open (lenient) — extra fields on objects are accepted, only missing required fields and type mismatches cause failures. When no signature is set, no validation is applied.
Tip
When a signature is defined, the expression editor automatically provides $input autocomplete and type errors based on the declared input shape.
Writing a Function
A platform function is a JavaScript expression that processes an incoming request and returns a response using the response object.
Basic Example
Full Example
// Validate input
if (!$input.body?.email) {
response.status(400).json({ error: "Email is required" })
return response
}
// Use the SDK to interact with components
const { Component } = $sdk.version('0.9')
const users = new Component("users")
// Check if user already exists
const existing = await users.findOne({ email: $input.body.email })
if (existing) {
response.status(409).json({ error: "User already exists" })
return response
}
// Create the user
const user = await users.create({
email: $input.body.email,
name: $input.body.name,
status: "pending"
})
response.status(201).json({ user_id: user.uuid })
Running a Function
Authenticated
The {ref} can be the function's UUID, name, or tag.
Public (No Authentication)
Public functions can be called without any authentication using a signed URL:
POST /v1/system/function/public/{signature}/run/{ref}
GET /v1/system/function/public/{signature}/run/{ref}
To generate a public URL:
import { Functions } from "@ptkl/sdk"
const functions = new Functions()
const { data } = await functions.generateSignature("send_welcome_email", "public")
// Returns a ready-to-use public URL
Note
Public functions do not have access to $globalEnv or $initiator. A scoped API user token is generated internally to authorize SDK calls within the function.
The response Object
Platform functions must build their response using the global response object. This is what differentiates them from component functions, which simply return a value.
All methods return this for chaining.
| Method | Description |
|---|---|
response.json(body) |
Send a JSON response (application/json) |
response.html(body) |
Send an HTML response (text/html) |
response.xml(body) |
Send an XML response (text/xml) |
response.send(body) |
Send a generic JSON response |
response.status(code) |
Set the HTTP status code |
response.redirect(url) |
Send a 307 redirect |
Examples
// JSON response with custom status
response.status(201).json({ created: true })
// HTML page
response.html("<h1>Welcome</h1><p>Your account has been created.</p>")
// XML response
response.xml("<result><status>ok</status></result>")
// Redirect
response.redirect("https://example.com/thank-you")
// Error response
response.status(422).json({
error: "Validation failed",
fields: { email: "Invalid email format" }
})
Execution Context
Request Input
The $input object contains the parsed HTTP request:
| Property | Type | Description |
|---|---|---|
$input.body |
object |
JSON-parsed request body |
$input.query |
object |
Parsed URL query parameters |
$input.headers |
object |
Request headers (only X-Func-* prefixed) |
$input.form_data |
object |
Parsed multipart form data fields |
$input.files |
array |
Uploaded files from multipart requests |
// Read JSON body
const email = $input.body?.email
// Read query parameters
const page = $input.query?.page || "1"
// Read custom headers
const source = $input.headers?.["X-Func-Source"]
// Access uploaded files
const file = $input.files?.[0]
if (file) {
console.log(file.key, file.filename, file.size)
}
Environment & Configuration
| Variable | Type | Description |
|---|---|---|
$env |
object |
Per-function environment variables for the current environment |
$globalEnv |
object |
Project-level global environment variables |
$currentEnv |
string |
Current environment name ("dev" or "public") |
$initiator |
object |
The user or service that invoked the function (authenticated only) |
$sdk |
object |
Protokol SDK factory — $sdk.version('0.9') |
// Per-function env var
const apiKey = $env.STRIPE_SECRET_KEY
// Global env var
const webhookSecret = $globalEnv.WEBHOOK_SECRET
// Environment check
if ($currentEnv === "dev") {
console.log("Running in development mode")
}
Versioning
Each function supports multiple versions, with two active pointers:
| Pointer | Description |
|---|---|
| Dev version | Used when the function runs in the dev environment |
| Public version | Used in all other environments (production) |
This allows you to iterate on a function in development without affecting production traffic.
Updating a Function
{
"settings": [
{ "version": "0.1.0", "expr": "response.json({ v: '0.1.0' })" },
{ "version": "0.2.0", "expr": "response.json({ v: '0.2.0' })" }
],
"dev_version": "0.2.0",
"public_version": "0.1.0"
}
import { Functions } from "@ptkl/sdk"
const functions = new Functions()
await functions.update("function-uuid", {
settings: [
{ version: "0.1.0", expr: "response.json({ v: '0.1.0' })" },
{ version: "0.2.0", expr: "response.json({ v: '0.2.0' })" }
],
dev_version: "0.2.0",
public_version: "0.1.0"
})
Environment Variables
Functions support per-environment variables stored in the function definition:
{
"env_variables": {
"dev": {
"STRIPE_SECRET_KEY": "sk_test_...",
"WEBHOOK_URL": "https://dev.example.com/hook"
},
"public": {
"STRIPE_SECRET_KEY": "sk_live_...",
"WEBHOOK_URL": "https://example.com/hook"
}
}
}
These are available as $env inside the function. The correct set is selected based on the execution environment.
Public Functions
Making a function publicly accessible allows external services to call it without authentication — useful for webhooks, callbacks, and public APIs.
Setup
-
Enable public access and assign an API user:
The
api_useris a platform user whose permissions will be used for SDK calls made inside the function. -
Generate a signed URL:
This returns a URL with an embedded signature that encodes the project and environment.
-
Call the public endpoint:
Public function limitations
$globalEnvis not available$initiatoris not available- The function runs with the permissions of the configured
api_user
Debugging
Console Logs
console.log() output is captured during execution. To retrieve logs, include the X-Debug: true header (or ?_debug=true query param for public functions):
The response will include a _debug field with captured output.
debug
Add structured debug data:
Available Libraries
Platform functions have access to the same libraries as component functions:
Core Utilities
| Global | Description |
|---|---|
moment |
Date/time manipulation |
_ |
Lodash utility functions |
handlebars |
Template engine |
Buffer |
Binary data operations |
Security & Encoding
| Global | Description |
|---|---|
bcrypt |
Password hashing — genSalt, hash, compare |
crypto |
Cryptographic operations — hash, hmac, encrypt, decrypt, sign, verify |
jwt |
JSON Web Tokens — sign, verify, decode |
Error Handling
Errors thrown inside a platform function are caught automatically and returned via the response object:
// The runtime catches uncaught errors and calls response.setError()
throw new Error("Something went wrong")
For structured error responses, set the status and body explicitly:
response.status(500).json({
error: "Payment processing failed",
code: "PAYMENT_ERROR",
details: { provider: "stripe", reason: "card_declined" }
})
Permissions
| Action | Required Permission |
|---|---|
| Create, update, delete, list | manage base_functions |
| Run a specific function | run function::{name} |
| Public execution | No permission required (uses api_user internally) |
Execution Environment
| Property | Value |
|---|---|
| Memory limit | 128 MB |
| Timeout | 30 seconds |
| Async support | Full async/await |
| Call depth | Maximum 5 nested function calls |
| File upload | Up to 15 GB |
Warning
Functions run in an isolated sandbox. Node.js system modules (e.g. fs, path, net) are not available. Use the SDK and provided libraries instead.