Skip to content
Agentmart
Submit a tool

bxl-authoring

Use when writing or reviewing a BXL expression in a Boxel card — a computeVia built from expression(), the fx / jq tags, spreadsheet-formula fields…

Skill by cardstack

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/cardstack-bxl-authoring

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Use when writing or reviewing a BXL expression in a Boxel card — a computeVia built from expression(), the fx / jq tags, spreadsheet-formula fields, aggregations over linked or query-backed collections. Covers which tag to reach for, what the derive profile refuses outright, the silent traps (a stream where an aggregate was meant, jq interpolation in a plain string, & on a blank field, dates and "today"), and why an indexed computed can differ from the one the viewer sees. Activates on expression(, fx`…`, jq`…`, "BXL", "formula field", "computed field with Excel functions", "sum the linked cards".

Use cases: spreadsheets, writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/bxl-authoring

What's inside

Skill name
bxl-authoring
Description
Use when writing or reviewing a BXL expression in a Boxel card — a computeVia built from expression(), the fx / jq tags, spreadsheet-formula fields, aggregations over linked or query-backed collections. Covers which tag to reach for, what the derive profile refuses outright, the silent traps (a stream where an aggregate was meant, jq interpolation in a plain string, & on a blank field, dates and "today"), and why an indexed computed can differ from the one the viewer sees. Activates on expression(, fx`…`, jq`…`, "BXL", "formula field", "computed field with Excel functions", "sum the linked cards".
License
MIT
Instructions
405 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Authoring BXL in a card

BXL is the workspace's expression language: readable spreadsheet syntax and Excel
formula libraries on top of a jq engine. In a card it drives `computeVia`.

```ts
import { expression, fx, jq } from '@cardstack/bxl';

export class Claim extends CardDef {
  @field paidAmount = contains(NumberField);
  @field reserveAmount = contains(NumberField);
  @field incurredAmount = contains(NumberField, {
    computeVia: expression(fx`ROUND((PaidAmount + ReserveAmount) * 100) / 100`),
  });
}
```

`expression` is the factory (`bxl` and `expr` are aliases); it compiles the
source once when the class body runs, then evaluates it against the card
instance on each read.

The rest of this skill is the decision layer and the trap list. For the full
syntax surface — labels, row selectors, predicates, the Excel function matrix —
read [bxl.boxel.site](https://bxl.boxel.site).

## 1. Which tag

| Source contains                             | Reach for       | Why                                                    |
| ------------------------------------------- | --------------- | ------------------------------------------------------ |
| `\(…)` jq interpolation                     | `` jq`…` ``     | A plain string drops the backslash — see trap 4        |
| Excel functions (`ROUND`, `IFS`, `SUM`)     | `` fx`…` ``     | Readable-syntax compilation, explicit at the call site |
| Bare PascalCase field labels (`PaidAmount`) | `` fx`…` ``     | The compiler resolves them to `.paidAmount`            |
| Pure jq (`.claims \| length`)               | `` jq`…` ``     | Skips the readable-syntax compile step                 |
| Quoted multi-word labels (`"Line Item"`)    | `fx` + `schema` | Label resolution needs the schema — see trap 3         |

A plain string compiles exactly like `` fx`…` ``. Prefer a tag: it tells the next
reader which dialect they are in, and it is the only form that survives `\(…)`.

Mixing dialects inside one source is fine — `` fx`IF(.status == "Open", 1, 0)` ``
and `` fx`if Status == "Open" then 1 else 0 end` `` both work. Case is the
dispatch: `IF(cond, t, f)` is the Excel function, `if cond then … end` is the jq
construct.

## 2. The `derive` profile — what a computed may not do

`expression()` validates against the `derive` profile **when the field is
defined**, so a violation throws while the card module loads rather than
producing a wrong value. The diagnostic names the rule:

```text
computeVia expression violates the derive profile:
derive-call-banned: Profile.derive is for deterministic write/index-time
computation and cannot use call TODAY: volatile calls are not stable write-time
derivations.
```

Refused: volatile calls (`TODAY`, `NOW`, `RAND`, `RANDBETWEEN`, and the two
clock-reading validator helpers `isAfter` / `isBefore`) · request, actor and
mutation context (`@User`, `@Env`, `$new`, `$old`) · user-defined `def` helpers ·
jq `try` / `catch` · `error` · `label` / `break` · assignment (`=`, `|=`) ·
recursive descent (`..`) · format filters (`@csv`) · control and side-effect
calls (`debug`, `env`, `input`, `stderr`, `halt`) · runtime metadata
(`builtins`).

Allowed and useful: `IFERROR` / `IFNA` · optional access (`.a?`) · aggregates
(`SUM`, `AVERAGE`, `COUNT`, `NPV`) · validator helpers (`isEmail`, `isDate`) ·
`LET` · bindings (`. as $x | …`) · explicit folds (`reduce`, `foreach`) ·
structural ops (`keys`, `to_entries`, `group_by`, `unique`, `tojson`).

`isAfter` and `isBefore` are the surprise on that list: they sit among the
validator helpers, they look like ordinary date comparisons, and they default
their second argument to the current time — so the profile classes them with
`NOW`. Compare two stored dates with the operators instead
(`` fx`StartDate < EndDate` ``), which is deterministic and needs no helper.

The boundary is determinism: a derived value comes from the record snapshot, not
from the clock, the viewer, or the request. It is computed once server-side and
stored in the search doc, so anything ambient would bake one viewer's answer in
for everyone.

## 3. An aggregate needs a collected array, not a stream

**The single most expensive trap.** jq function arguments are streams, so
navigating into an array field and handing that straight to an aggregate calls
the aggregate once *per element* — and the field receives an array of per-element
results instead of one number. Nothing errors.

```ts
// WRONG — compiles to SUM(.claims[].paid); with two claims the field gets [10, 5]
computeVia: expression(fx`SUM(Claims[].Paid)`);

// RIGHT — collect first, then aggregate: 15
computeVia: expression(fx`SUM([Claims[].Paid])`);

// RIGHT — the jq spelling, with a fallback for the empty case
computeVia: expression(jq`[.claims[] | .paidAmount] | add // 0`);
```

Same for `AVERAGE`, `COUNT`, `MAX`, `MIN`, `SUMIF`. Collect **the argument that
iterates**, and only that one — the rule is narrower than "wrap everything", and
wrapping the wrong thing is its own bug:

- An iterating path is what needs it: `SUM([Claims[].Paid])`,
  `SUMIF([Claims[].Paid], ">6")`.
- A field that already holds an array is a single value. `SUM(Amounts)` over a
  `containsMany(NumberField)` is correct as it stands.
- A comma list is collected by the compiler, which is why the multi-argument
  spelling has never shown the trap: `SUM(Paid, Reserve)` compiles to
  `SUM([.paid, .reserve])`, and even `SUM(Claims[].Paid, 0)` becomes
  `SUM([.claims[].paid, 0])`. Write the `[…]` anyway — the single-argument form
  is what an author reaches for next.
- **Never wrap a scalar parameter.** `ROUND([1.234], 2)` and
  `NPV([0.1], CashFlows)` hand an array to a function that wants a number; each
  returns null.

Passing a `schema` is the other way to get this right — with field metadata, the
compiler collects implicitly, so `SUM("Line Item"."Line Total")` compiles to
`SUM([.lineItems[].lineTotal])`. Without a schema, quoted multi-word labels fail
loudly (`Cannot index string with string`) and a bare PascalCase label resolves

From cardstack/boxel-skills, MIT.

More from cardstack/boxel-skills

Signals

Stars
1
Forks
0
Open issues
0
Last commit (this folder)
Oct 8, 2026 (21 days ago)
Last release
Oct 23, 2026 (6 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from cardstack

Cite this page

bxl-authoring skill. Agent Mart. https://agentmart.dev/skills/cardstack-bxl-authoring (last updated 2026-10-24)