Skip to main content
Data Forms V2 introduces a declarative, schema-driven approach to building user interfaces in Kodexa. Instead of configuring cards with flat YAML properties, you define a tree of UINode objects with typed bindings, event handlers, and scripting support. This gives you fine-grained control over how data flows into components and how user interactions are processed. This guide walks you through building a simple V2 form from scratch. By the end, you will have a working form with a card panel, a label bound to live data, and an event handler that responds to user actions. If you are migrating from V1 card-based forms, see the Migration Guide for a side-by-side comparison.

Prerequisites

  • A Kodexa project with at least one data definition (taxonomy)
  • A document store with sample data
  • Familiarity with the Data Forms overview

Step 1: Create a V2 Data Form

A V2 data form is identified by setting version: "2" at the top level. The form body is defined in the nodes array, which contains UINode objects instead of flat card entries.
{
  "version": "2",
  "nodes": []
}
When version is set to "2" or the nodes array is present and non-empty, the platform automatically uses the V2 schema renderer. Existing V1 forms with cards arrays continue to work unchanged.

Step 2: Add a Card Panel with a Label

Every visible element in a V2 form is a UINode with a component field that references a registered component. The V2 renderer re-uses all existing V1 card components — you reference them by their camelCase type name. Add a card panel containing a label:
{
  "version": "2",
  "nodes": [
    {
      "component": "card:cardPanel",
      "props": {
        "title": "Invoice Summary",
        "showHeader": true
      },
      "children": [
        {
          "component": "card:label",
          "props": {
            "label": "Hello, V2 Forms!"
          }
        }
      ]
    }
  ]
}
The component field uses the format namespace:type. All built-in card components live in the card namespace. You can also use just the type name (e.g., "cardPanel") and the registry will resolve it via its alias table.

Step 3: Add a Data Binding

Static labels are useful, but the real power of V2 forms comes from bindings — JavaScript expressions that are evaluated against the current data context. Replace the static label with a binding:
{
  "component": "card:label",
  "bindings": {
    "label": "ctx.dataObjects?.[0]?.path ?? 'No data loaded'"
  }
}
The bindings object maps prop names to JavaScript expressions. Each expression receives a ctx object containing the current data context, which includes dataObjects, tagMetadataMap, and any loop variables from parent for iterators. See the Data Binding guide for the full context shape and expression syntax.

Step 4: Add an Event Handler

V2 forms support a rich event system. You can attach handlers to any component event using the events object. Each handler specifies a type (such as script, emit, or scriptRef) and a target. Add a click handler that logs a message:
{
  "component": "card:label",
  "bindings": {
    "label": "ctx.dataObjects?.[0]?.path ?? 'No data loaded'"
  },
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.log.debug('Label clicked', ctx.dataObjects?.[0]?.uuid)"
    }
  }
}
You can also attach multiple handlers to the same event by using an array:
"events": {
  "click": [
    { "type": "script", "target": "kodexa.log.debug('Handler 1')" },
    { "type": "emit", "target": "label-clicked" }
  ]
}
See the Event Handling guide for all event types and configuration options.

Full Example

Here is the complete V2 form combining all the steps above:
{
  "version": "2",
  "nodes": [
    {
      "component": "card:cardPanel",
      "props": {
        "title": "Invoice Summary",
        "showHeader": true,
        "groupTaxon": "invoice"
      },
      "children": [
        {
          "component": "card:label",
          "bindings": {
            "label": "ctx.dataObjects?.[0]?.path ?? 'No data loaded'"
          },
          "events": {
            "click": {
              "type": "script",
              "target": "kodexa.log.debug('Label clicked')"
            }
          }
        },
        {
          "component": "card:dataAttributeEditor",
          "props": {
            "taxon": "invoice/invoiceNumber"
          }
        },
        {
          "component": "card:dataAttributeEditor",
          "props": {
            "taxon": "invoice/totalAmount"
          }
        }
      ]
    }
  ],
  "scripts": {
    "formatCurrency": "function(ctx) { const val = ctx.value; return val ? '$' + Number(val).toFixed(2) : '--'; }"
  },
  "bridge": {
    "permissions": ["data:read", "navigation", "formState"],
    "maxExecutionMs": 1000
  }
}

Key sections explained

SectionPurpose
versionMust be "2" to enable the V2 schema renderer
nodesArray of UINode objects forming the component tree
scriptsNamed script registry for reusable functions (referenced via scriptRef events or computed bindings)
bridgeConfiguration for the kodexa.* bridge API, including permissions and execution limits

Next Steps