Skip to content

Blocks ​

A block is a file in src/blocks that default-exports block({...}):

ts
import { block, param } from "@fluxify/sdk/blocks";

export default block({
	name: "acme/slack/messages/post",
	label: "Post message",
	params: { channel: param.string({ required: true }) },
	async run({ params }) {
		return { output: `posted to ${params.channel}` };
	},
});

block() checks the definition when the file loads and throws an error that names the block, so a mistake shows up in bun test and in the build. Only name, label and run are required.

Fields ​

FieldRequiredDefaultWhat it is
nameYesThe block's type: scope/name/folder/block
labelYesThe name shown on the canvas and in the picker
descriptionNoOne line shown in the picker
iconNoA default iconAn image inside the plugin
shapeNosquaresquare or wide
docsNoMarkdown for the block's Docs tab
invokeNosync, async, queuedThe ways a canvas may call the block
permissionsNoNoneWhat ctx may reach
paramsNoNoneThe settings the user fills in
tabsNoBy first useThe order of the settings tabs
handlesNoIn target, out sourceInputs, outputs and nested chains
runYesThe code
DeprecationNoA message that the block is going away

name ​

The block's type. It must be the plugin name plus the path of the file under src/blocks, in lowercase. The build stops if they differ. See names.

The type is also part of the id a recorded run stores for the block, <plugin>@<version>:<project id>:<block type>, which holds 500 characters. The build budgets the longest project id (50) and stops, naming the block and how far over it is, when the id could not fit. Shorten the plugin name or a folder if that happens.

ts
name: "acme/slack/messages/post", // src/blocks/messages/post.ts in acme/slack

label ​

What the user sees on the canvas and in the picker.

ts
label: "Post message",

description ​

A short line under the label in the picker.

ts
description: "Posts a message to a Slack channel.",

icon ​

A path to an image inside the plugin: .svg, .png, .jpg, .jpeg, .gif or .webp. The build copies an icon from outside icons/ into icons/ so it ships, and stops if the image is missing or too large (about 50 KB).

ts
icon: "./icons/post.svg",

shape ​

square is the usual one-icon block. wide draws a wider node on the canvas, as the Agent block in the example plugin does.

ts
shape: "wide",

docs ​

Markdown shown in the block's Docs tab. Follow the page layout of the built-in blocks: a sentence, then When to use it, Inputs, Outputs, Example and How it behaves. A block with no docs gets a build warning.

ts
docs: `Posts a message to a channel.

## When to use it
After a block that builds the text.

## Inputs
| Name | Required | Default |
| :--- | :--- | :--- |
| Channel | Yes | |`,

The Docs tab shows a small subset of markdown. The build warns about the rest:

  • Use headings, paragraphs, lists, tables, code fences and the callouts ::: info, ::: tip, ::: warning and ::: danger.
  • Do not use raw HTML, images, {#id} on headings, or [text](#anchor) links. The tab opens every link in a new tab, so an in-page link goes nowhere.

invoke ​

The ways a canvas may call the block. With one mode the user gets no choice; with more, the block's settings show an Execution Mode dropdown.

ModeThe caller
syncWaits for the block, and uses its output
asyncStarts it on this worker and carries on
queuedPuts it on a queue for a worker to pick up, and carries on

async and queued calls pass their input and params as JSON, and the caller never learns the outcome. The handles page lists the combinations Fluxify refuses.

ts
invoke: ["sync"], // the output holds a function, which only a sync call can pass on

permissions ​

What ctx may reach, beyond what every block gets. A creator sees it when approving the plugin, and the approval covers exactly what you ask for. Context lists them.

ts
permissions: { integrations: ["database"], appconfig: true },

params ​

The settings the user fills in, typed from the declaration so params in run has the right types. See params.

ts
params: {
	channel: param.string({ label: "Channel", required: true }),
	silent: param.boolean({ default: false }),
},

tabs ​

The settings tabs in order. Without it, tabs appear in order of first use with General first. A param's tab that tabs leaves out is added after the listed ones, and the build warns about it.

ts
tabs: ["General", "Advanced"],

handles ​

Inputs, outputs and nested chains. See handles.

ts
handles: { out: [handle.out("success"), handle.out("failure")] },

run ​

ts
async run({ params, input, ctx, executeHandle }) {
	return { output: params.channel, handle: "success" };
},
ArgumentWhat it is
paramsThe settings, typed from params. Defaults are filled in, required ones are checked, and js: values are already evaluated (params)
inputThe previous block's output
ctxThe block's context (context)
executeHandleRuns the chains on the block's body handles (handles)

run returns an object, or nothing:

ReturnMeaning
{ output }output goes to the next block
{ output, handle }The same, leaving by a declared output handle. The default is the first one
nothingThe output is undefined

Returning a handle the block did not declare fails the run with an error that lists the handles it has.

Errors ​

A block fails by throwing. Where the failure goes depends on its outputs:

OutputsA throw
[success, failure]Leaves by failure with the error's message as its output, if something is wired to failure. Otherwise it fails the route
[source] or []Fails the route

Returning { handle: "failure", output } yourself takes the same path without a throw.

A missing required param, or a value of the wrong type, is an error thrown before your code runs, so it is routed like any other throw. A js: expression that throws fails the route before the block is called, and never goes to failure.

ts
async run({ params, ctx }) {
	const res = await ctx.vars.httpClient.get(params.url);
	if (res.status !== 200) throw new Error(`the server answered ${res.status}`);
	return { output: res.data, handle: "success" };
},

Deprecation ​

Wrap the finished block in deprecated(...) with a message for the user. The message is shown as written, so say what to use instead and when the block goes away. Publishing covers when to use it.

ts
import { block, deprecated } from "@fluxify/sdk/blocks";

export default deprecated(
	block({ name: "acme/slack/post", label: "Post (old)", async run() {} }),
	"Removed in the Jan 2027 release; use `acme/slack/messages/post`.",
);

deprecated takes a param or a handle in the same way. See params and handles.

A complete block ​

This is the Tool block of the agent-tools example. It uses a param, a body handle and a late executeHandle call, all covered in the next pages.

ts
// source: examples/plugins/agent-tools/src/blocks/tool.ts
export default block({
	name: "fluxify-examples/agent-tools/tool",
	label: "Tool",
	description: "A tool an Agent block can call. Wire what it does to its Execute handle.",
	shape: "square",
	// the output holds a function, which only a sync call can pass on
	invoke: ["sync"],
	params: {
		name: param.string({ label: "Name", required: true }),
		description: param.string({ label: "Description", default: "" }),
	},
	handles: {
		out: [],
		body: [handle.body("execute", "bottom-right")],
	},
	async run({ params, executeHandle }) {
		return {
			output: {
				name: params.name,
				description: params.description,
				// runs after this `run` returned, while the agent's run is still going
				call: (args: unknown) => executeHandle("execute", args),
			},
		};
	},
});

Released under the Apache License 2.0. Enterprise features are under the Fluxify Enterprise Edition License.