Custom block reference for agents
A custom block is a reusable block with your own JavaScript and typed inputs. save_custom_block sets its details. Its code lives on its canvas, which you edit with edit_canvas. For background, see Custom Blocks.
Tools
| Tool | Role | What it does |
|---|---|---|
list_custom_blocks | viewer | Blocks of a project: id, name, label, description, usage. |
get_custom_block | viewer | One block: id, name, label, description, usage, inputParams, docs, the version live in production, whether it has uncommitted changes, and everything that uses it (references). Not its code. |
save_custom_block | creator | Create (no customBlockId) or update (customBlockId) the details. |
extract_custom_block | creator | Turn blocks on a canvas into a new custom block. See Extract blocks into a custom block. |
delete_custom_block | creator | Deletes the block and its code. Refused while a route, workflow, block, middleware or test hook uses it. |
commit_version, promote_version, rollback_version | creator | Version the block with kind: "custom_block". See Versions. |
get_canvas, edit_canvas | viewer, creator | Read and write the code. kind is "custom_block". |
Create a custom block
Pass projectId, name and label.
{
"projectId": "<project id>",
"name": "slack_notify",
"label": "Slack notify",
"description": "Posts a message to a Slack webhook",
"usage": "flow",
"inputParams": [
{ "type": "text_input", "name": "message", "label": "Message", "description": "Text to post" },
{ "type": "app_config_selector", "name": "webhook_key", "label": "Webhook secret" }
]
}The result is { "id": "<custom block id>" }.
A new block has an entrypoint and an error handler on its canvas, and no code yet. See Write the code.
Update a custom block
Pass customBlockId and only the fields that change. name, usage and projectId cannot change after create. If you pass them they are ignored.
{ "customBlockId": "<custom block id>", "label": "Slack message", "docs": "# Slack notify\nPosts `message` to the webhook." }inputParams replaces the whole list. Send the full list, not one change.
Fields
| Field | Type | Rule | Default |
|---|---|---|---|
projectId | string | Create only. | none |
name | string | Lowercase letters, digits and _ only. Create only. Unique in the project. | none |
label | string | Shown in the block picker. Up to 50 characters. | none |
description | string | Free text. | none |
usage | string | flow, test or middleware. Create only. See Usage. | flow |
inputParams | list | See Input parameters. | none |
docs | string or null | Markdown shown in the block's docs. Up to 100000 characters. | none |
icon | string | premade-list or custom. | none |
iconUrl | string | With premade-list: the icon name, for example code, webhook, json or brand-slack. With custom: a URL or base64 data, up to 68266 characters. | none |
The stored name is user_defined.project.<name>. The database holds at most 50 characters of it, so keep name to 29 characters or fewer. Use the stored name from get_custom_block when you place the block on a canvas.
Usage
usage says where the block may run. It is fixed at create, because routes, test suites and middlewares depend on it.
usage | Where it runs |
|---|---|
flow | On any route, workflow or custom block canvas. |
test | Only as a test suite setup, teardown or input loader. Hidden from canvases and never run by live traffic. |
middleware | Only as a link in a middleware. It takes no input parameters: inputParams is always saved as an empty list. |
Input parameters
inputParams is a list. Each item has a type, a name (lowercase letters, digits and _), a label and an optional description.
type | Extra fields | Use it for |
|---|---|---|
text_input | none | A text value. It accepts a js: expression, such as js: return input.id. |
checkbox | none | On or off. |
array_editor | none | A list of values. |
dropdown | options: [{ "label": "...", "value": "..." }] | A fixed set of choices. |
integration_selector | group (required), variant (optional), tags (list, default []) | The id of one of the project's integrations. |
app_config_selector | none | The key of an app config entry. |
Prefer app_config_selector for secrets. The block receives the key, and reads the value with getConfig(params.webhook_key). The secret is never stored in the canvas.
Write the code
The code is the block's canvas. Read it first to get the revision and the block keys.
{ "target": { "kind": "custom_block", "id": "<custom block id>" } }Then add a jsrunner block and wire it between the entrypoint and the end of the chain. Pass the revision from get_canvas.
{
"target": { "kind": "custom_block", "id": "<custom block id>" },
"revision": 0,
"ops": [
{ "op": "add_block", "ref": "code", "type": "jsrunner",
"data": { "value": "const url = getConfig(params.webhook_key);\nconst res = await httpClient.post(url, { text: params.message });\nreturn { ok: res.status === 200 };" },
"connect_from": { "from": "entrypoint_1" } }
],
"validate": true
}Inside the code:
| Name | What it is |
|---|---|
input | The output of the block before this custom block on the caller's canvas. |
params | The values the caller filled in, by parameter name. |
getConfig(key) | Reads an app config value of the project. |
Whatever the last block outputs is the custom block's result.
Extract blocks into a custom block
extract_custom_block builds a custom block from blocks that already work on a canvas, in one call.
{ "projectId": "p1", "from": { "kind": "sandbox", "id": "<sandbox id>" },
"keys": ["jsrunner_1", "jsrunner_2"], "name": "shout_text", "label": "Shout text" }| Field | Notes |
|---|---|
from | The canvas: route, workflow, custom_block or your own sandbox, in the same project. |
keys | Block keys from get_canvas. The entrypoint and error handler cannot be extracted. |
name, label | Required. name uses lowercase letters, digits and _. |
replace | Default false. When true, the selection on the source canvas becomes one instance of the new block. It asks the user first, even in auto mode. |
The new block has the default icon, usage flow, no description and no input parameters. The previous block's output is its input, and what its last block returns is its output. Edit the details afterwards with save_custom_block.
The selection has to be tidy: one entry block, at most one edge into it, and at most one edge out of its single last block. If it is not, the call is refused and the message says what to clean up. Ask the user to tidy the canvas; do not rewire it yourself.
Use a block on a canvas
Add a block whose type is the stored name. Put each parameter value in data, by parameter name. A value can be a js: expression. See Dynamic values and js: expressions.
{ "op": "add_block", "ref": "notify", "type": "user_defined.project.slack_notify",
"data": { "message": "js: return 'New order ' + input.id", "webhook_key": "SLACK_WEBHOOK", "invoke": "sync" },
"connect_from": { "from": "<previous block key>" } }invoke is optional:
invoke | Behaviour |
|---|---|
sync (default) | The flow waits and uses the block's output. A failure fails the flow. |
async | The block starts and the flow carries on. Its output is not available. Lost if the server restarts. |
queued | Saved as a job and run later, possibly on another worker. It survives restarts and may be retried, so make it safe to run twice. Only the params and input are handed over. |
Versions
A custom block has versions like a route. Development runs the latest saved state. Production runs the promoted version. Promoting or rolling back a block changes every production route and workflow that uses it, so its tests gate runs the suites of each of them first. See Custom blocks and middlewares.
commit_versionwithkind: "custom_block"saves its canvas and inputs.list_versionsandget_versionread the history.promote_versionandrollback_versionalways ask a person, who writes the reason.- A route that uses a block with no promoted version cannot be promoted: the failure lists the blocks by
idandname. Promote those first. get_custom_blockshowsreferences: each route, workflow, block, middleware or test suite that uses the block, withinProductionwhen its promoted version does.
Common errors
| Message | Cause and fix |
|---|---|
Invalid input: name: ... | name has a character outside lowercase letters, digits and _. |
Invalid input: iconUrl: Invalid premade icon name | With icon: "premade-list", iconUrl must be a premade icon name. |
Fluxify API error 409: custom block with name user_defined.project.X already exists in this project | Pick another name. |
Fluxify API error 409: This custom block is still used by ... | On delete: the message names each route, workflow, block, middleware or test suite that uses it, in a working copy or a promoted version. Remove the use there first, or promote a version that no longer has it. Sandboxes never block it. |
Fluxify API error 403: Cannot delete a custom block originating from a plugin | Plugin blocks cannot be deleted. |
The selection has 2 entry blocks (...) or Connections ... must be one edge ... | extract_custom_block needs a tidy selection. Ask the user to clean up the connections, then try again. |
Not found: Custom block not found | Wrong customBlockId. Use list_custom_blocks. |
No codegen for block type in get_system_logs | A canvas uses the bare name. Use the stored name user_defined.project.<name>. |
You need the Creator role in this project. | Ask a project admin. Do not retry. |
