> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Form Structure & Schema

> Reference for the Kodexa data form schema, covering UINode tree structure, conditional rendering, iteration, and the top-level form shape.

V2 data forms are declarative JSON documents that describe a component tree. The renderer walks the tree top-down, resolving each node's component from the registry, evaluating conditionals and bindings, and rendering the result. This page covers the top-level form shape, the UINode interface, conditional rendering, iteration, and styling.

## The DataFormV2 Shape

Every V2 form has four top-level sections alongside the component tree.

```json theme={null}
{
  "version": "2",
  "nodes": [],
  "scripts": {
    "formatCurrency": "function(ctx) { return '$' + Number(ctx.value).toFixed(2); }"
  },
  "scriptModules": {
    "validateRow": {
      "source": "function(ctx) { return ctx.amount > 0; }",
      "description": "Validates that amount is positive",
      "inputs": { "amount": "number" },
      "returns": "boolean",
      "debounce": 300
    }
  },
  "bridge": {
    "permissions": ["data:read", "data:write", "navigation"],
    "apiBaseUrl": "/api",
    "maxExecutionMs": 2000
  }
}
```

| Field           | Type                           | Description                                                                                                                          |
| --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `version`       | `"2"`                          | Required flag that enables V2 rendering. If omitted, a non-empty `nodes` array also triggers V2 mode.                                |
| `nodes`         | `UINode[]`                     | The root-level component tree.                                                                                                       |
| `scripts`       | `Record<string, string>`       | Named script dictionary. Keys are names referenced by `scriptRef` events; values are JavaScript function bodies.                     |
| `scriptModules` | `Record<string, ScriptModule>` | Reusable script modules with declared `inputs`, `returns`, and optional `debounce`.                                                  |
| `bridge`        | `BridgeConfig`                 | Permissions and configuration for the `kodexa.*` Bridge API. The `permissions` array controls which bridge methods scripts may call. |
| `copyRules`     | `TaxonCopyRule[]`              | Optional form-level copy rules shared by every card on the form. See [Form-level copy rules](#form-level-copy-rules).                |

## Form-level Copy Rules

`DataFormV2` accepts an optional top-level `copyRules: TaxonCopyRule[]` field. Cards that consume copy rules (panels, transposed grid rollups) merge form-level rules with their own per-card rules — on a `sourceTaxon` conflict, **card-level wins as a whole** (the per-card rule replaces the form-level entry for that source taxon, no field-level merge).

Use form-level rules when several panels on the same form need the same copy block — a freight review form with five-plus source panels (one per BoL and manifest) typically needs the same `sourceDocumentRef` / `sourceBolNumber` / `sourceDocumentType` stamp on each. Declaring the rule once at the form level avoids repeating it on every panel.

```json theme={null}
{
  "version": "2",
  "copyRules": [
    {
      "sourceTaxon": "billoflading/lineitems",
      "allowedDestinations": [
        {
          "taxon": "shipments/freightclasses",
          "copyBehavior": {
            "copyAttributes": true,
            "stampAttributes": {
              "sourceDocumentRef":  "${source.parent.idString}",
              "sourceBolNumber":    "${source.parent.blnumber}",
              "sourceDocumentType": "BillOfLading"
            }
          }
        }
      ]
    }
  ],
  "nodes": []
}
```

### `stampAttributes` on copy rules

`stampAttributes` lives on a rule's `copyBehavior` and is distinct from `copyAttributes`:

* **`copyAttributes`** (boolean) clones existing source attributes onto the destination with content-tag preservation, so the destination keeps the source's spatial anchors.
* **`stampAttributes`** (map) writes *new* derived attributes onto the destination — typically provenance/audit fields like `sourceDocumentRef`, `sourceBolNumber`, `sourceDocumentType` that have no source attribute to clone.

Each `stampAttributes` value is either a literal string (no `${...}` placeholders) written as-is, or a `${path}` template resolved against a source-row context with these accessors:

| Accessor                                           | Resolves to                                                 |
| -------------------------------------------------- | ----------------------------------------------------------- |
| `source`                                           | The source `DataObject` being copied                        |
| `source.parent`                                    | The source's parent `DataObject`                            |
| `source.idString` / `source.parent.idString`       | `String(dataObject.id)` — stable within the current session |
| `source.uuid` / `source.parent.uuid`               | The data object's UUID                                      |
| `source.<fieldName>` / `source.parent.<fieldName>` | `getFirstAttributeValue(<fieldName>)` on that object        |

Templates that resolve to `undefined` are dropped (no attribute is created). When a template expression is a single `${...}`, the resolved value preserves its native type (number, boolean, date); when it's mixed with literal text, the result is stringified.

Stamped attributes are written as plain values — they do not carry a content-node `tagId` link. Use `copyAttributes: true` for fields that should keep the source's tag-to-content-node link.

## UINode Anatomy

A `UINode` is the fundamental building block. Every element in a V2 form -- panels, editors, labels, grids -- is a UINode.

```typescript theme={null}
interface UINode {
  component: string;                              // Registry key, e.g. "card:cardPanel"
  props?: Record<string, any>;                    // Static property values
  bindings?: Record<string, string>;              // JS expressions evaluated against the data context
  computed?: Record<string, string>;              // Cached computed expressions (reserved)
  events?: Record<string, EventConfig | EventConfig[]>; // Event handler configurations
  children?: UINode[];                            // Nested child nodes (default slot)
  slots?: Record<string, UINode[]>;               // Named slot content
  if?: string;                                    // JS expression -- removes node from DOM when falsy
  show?: string;                                  // JS expression -- hides via CSS when falsy
  for?: ForConfig;                                // Iteration directive
  class?: string | Record<string, string>;        // CSS class(es)
  style?: Record<string, any> | string;           // Inline styles
  key?: string;                                   // Unique identifier for list rendering
  ref?: string;                                   // Template ref for Bridge API access
  meta?: NodeMeta;                                // Design-time metadata (ignored at runtime)
}
```

### How the renderer works

`SchemaRoot` initializes the QuickJS script runtime, the Bridge host, and a reactive data context containing the current `dataObjects` and `tagMetadataMap`. It then walks the `nodes` array and renders a `SchemaNode` for each entry.

Each `SchemaNode` resolves its `component` from the registry, evaluates the `if` conditional, merges static `props` with dynamically evaluated `bindings`, wires up `events` as handler functions, and renders the resolved Vue component. Children and slots are rendered recursively as nested `SchemaNode` instances.

## Conditional Rendering

V2 forms support two conditional mechanisms that control whether a node appears in the DOM.

### `if` and `show` (JavaScript expressions)

`if` removes the node from the DOM entirely when its expression evaluates to a falsy value. `show` keeps the node mounted but hides it with `display: none`.

```json theme={null}
{
  "component": "card:cardPanel",
  "if": "ctx.dataObjects?.some(o => o.path === 'invoice')",
  "props": { "title": "Invoice Details" }
}
```

```json theme={null}
{
  "component": "card:label",
  "show": "ctx.$item?.status !== 'draft'",
  "props": { "label": "Published" }
}
```

<Note>
  Use `if` when the node is expensive to keep mounted or when you want clean DOM output. Use `show` when you need the node to retain its internal state while toggled.
</Note>

### `ifFormula` and `showFormula` (reactive KEXL formulas)

These behave the same as `if` and `show` but accept reactive KEXL formulas evaluated via the WASM document engine rather than plain JavaScript expressions. They re-evaluate automatically when referenced data attributes change, without requiring explicit watchers.

```json theme={null}
{
  "component": "card:cardPanel",
  "ifFormula": "hasValue('invoice/totalAmount')",
  "props": { "title": "Total Summary" }
}
```

```json theme={null}
{
  "component": "card:label",
  "showFormula": "getAttribute('invoice/status') == 'approved'",
  "props": { "label": "Approved" }
}
```

<Note>
  `ifFormula` and `showFormula` are planned extensions to the UINode interface. Use `if` and `show` with JavaScript expressions for current implementations.
</Note>

## Iteration

The `for` directive renders a node once for each item in a source collection. Loop variables are injected into the data context for child expressions.

```typescript theme={null}
interface ForConfig {
  source: string;   // JS expression resolving to an array
  itemAs: string;   // Variable name for the current item
  indexAs?: string;  // Variable name for the current index
  key: string;       // Expression for a unique key per item
}
```

### Example: rendering a panel per data object

```json theme={null}
{
  "component": "card:cardPanel",
  "for": {
    "source": "ctx.dataObjects",
    "itemAs": "$item",
    "indexAs": "$index",
    "key": "$item.uuid"
  },
  "bindings": {
    "title": "$item.path + ' (#' + ($index + 1) + ')'"
  },
  "children": [
    {
      "component": "card:dataAttributeEditor",
      "bindings": {
        "dataObjectUuid": "$item.uuid"
      },
      "props": { "taxon": "lineItem/description" }
    }
  ]
}
```

The `source` expression is evaluated against the current data context. Each iteration injects `$item` and `$index` (or whatever names you specify in `itemAs` and `indexAs`) into the context for use in bindings and child expressions. The `key` expression must produce a unique value per item to enable efficient DOM updates.

## Styling

V2 nodes accept `class` and `style` for visual customization.

`class` can be a static string or a record mapping class names to binding expressions for conditional application:

```json theme={null}
{
  "class": "mt-4 rounded-lg border"
}
```

```json theme={null}
{
  "class": {
    "bg-red-50": "ctx.$item?.hasException",
    "bg-white": "!ctx.$item?.hasException"
  }
}
```

`style` accepts either an inline CSS string or an object of style properties:

```json theme={null}
{
  "style": { "maxHeight": "400px", "overflow": "auto" }
}
```

The platform uses Tailwind CSS, so Tailwind utility classes in the `class` field are the preferred approach for most styling needs.

## Next Steps

<CardGroup cols={2}>
  <Card title="Data Binding" icon="link" href="/guides/data-forms/data-binding">
    Expressions, context variables, and reactive bindings
  </Card>

  <Card title="Layout Components" icon="table-columns" href="/guides/data-forms/layout-components">
    Panels, tabs, rows, columns, and alerts
  </Card>

  <Card title="Data Components" icon="pen-to-square" href="/guides/data-forms/data-components">
    Attribute editors, tables, grids, and more
  </Card>

  <Card title="Bridge API" icon="plug" href="/guides/data-forms/bridge-api">
    The kodexa.\* scripting API reference
  </Card>
</CardGroup>
