Functions (UDFs) API Overview
The Functions API manages user-defined Functions (UDFs): small pieces of
JavaScript that queries, Datasets, and Reports reuse to transform values.
Every route is prefixed with /api.
A Function's script is a function body, not a function(...) { ... }
wrapper. Informer wraps the body as function <name>(<params>) { <script> }
and self-invokes it, so a valid script returns a value directly (for example
return value.toFixed(2);). A script of function(x) { return x * x; } would
only define an inner function and return undefined.
Each params entry describes one input the body can reference by name:
| Field | Type | Description |
|---|---|---|
id | string | The in-script variable name the body references |
label | string | Display label for the parameter |
dataType | string | Parameter type (numeric, date, any, etc.) |
sample | any | Sample value used as the argument during test execution |
Functions are Tenant-scoped (there is no per-owner scope) and unique by
namespace + name within a Tenant.
Authentication
All Function endpoints require authentication. Reading the Function list and a
single Function needs only an authenticated caller in the Tenant. Creating,
updating, and deleting a Function each require Superuser (the
permission.functions.create, permission.function.edit, and
permission.function.delete checks are bare Superuser checks).
GET /api/functions
List every user-defined Function in the Tenant.
Authentication: Required (no permission gate beyond authentication)
Response:
A flat JSON array (a raw SQL select, with no HAL collection envelope and no
pagination). Each row carries id, name, namespace, description,
script, params, createdAt, and updatedAt.
[
{
"id": "00000000-0000-4000-8000-000000000001",
"name": "formatCurrency",
"namespace": "custom",
"description": "Format a number as a USD currency string.",
"script": "return '$' + value.toFixed(2);",
"params": [
{
"id": "value",
"label": "value",
"sample": 1234.5,
"dataType": "numeric"
}
],
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
},
{
"id": "00000000-0000-4000-8000-000000000002",
"name": "scratchFunction",
"namespace": "custom",
"description": "Throwaway function used for the delete example.",
"script": "return 1;",
"params": [],
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
},
{
"id": "00000000-0000-4000-8000-000000000003",
"name": "average",
"namespace": "math",
"description": "Average the values of a numeric array.",
"script": "return values.reduce(function (a, b) { return a + b; }, 0) / values.length;",
"params": [
{
"id": "values",
"label": "values",
"sample": [
1,
2,
3
],
"dataType": "any"
}
],
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
]
GET /api/functions/{id}
Retrieve a single Function.
Authentication: Required
Pre-blocks: function.lookup(params.id)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string (UUID) | Function id. Must be a UUID; the natural-id form is not accepted here |
The lookup is a plain Tenant-scoped find, so a missing or out-of-Tenant id
answers 404.
Response:
The Function as a HAL resource with a _links.self. The body adds tenant,
source, sourceId, and a computed permissions block (edit, delete).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/functions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"delete": true,
"edit": true
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"namespace": "custom",
"name": "formatCurrency",
"description": "Format a number as a USD currency string.",
"script": "return '$' + value.toFixed(2);",
"params": [
{
"id": "value",
"label": "value",
"sample": 1234.5,
"dataType": "numeric"
}
],
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
POST /api/functions
Create a user-defined Function.
Authentication: Superuser
Pre-blocks: permission.functions.create, function.validate(payload)
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Organizational namespace (for example custom, math, text) |
name | string | Yes | Function name (used when calling) |
script | string | Yes | JavaScript function body (not a function(...) {} wrapper) |
params | array | No | Array of parameter definitions ({ id, label, dataType, sample }) |
description | string | No | Description (null or empty allowed) |
The payload is stripUnknown, so unrecognized fields are dropped.
Example Request:
{
"namespace": "text",
"name": "formatPhone",
"description": "Format a 10-digit US phone number.",
"script": "var cleaned = ('' + phone).replace(/\\D/g, ''); var m = cleaned.match(/^(\\d{3})(\\d{3})(\\d{4})$/); return m ? '(' + m[1] + ') ' + m[2] + '-' + m[3] : phone;",
"params": [
{ "id": "phone", "label": "phone", "dataType": "any", "sample": "8005551234" }
]
}
Response:
Responds 201 Created with the Function (as a HAL resource) and a Location
header. The create runs in a DB transaction.
{
"namespace": "text",
"name": "formatPhone",
"description": "Format a 10-digit US phone number.",
"script": "var cleaned = ('' + phone).replace(/\\D/g, ''); var m = cleaned.match(/^(\\d{3})(\\d{3})(\\d{4})$/); return m ? '(' + m[1] + ') ' + m[2] + '-' + m[3] : phone;",
"params": [
{
"id": "phone",
"label": "phone",
"dataType": "any",
"sample": "8005551234"
}
]
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/functions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"delete": true,
"edit": true
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"namespace": "text",
"name": "formatPhone",
"description": "Format a 10-digit US phone number.",
"script": "var cleaned = ('' + phone).replace(/\\D/g, ''); var m = cleaned.match(/^(\\d{3})(\\d{3})(\\d{4})$/); return m ? '(' + m[1] + ') ' + m[2] + '-' + m[3] : phone;",
"params": [
{
"id": "phone",
"label": "phone",
"sample": "8005551234",
"dataType": "any"
}
],
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"source": null,
"sourceId": null,
"_altid": null
}
The function.validate pre-block executes the proposed Function in an
isolated VM before anything is saved. A syntax error, or a reference to an
undefined identifier, fails validation and the route answers 400 with no row
written.
{
"namespace": "custom",
"name": "brokenFunction",
"script": "return undefinedIdentifier;",
"params": []
}
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid Function - Test execution error: undefinedIdentifier is not defined"
}
POST /api/functions/_test
Validate a Function definition without saving it.
Authentication: Required
Pre-blocks: function.validate(payload)
Request Body:
Same shape as POST /api/functions (namespace, name, script, optional
params and description). Nothing is persisted.
Example Request:
{
"namespace": "math",
"name": "square",
"script": "return 1;",
"params": []
}
Response:
Responds 200 with { result: <the test-execution completion value> }. The
result is the value the script actually returned, not a fixed true: a
script of return 1; yields { "result": 1 }. An invalid definition answers
400 with the validation error.
{
"namespace": "math",
"name": "square",
"script": "return 1;",
"params": []
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/functions/_test"
}
},
"result": 1
}
{
"namespace": "math",
"name": "brokenSquare",
"script": "return missingValue * missingValue;",
"params": []
}
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid Function - Test execution error: missingValue is not defined"
}
PUT /api/functions/{id}
Update a Function.
Authentication: Superuser
Pre-blocks: function.lookup(params.id), permission.function.edit
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string (UUID) | Function id |
Request Body (partial):
| Field | Type | Description |
|---|---|---|
namespace | string | Namespace |
name | string | Function name |
description | string | Description (null or empty allowed) |
script | string | JavaScript function body |
params | array | Array of parameter definitions ({ id, label, dataType, sample }) |
The payload is stripUnknown. The route applies the patch, re-tests the
resulting Function in an isolated VM, and only saves on success. An invalid
edited script answers 400 and nothing changes. The update runs in a DB
transaction.
Example Request:
{
"description": "Average the values of a numeric array, returning 0 for an empty array.",
"script": "return values.length ? values.reduce(function (a, b) { return a + b; }, 0) / values.length : 0;"
}
Response:
Responds 200 with the updated Function.
{
"description": "Average the values of a numeric array, returning 0 for an empty array.",
"script": "return values.length ? values.reduce(function (a, b) { return a + b; }, 0) / values.length : 0;"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/functions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"delete": true,
"edit": true
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"namespace": "math",
"name": "average",
"description": "Average the values of a numeric array, returning 0 for an empty array.",
"script": "return values.length ? values.reduce(function (a, b) { return a + b; }, 0) / values.length : 0;",
"params": [
{
"id": "values",
"label": "values",
"sample": [
1,
2,
3
],
"dataType": "any"
}
],
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
DELETE /api/functions/{id}
Permanently delete a Function.
Authentication: Superuser
Pre-blocks: permission.function.delete
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string (UUID) | Function id |
Response:
Responds 200 with an empty body (Informer's db.remove convention), not
204. The delete runs in a DB transaction.
Using Functions in queries
Once saved, a Function is callable by its namespace.name in Dataset queries
and Reports:
SELECT
custom.formatCurrency(revenue) AS formatted_revenue,
text.formatPhone(phone_number) AS formatted_phone
FROM sales_data
Security
Functions execute in an isolated VM. Scripts cannot reach Node.js built-in modules, make network requests, or touch the file system.