Custom Code Examples
> Ready-to-run examples covering every SDK feature. Each example is a complete, working .br.ts file you can drop into any Brickr code project.
The examples below match the files in the code-nodes-examples/ folder of the Brickr repository. To follow along locally:
npx brickr
brickr dev
---
Project config — brickr.config.ts
Before using enums, structures, or secrets across multiple nodes, define them once in brickr.config.ts and sync them to the workspace.
import { collectAssets, defineEnum, defineSecret, defineStructure } from "@brickr/sdk";
// Enums — fixed sets of allowed string values
const OrderStatus = defineEnum({
name: "OrderStatus",
description: "Lifecycle states for an order",
values: ["draft", "paid", "shipped", "cancelled"]
});
const NotificationChannel = defineEnum({
name: "NotificationChannel",
values: [
{ key: "email", value: "email", label: "Email" },
{ key: "sms", value: "sms", label: "SMS" },
{ key: "webhook", value: "webhook", label: "Webhook" }
]
});
// Structures — object shapes shared by multiple nodes
// Field types use raw Brickr type strings, not t.* builders
const Order = defineStructure({
name: "Order",
fields: [
{ name: "id", type: "string", required: true },
{ name: "amount", type: "number", required: true },
{ name: "status", type: "enum-value:OrderStatus", required: true },
{ name: "tags", type: "string:array", required: false }
]
});
const HttpHeader = defineStructure({
name: "HttpHeader",
fields: [
{ name: "key", type: "string", required: true },
{ name: "value", type: "string", required: true }
]
});
// Secrets — declare required credentials; values are set per-workspace
const StripeApiKey = defineSecret({
name: "STRIPE_API_KEY",
description: "Stripe server-side secret key",
required: true
});
// collectAssets() groups everything into the expected shape
const assets = collectAssets([OrderStatus, NotificationChannel, Order, HttpHeader, StripeApiKey]);
export default {
projectName: "my-project",
...assets
};
Sync to workspace after every change:
brickr assets sync
---
01 — Hello World
The minimal Brickr node. Shows the required structure of every .br.ts file.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.hello-world", // stable identity — never change after deploy
name: "Hello World", // label shown in the Builder
description: "Returns a greeting",
category: "examples",
inputs: {
name: t.string().description("Name to greet").default("World")
},
outputs: {
message: t.string()
},
run(ctx) {
const name = String(ctx.inputs.name ?? "World");
return { message: `Hello ${name}` };
}
});
Key rules:
- One file, one
export default defineCodeFunctionNode(...). nodeKeyis permanent — changing it creates a new node identity in the workspace.- Return object keys must exactly match the declared output pin ids.
---
02 — Multiple Types and Outputs
t.number(), t.boolean(), multiple outputs, and safe type coercion.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.format-price",
name: "Format Price",
category: "examples",
inputs: {
amount: t.number().default(99.99),
currency: t.string().default("quot;),
discount: t.number().description("Percentage 0–100").default(0),
uppercase: t.boolean().default(false)
},
outputs: {
formatted: t.string(),
finalAmount: t.number(),
hasDiscount: t.boolean()
},
run(ctx) {
const amount = Number(ctx.inputs.amount ?? 0);
const currency = String(ctx.inputs.currency ?? "quot;);
const discount = Math.min(100, Math.max(0, Number(ctx.inputs.discount ?? 0)));
const hasDiscount = discount > 0;
const finalAmount = hasDiscount ? amount * (1 - discount / 100) : amount;
let formatted = `${currency}${finalAmount.toFixed(2)}`;
if (hasDiscount) formatted += ` (${discount}% off)`;
if (ctx.inputs.uppercase) formatted = formatted.toUpperCase();
return { formatted, finalAmount, hasDiscount };
}
});
Always coerce inputs. Values from upstream connections may arrive as strings even when the pin type is number or boolean. Use Number(), String(), Boolean() defensively.
---
03 — Error Handling and Logging
ctx.fail() stops execution. ctx.log() writes to Builder live logs and brickr dev output.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.validate-email",
name: "Validate Email",
category: "examples",
inputs: {
email: t.string(),
allowPlusAddressing: t.boolean().default(true)
},
outputs: {
normalized: t.string(),
domain: t.string(),
warning: t.string().optional()
},
run(ctx) {
const raw = String(ctx.inputs.email ?? "").trim();
ctx.log("Validating:", raw);
if (!raw) ctx.fail("email is required", "MISSING_EMAIL");
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(raw)) ctx.fail(`"${raw}" is not valid`, "INVALID_FORMAT");
if (!ctx.inputs.allowPlusAddressing && raw.includes("+")) {
ctx.fail("Plus-addressing not allowed", "PLUS_ADDRESS_REJECTED");
}
const normalized = raw.toLowerCase();
const domain = normalized.split("@")[1];
const warning = domain.endsWith(".test") ? "Looks like a test address" : undefined;
return { normalized, domain, ...(warning ? { warning } : {}) };
}
});
ctx.fail(message, code?) — throws immediately, nothing after it runs. The optional code argument lets callers handle specific failures programmatically. .optional() outputs can be omitted from the return object when absent.
---
04 — Secrets
ctx.secret("NAME") reads from the workspace secret store in production and from an environment variable of the same name in local dev.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.secret-info",
name: "Secret Info",
category: "examples",
inputs: {
secretName: t.string().default("STRIPE_API_KEY"),
previewChars: t.number().default(4)
},
outputs: {
isConfigured: t.boolean(),
preview: t.string(),
length: t.number()
},
run(ctx) {
const name = String(ctx.inputs.secretName ?? "");
if (!name) ctx.fail("secretName is required");
// Reads workspace secret in deployed nodes.
// Reads process env var in local brickr dev.
const value = ctx.secret(name);
const previewLen = Math.min(8, Math.max(0, Number(ctx.inputs.previewChars ?? 4)));
const isConfigured = value.length > 0;
const preview = isConfigured ? `${value.slice(0, previewLen)}...` : "(not set)";
return { isConfigured, preview, length: value.length };
}
});
Local dev setup:
export STRIPE_API_KEY=sk_test_...
brickr dev examples.secret-info
Never use process.env in nodes — it only works locally. Never log or return the full secret value.
---
05 — Enums and Structures
Use shared types from brickr.config.ts with t.enumValue() and t.struct().
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.order-summary",
name: "Order Summary",
category: "examples",
inputs: {
// t.struct("Order") — expects an object matching the Order structure
order: t.struct("Order"),
// t.enumValue("OrderStatus") — expects one of: draft, paid, shipped, cancelled
highlight: t.enumValue("OrderStatus").default("paid")
},
outputs: {
summary: t.string(),
isHighlighted: t.boolean(),
tagCount: t.number()
},
run(ctx) {
const order = ctx.inputs.order as Record<string, unknown>;
const id = String(order?.id ?? "unknown");
const amount = Number(order?.amount ?? 0);
const status = String(order?.status ?? "draft");
const tags = Array.isArray(order?.tags) ? (order.tags as unknown[]) : [];
return {
summary: `Order #${id} | ${status.toUpperCase()} | ${amount.toFixed(2)}`,
isHighlighted: status === ctx.inputs.highlight,
tagCount: tags.length
};
}
});
Workflow: 1. Define types in brickr.config.ts 2. brickr assets sync — pushes them to the workspace 3. Reference with t.struct("Name") and t.enumValue("Name") in your nodes
---
06 — Arrays
t.array() wraps any other type builder. Use it for both inputs and outputs.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.filter-headers",
name: "Filter HTTP Headers",
category: "examples",
inputs: {
headers: t.array(t.struct("HttpHeader")),
prefix: t.string().default("x-"),
caseSensitive: t.boolean().default(false)
},
outputs: {
filtered: t.array(t.struct("HttpHeader")),
keys: t.array(t.string()),
count: t.number()
},
run(ctx) {
const rawHeaders = Array.isArray(ctx.inputs.headers)
? (ctx.inputs.headers as Array<{ key: string; value: string }>)
: [];
const prefix = String(ctx.inputs.prefix ?? "");
const cs = !!ctx.inputs.caseSensitive;
const filtered = rawHeaders.filter(h => {
const key = cs ? h.key : h.key.toLowerCase();
const p = cs ? prefix : prefix.toLowerCase();
return key.startsWith(p);
});
return { filtered, keys: filtered.map(h => h.key), count: filtered.length };
}
});
Array syntax: | Type | Builder | Notes | |------|---------|-------| | t.array(t.string()) | string:array | Array of strings | | t.array(t.number()) | number:array | Array of numbers | | t.array(t.struct("X")) | object:X:array | Array of named structures | | t.array(t.any()) | any:array | Mixed / unknown items |
---
07 — HTTP Requests
Use fetch() for outbound HTTP calls. Use ctx.secret() for auth tokens.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.http-get",
name: "HTTP GET Request",
category: "examples",
inputs: {
url: t.string().default("https://api.example.com/data"),
secretName: t.string().default("API_KEY"),
timeoutMs: t.number().default(5000)
},
outputs: {
ok: t.boolean(),
status: t.number(),
body: t.any(),
error: t.string().optional()
},
async run(ctx) {
const url = String(ctx.inputs.url ?? "");
if (!url) ctx.fail("url is required", "MISSING_URL");
const apiKey = ctx.secret(String(ctx.inputs.secretName ?? "API_KEY"));
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), Number(ctx.inputs.timeoutMs ?? 5000));
try {
const res = await fetch(url, {
headers: { "Authorization": `Bearer ${apiKey}`, "Accept": "application/json" },
signal: controller.signal
});
clearTimeout(timeout);
let body: unknown = null;
try { body = await res.json(); } catch { /* non-JSON response */ }
return { ok: res.ok, status: res.status, body, ...(res.ok ? {} : { error: `HTTP ${res.status}` }) };
} catch (err: unknown) {
clearTimeout(timeout);
const message = err instanceof Error ? err.message : String(err);
return { ok: false, status: 0, body: null, error: message };
}
}
});
Use fetch(), not:
axios— not available in the Worker runtimenode:https/require('http')— Node.js onlyXMLHttpRequest— not available in Workers
---
08 — Sequence Flow
seq.in() and seq.out() give explicit execution control — like Blueprint exec pins.
import { defineCodeFunctionNode, seq, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.rate-limit-check",
name: "Rate Limit Check",
category: "examples",
inputs: {
in: seq.in(),
requestCount: t.number().default(0),
limit: t.number().default(100),
identifier: t.string().default("anonymous")
},
outputs: {
allowed: seq.out(), // wire to the "continue" path
blocked: seq.out(), // wire to the "reject" path
remaining: t.number(),
message: t.string()
},
run(ctx) {
const count = Number(ctx.inputs.requestCount ?? 0);
const limit = Number(ctx.inputs.limit ?? 100);
const remaining = Math.max(0, limit - count);
const isAllowed = count < limit;
ctx.log(`${ctx.inputs.identifier}: ${count}/${limit}`);
return {
remaining,
message: isAllowed
? `${remaining} requests remaining`
: `Rate limit exceeded for ${ctx.inputs.identifier}`
};
}
});
Sequence pins:
seq.in()— node only executes when this pin receives flowseq.out()— passes execution to the next node in the chain- Multiple
seq.out()pins let the Builder route to different branches - Sequence pins are port-only (no inline value) by default
---
09 — Request Context and Runtime Environment
ctx.request exposes HTTP metadata. ctx.env exposes Brickr runtime metadata.
import { defineCodeFunctionNode, t } from "@brickr/sdk";
export default defineCodeFunctionNode({
nodeKey: "examples.request-inspector",
name: "Request Inspector",
category: "examples",
inputs: {
// .portOnly() — pin must be wired, inline value entry is hidden in the Builder
targetHeader: t.string().portOnly().description("Header name to extract"),
includeEnv: t.boolean().default(false)
},
outputs: {
method: t.string(),
url: t.string(),
headerValue: t.string(),
isDev: t.boolean(),
envMeta: t.any().optional()
},
run(ctx) {
const method = String(ctx.request?.method ?? "UNKNOWN");
const url = String(ctx.request?.url ?? "");
const headers = ctx.request?.headers ?? {};
const targetKey = String(ctx.inputs.targetHeader ?? "").toLowerCase();
const headerValue = String(headers[targetKey] ?? headers[targetKey.toUpperCase()] ?? "");
const env = ctx.env as Record<string, unknown>;
const isDev = !!(env?.isDev);
return {
method, url, headerValue, isDev,
...(ctx.inputs.includeEnv ? { envMeta: ctx.env } : {})
};
}
});
ctx.request fields: | Field | Type | When available | |-------|------|----------------| | method | string | All HTTP-triggered routes | | url | string | All HTTP-triggered routes | | headers | Record<string, string> | All HTTP-triggered routes |
ctx.env is Brickr metadata only: | Field | Description | |-------|-------------| | isDev | true when running in the dev execution channel | | version | Deployed version number of this node | | gitSha | Git commit SHA of the deployed bundle |
Do not use ctx.env to access raw Worker bindings, D1, KV, or platform secrets. Use ctx.secret() for credentials.
---
AI agent quick reference
If you are an AI agent generating Brickr code nodes, use this as your contract.
Pin type builders
| Builder | Brickr type | Notes | |---------|-------------|-------| | t.string() | string | | | t.number() | number | | | t.boolean() | boolean | | | t.any() | any | Unrestricted | | t.object() | object | Generic object | | t.struct("Name") | object:Name | Named structure — must exist in config | | t.enumValue("Name") | enum-value:Name | Enum — must exist in config | | t.array(t.string()) | string:array | Wrap any builder | | seq.in() | sequence (in) | Port-only, flow control | | seq.out() | sequence (out) | Port-only, flow control |
Pin modifiers
| Modifier | Effect | |----------|--------| | .optional() | Pin may be absent — runtime allows missing output key | | .default(value) | Default shown in Builder; used in local test inputs | | .description(text) | Tooltip shown in Builder | | .portOnly() | Pin must be wired — inline value UI hidden |
Context API
| API | Description | |-----|-------------| | ctx.inputs.pinId | Read an input value | | ctx.secret("NAME") | Read a workspace secret | | ctx.log(...args) | Write debug output | | ctx.fail(msg, code?) | Stop execution with a structured error | | ctx.request.method | HTTP method of the triggering request | | ctx.request.url | Full request URL | | ctx.request.headers | Headers as Record<string, string> | | ctx.env | Brickr runtime metadata only (isDev, version, gitSha) |
Rules for generated code
| Rule | What to do | |------|------------| | Return shape | run(ctx) must return a plain object whose keys exactly match declared output pin ids | | Secrets | Use ctx.secret("NAME") — never process.env, never hard-coded values | | HTTP | Use fetch() — never axios, node:https, or require('http') | | Runtime | Target Worker-style execution — avoid Node.js-only APIs (fs, path, require) | | Types | Coerce inputs with Number(), String(), Boolean() — upstream values may not match declared types | | nodeKey | Treat as permanent — changing it creates a new node identity | | Assets | Enums and structures live in brickr.config.ts — reference with t.enumValue() / t.struct() |
---
What's next?
| Topic | Description | |-------|-------------| | SDK | Complete @brickr/sdk API reference | | CLI | brickr dev, brickr deploy, brickr assets sync | | Workflow | End-to-end authoring and deploy flow | | Overview | Architecture and lifecycle |