Skip to content

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 response object — 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

POST /v1/system/function
{
  "name": "send_welcome_email"
}

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

const name = $input.body?.name || "World"

response.json({ message: `Hello, ${name}!` })

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

POST /v1/system/function/run/{ref}
GET  /v1/system/function/run/{ref}

The {ref} can be the function's UUID, name, or tag.

import { Functions } from "@ptkl/sdk"

const functions = new Functions()
const result = await functions.run("send_welcome_email", {
  input: { email: "john@example.com", name: "John" },
  query: { notify: "true" },
  headers: { "X-Func-Source": "signup-form" }
})
curl -X POST /v1/system/function/run/send_welcome_email \
  -H "Content-Type: application/json" \
  -H "X-Func-Source: signup-form" \
  -d '{ "email": "john@example.com", "name": "John" }'

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:

GET /v1/system/function/signature/{env}/{ref}
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

PATCH /v1/system/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"
}
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

  1. Enable public access and assign an API user:

    {
      "public": true,
      "config": {
        "api_user": "user-uuid"
      }
    }
    

    The api_user is a platform user whose permissions will be used for SDK calls made inside the function.

  2. Generate a signed URL:

    GET /v1/system/function/signature/{env}/{ref}
    

    This returns a URL with an embedded signature that encodes the project and environment.

  3. Call the public endpoint:

    POST /v1/system/function/public/{signature}/run/{ref}
    

Public function limitations

  • $globalEnv is not available
  • $initiator is 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):

curl -X POST /v1/system/function/run/my_function \
  -H "X-Debug: true" \
  -d '{}'

The response will include a _debug field with captured output.

debug

Add structured debug data:

debug.add({ step: "validation", valid: true })
debug.add({ step: "processing", items_count: 42 })

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.