Params
Params are the settings a user fills in on a block. You declare them with param.<type>(...) inside params, and run receives their values, typed from the declaration.
import { block, param } from "@fluxify/sdk/blocks";
export default block({
name: "acme/slack/messages/post",
label: "Post message",
params: {
channel: param.string({ label: "Channel", required: true }),
retries: param.number({ default: 3 }),
},
async run({ params }) {
params.channel; // string
params.retries; // number
},
});A param with required: true or a default is always there, so its type has no undefined. Any other param is optional and typed T | undefined.
The types
| Type | The user sees | run receives | Extra options |
|---|---|---|---|
string | A one-line text box that accepts js: expressions | string | |
number | A number box | number | min, max, integer |
boolean | A checkbox | boolean | |
select | A dropdown | One of the options, typed as that union | options (required) |
js | A code editor | Whatever the code returned | |
json | A JSON editor | A JSON value | |
integration | A picker of the project's integrations of one kind | The integration's id, a string | kind (required), variants |
appconfig | A picker of the project's app config keys | The key, a string | |
conditions | The same conditions builder the If block uses | Condition[] | |
schema | The schema builder | { zod, schema }, compiled | |
keyValue | Rows of a key and a value | Record<string, unknown> | keys.placeholder |
array | A list of items | unknown[] | of, min, max |
fieldMap | A mapper from source field to destination field | { [destination]: input[source] } | |
hint | A callout between the params (not a setting) | Nothing | text, tone |
Options every param takes
| Option | What it does |
|---|---|
label | The name above the field. Without it the param's key is shown |
description | Help text under the label |
required | run is only called when there is a value. Otherwise the run fails with param 'x' is required. The label shows a * |
default | Used when the user left the param empty. Not on js, integration, schema and fieldMap, which only the canvas can fill |
visibleWhen | Shows the param only when other params hold given values (below) |
tab | The settings tab it sits in. Default General |
order | Its position in the tab, lowest first. Params without one follow in declaration order |
A default is used exactly as you wrote it. A js: string in a default is not evaluated.
js: values
The user can type a JavaScript expression instead of a plain value by starting it with js:. Fluxify evaluates it when the block is called, with the previous block's output available as input, and run receives the result. This works in string, js, json, keyValue and array params, in every string inside them. Your code never sees the js: text.
| The user types | run gets |
|---|---|
#general | "#general" |
js:input.channel | The value of input.channel |
A string param always arrives as a string: if the expression returns an object it arrives as JSON text, and any other type is converted with String(). Use js or json when you want the value as it is.
Examples
string, number, boolean, select
params: {
text: param.string({ label: "Message", required: true }),
retries: param.number({ default: 3, min: 1, max: 10, integer: true }),
silent: param.boolean({ default: false }),
mode: param.select({ options: ["simple", "advanced"], default: "simple" }),
},select options must be unique, and default must be one of them. params.mode is typed "simple" | "advanced".
number bounds
min and max are inclusive limits on a number param, and integer: true accepts whole numbers only. The settings panel sets the input's limits from them and refuses a value outside them, so a typo is caught while the user saves rather than when the block runs.
params: {
port: param.number({ label: "Port", default: 5432, min: 1, max: 65_535, integer: true }),
intervalMs: param.number({ label: "Every (ms)", default: 1_000, min: 10 }),
threshold: param.number({ label: "Threshold", default: 0.5 }), // any number
},The same check runs before run and when a trigger starts, so a value saved by an older version or sent through the API fails with a message that says what it broke:
| Value | Error |
|---|---|
0 with min: 1 | param 'port' must be at least 1, got 0 |
65_536 with max: 65_535 | param 'port' must be at most 65535, got 65536 |
1.5 with integer: true | param 'port' must be an integer |
A min above the max, or a default outside the bounds, fails the build. Changing the bounds in a later version is listed in the upgrade diff, and narrowing a range blocks the upgrade while something saved falls outside it, the same as narrowing an integration param's variants.
js and json
params: {
// the user writes code; run gets what it returned
pick: param.js({ label: "Pick the rows", description: "Return the rows to post" }),
// a JSON value; strings inside may be `js:` expressions
extra: param.json({ label: "Extra fields", default: {} }),
},integration and appconfig
These need a permission: an integration param needs its kind in permissions.integrations, and an appconfig param needs permissions.appconfig: true. The build refuses a block that has the param without the permission.
permissions: { integrations: ["database"], appconfig: true },
params: {
connection: param.integration({ kind: "database", required: true }),
key: param.appconfig({ label: "Webhook URL key" }),
},
async run({ params, ctx }) {
const db = ctx.dbFactory!.getDbAdapter(params.connection);
const url = ctx.vars.getConfig(params.key!);
},An integration param can also name the variants it accepts, so a Memcached integration cannot be handed to code that speaks Redis:
params: {
database: param.integration({
kind: "database",
variants: ["PostgreSQL", "MySQL"],
required: true,
}),
},The picker offers only those variants, the server refuses any other, and ctx.integrations.connection types each variant's own client. See Variant-specific integrations.
conditions
params: { when: param.conditions({ label: "Only post when" }) },run receives the conditions as the builder stores them. Each is { lhs, rhs, operator, chain }, or { group, chain } for a group. operator is eq, neq, gt, gte, lt, lte, js, is_empty or is_not_empty, and chain is and or or. The lhs and rhs sides arrive evaluated.
schema
params: { body: param.schema({ label: "Request body" }) },
async run({ params, input }) {
const parsed = params.body?.zod.safeParse(input);
},The user designs the schema in Fluxify's schema builder, and run receives it compiled: zod is a ready zod schema and schema is the definition itself. A schema is compiled once, when the route is loaded, and shared by every request, so a refinement must not keep state between requests. An empty schema param arrives as absent.
keyValue
params: {
headers: param.keyValue({ label: "Headers", keys: { placeholder: "Header name" } }),
},Each value is a literal or a js: expression. A row with an empty key, or two rows with the same key, is refused in the settings panel and in run.
array
params: {
to: param.array({ label: "Recipients", of: "recipient", min: 1, max: 10 }),
},of names one item ("Add recipient"). Each item is a literal or a js: expression. Fewer than min or more than max items fails the run with a message that says so.
fieldMap
params: { rename: param.fieldMap({ label: "Rename fields" }) },The user maps source field names to destination names. With { id: "userId" } and an input of { id: 7 }, run receives { userId: 7 }.
Hints
A hint is a callout shown between the params of a tab. It is declared in params like a param, but it holds no value and never reaches run.
import { hint, param } from "@fluxify/sdk/blocks";
params: {
mode: param.select({ options: ["simple", "advanced"], default: "simple" }),
note: hint("Advanced mode sends the message as a thread.", {
tone: "tip",
visibleWhen: { mode: "advanced" },
}),
},text is markdown. tone is info (the default), tip or warning. A hint takes tab, order and visibleWhen like a param.
Showing a param only sometimes
visibleWhen is data, not code. It names params of the same block and the values they must hold:
params: {
mode: param.select({ options: ["simple", "advanced"], default: "simple" }),
shape: param.schema({ visibleWhen: { mode: "advanced" } }),
level: param.string({ visibleWhen: { mode: ["simple", "advanced"] } }),
both: param.string({
visibleWhen: { all: [{ mode: "advanced" }, { level: "high" }] },
}),
},| Form | Shown when |
|---|---|
{ mode: "advanced" } | mode is advanced |
{ mode: ["a", "b"] } | mode is a or b |
{ a: 1, b: 2 } | Every key matches |
{ all: [...] } | Every condition inside matches |
{ any: [...] } | At least one matches |
You can compare select, string, number and boolean params, and TypeScript and the build both check that the name and value exist. all and any are reserved words, so no param can be called that.
Tabs and order
A block with many params can split them across tabs:
tabs: ["General", "Advanced"],
params: {
text: param.string({ required: true, order: 1 }),
channel: param.string({ order: 0 }),
timeout: param.number({ tab: "Advanced" }),
},- General comes first unless
tabsplaces it, then the tabs intabs, then any other tab in order of first use. - Within a tab, params with an
ordercome first, lowest first, and the rest follow in declaration order. - A tab with no param in it is a build error. A tab whose params are all hidden by
visibleWhendisappears from the panel. A block with a single tab shows no tab bar. tabandorderchange only how the panel looks.runnever sees them.
Names you cannot use
These keys belong to Fluxify on every block: blockName, blockDescription, invoke, saveAsVariable, order. all and any are reserved for visibleWhen. Declaring a param with one of these names fails the build.
Deprecating a param
Wrap a param in deprecated(...) when you plan to remove it. It keeps working. The settings panel shows your message beside it.
import { deprecated, param } from "@fluxify/sdk/blocks";
params: {
channel: param.string({ required: true }),
legacy: deprecated(param.string(), "Ignored since Oct 2026, removed in the Jan 2027 release; use `channel`."),
},