Skip to content
Documentation Prelude Collector 1.0.0

Custom Transforms

Reference for writing, registering, and managing Starlark transforms in Prelude Collector — value types, return contract, available globals, and the transforms API.

When the built-in transforms do not cover your case, you can write your own using Starlark — a deterministic, sandboxed scripting language with Python-like syntax. After this first mention, the rest of this page refers to Prelude Collector as "the collector".

Custom transforms are stored in the database, registered into the in-memory transform registry at startup, and hot-reloaded after any change so they become usable in field mappings without a restart.


What is Starlark?

Starlark is a dialect of Python designed for embedded scripting. In the collector it is:

  • Python-like — familiar if / else, for, while, string operations, list comprehensions.
  • Sandboxed — no file I/O, no network access, no module imports. The only observable side effect is print(...), which is wired to the collector's debug log channel (see Debugging below).
  • Deterministic — the same input always produces the same output.
  • Step-limited — execution is capped at 1,000,000 Starlark steps to prevent infinite loops. There is no separate wall-clock timeout; a tight CPU-bound loop will run until the step cap trips.

The following extensions are enabled in addition to the Starlark core:

  • set() literals and operations
  • while loops
  • Recursion

The expression you write

The collector wraps your code in a def transform(value): function. You write only the body — the lines that go inside that function.

Function signature

The expression receives a single argument named value and must return a value:

return value * 2

The collector internally builds:

def transform(value):
    return value * 2

Argument types

The value argument is always one of:

Starlark type Origin
int Any integer type (e.g. int, int64, uint32).
float Floating-point numbers.
str Strings.
bool Booleans.
bytes Raw byte sequences. Round-tripping is lossy: returning a bytes value from the transform converts it to a Go string via fmt.Sprintf("%v", …), not back to raw bytes.
None A null / missing source value.

Return contract

Return any of the same types listed above. Returning None is treated as a failure: a warning is logged and the original value is kept. Raising or otherwise erroring follows the same rule — see Error handling on the overview page.


Available globals

All registered built-in transforms are exposed as callable globals inside your Starlark expression. You can call them directly by their registered name:

# Call the built-in lowercase transform from inside a custom transform
return lowercase(value)

This means a custom transform can reuse any built-in conversion as a helper instead of re-implementing it. For the full list of names available this way, see Built-in transforms.

In addition, one Starlark-only helper is provided that is not a pipeline transform:

Name Signature Description Example
round round(number [, ndigits]) Rounds a number to ndigits decimal places (default 0); returns int when ndigits <= 0. round(3.7)4

Registering a custom transform

Custom transforms are managed through the collector REST API. For the full request and response schemas of every endpoint mentioned here, see the API reference.

Create

export BASE="https://collector.example.com"
export TOKEN="<your-api-token>"

curl -s -X POST "$BASE/api/v1/transforms" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "double_value",
    "expression": "return value * 2"
  }'

Bruno: 07 Transforms / Create transform

Field Type Constraints Description
name string Matches ^[a-z][a-z0-9_]*$ Unique name. Must not collide with a built-in.
expression string Valid Starlark body The function body, without the def transform(value): wrapper.

Response 201 Created with the created transform object.

400 Bad Request if the name is already used by another custom transform, collides with a built-in, or fails the naming-rule regex. Validation errors are returned in the standard form-error envelope with the offending field listed under errors.

The expression is parsed and compile-checked at creation time, so syntactic errors are reported immediately.

List, read, update, delete

Method Path Description
GET /api/v1/transforms List all user-defined transforms (paginated).
GET /api/v1/transforms/{id} Get a transform by ID.
PUT /api/v1/transforms/{id} Update an existing transform.
DELETE /api/v1/transforms/{id} Delete a transform.

Naming rules

Transform names must match ^[a-z][a-z0-9_]*$:

Valid Invalid
my_transform MyTransform — uppercase start
transform2 2transform — digit start
x my-transform — hyphen not allowed
my transform — space not allowed

Names that collide with a built-in transform are rejected with 400 Bad Request and the name field flagged in the form-error envelope.


Hot-reload behaviour

After a successful create, update, or delete the registry is refreshed and the new state becomes visible to active collections without a restart. Any subsequent collected value flowing through a field that references the transform uses the latest version.

For the equivalent debounce and re-subscription behaviour on the data-model side, see Custom Models — Hot-reload.


Examples

Multiply by a constant

return value * 2

Conditional logic

if value > 1000:
    return value / 1000
return value

String manipulation

return value + " suffix"

Composing built-ins

# Convert to Mbps and round to 2 decimal places.
mbps = to_mbps(value)
return round(mbps, 2)

Chaining string operations

cleaned = trim_whitespace(value)
return uppercase(cleaned)

Test before saving

The API compile-checks the Starlark expression at creation time. You can also test a draft expression against a sample value from the web UI before registering it, which catches runtime errors before any collection sees the transform.

Debugging

Starlark's built-in print(...) is enabled and wired to the collector's debug log channel. Any print() call emits a starlark print debug record tagged with the transform's name and the formatted message:

print("debug:", value)
return value * 2

Run the collector with debug logging on (PRELUDE_LOG_LEVEL=debug or the equivalent for your deployment) to surface these messages. There is no print rate limit, so remove debug calls from production transforms — they remain cheap but they do count against the per-call step budget.


Attaching a custom transform to a field

Once registered, a custom transform behaves exactly like a built-in. Use its name in the transforms list of any field mapping:

{
  "field": "speed_mbps",
  "oid": "1.3.6.1.2.1.31.1.1.1.15",
  "transforms": ["double_value", "to_mbps"]
}

Built-in and custom transforms can be mixed freely in the same chain, and they execute left to right as documented in Chaining.

See also

Filtering by: