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(...).
  • nodeKey is 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 runtime
  • node:https / require('http') — Node.js only
  • XMLHttpRequest — 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 flow
  • seq.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 |