Skip to main content

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:

FieldTypeDescription
idstringThe in-script variable name the body references
labelstringDisplay label for the parameter
dataTypestringParameter type (numeric, date, any, etc.)
sampleanySample 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.

List functionsGET /api/functions
GET /api/functions returns a FLAT array (raw SQL select, no HAL collection envelope, no pagination) of every user-defined function in the tenant. No permission gate beyond authentication. Each row carries id, name, namespace, description, script, params, createdAt, updatedAt.
Response · 200
[
{
"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"
}
]
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/functions/{id}

Retrieve a single Function.

Authentication: Required

Pre-blocks: function.lookup(params.id)

Path Parameters:

ParameterTypeDescription
idstring (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).

Get a functionGET /api/functions/00000000-0000-4000-8000-000000000001
GET /api/functions/{id} resolves through the db.lookup handler (plain tenant-scoped find; 404 when missing). `{id}` MUST be a UUID — the natural id form is not accepted here. Returns the function as a HAL resource with a `_links.self`. A `params` item is { id, label, dataType, sample }, not { name, type, description, ... }.
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/functions

Create a user-defined Function.

Authentication: Superuser

Pre-blocks: permission.functions.create, function.validate(payload)

Request Body:

FieldTypeRequiredDescription
namespacestringYesOrganizational namespace (for example custom, math, text)
namestringYesFunction name (used when calling)
scriptstringYesJavaScript function body (not a function(...) {} wrapper)
paramsarrayNoArray of parameter definitions ({ id, label, dataType, sample })
descriptionstringNoDescription (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.

Create a functionPOST /api/functions
POST /api/functions requires permission.functions.create (superuser) and runs the proposed function through function.validate (isolated-vm execution) before persisting. Payload is { namespace (required), name (required), script (required), params?, description? } and is stripUnknown. `script` is a function BODY, not a `function(...) {...}` wrapper. Responds 201 with a Location header.
Request body
{
"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 · 201
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.
Scripts are validated before persisting

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.

Create a function with an invalid scriptPOST /api/functions
The function.validate pre-block executes the proposed function in an isolated-vm before persisting. A reference to an undefined identifier (or any runtime/syntax error) fails validation and the route answers 400 — nothing is saved.
Request body
{
"namespace": "custom",
"name": "brokenFunction",
"script": "return undefinedIdentifier;",
"params": []
}
Response · 400
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid Function - Test execution error: undefinedIdentifier is not defined"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Test a function without savingPOST /api/functions/_test
POST /api/functions/_test runs the same isolated-vm validation as create but persists nothing. The body is { result: <the test-execution completion value> }, NOT a fixed { result: true }: a script of `return 1;` yields { result: 1 }. An invalid definition answers 400.
Request body
{
"namespace": "math",
"name": "square",
"script": "return 1;",
"params": []
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/functions/_test"
}
},
"result": 1
}
Captured from the API examples test suite; ids and timestamps are normalized.
Test an invalid functionPOST /api/functions/_test
When the proposed script fails to execute (here, referencing an undefined identifier), _test answers 400 with the validation error rather than { result: ... }.
Request body
{
"namespace": "math",
"name": "brokenSquare",
"script": "return missingValue * missingValue;",
"params": []
}
Response · 400
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid Function - Test execution error: missingValue is not defined"
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/functions/{id}

Update a Function.

Authentication: Superuser

Pre-blocks: function.lookup(params.id), permission.function.edit

Path Parameters:

ParameterTypeDescription
idstring (UUID)Function id

Request Body (partial):

FieldTypeDescription
namespacestringNamespace
namestringFunction name
descriptionstringDescription (null or empty allowed)
scriptstringJavaScript function body
paramsarrayArray 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.

Update a functionPUT /api/functions/00000000-0000-4000-8000-000000000001
PUT /api/functions/{id} requires permission.function.edit (superuser). It looks up the function, applies the stripUnknown payload ({ namespace?, name?, description?, script?, params? }), re-tests the result in an isolated-vm, and on success saves and returns the updated function. Runs in a DB transaction. An invalid edited script answers 400 and nothing changes.
Request body
{
"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 · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

DELETE /api/functions/{id}

Permanently delete a Function.

Authentication: Superuser

Pre-blocks: permission.function.delete

Path Parameters:

ParameterTypeDescription
idstring (UUID)Function id

Response:

Responds 200 with an empty body (Informer's db.remove convention), not 204. The delete runs in a DB transaction.

Delete a functionDELETE /api/functions/00000000-0000-4000-8000-000000000001
DELETE /api/functions/{id} requires permission.function.delete (superuser) and uses the db.remove handler: it permanently removes the function and returns 200 with an EMPTY body (Informer convention), NOT 204 as the prose claimed. Runs in a DB transaction.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.

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.