Use when creating or editing Kodexa Data Forms V2 — schema-driven JSON/YAML defining UI components for viewing and editing extracted document data, including UINode trees, data binding, QuickJS scripting, Bridge API, and event handling
How this skill is triggered — by the user, by Claude, or both
Slash command
/kodexa-metadata-skills:data-formThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Data Forms V2 define the UI for viewing and editing extracted document data. They use a component tree (UINode) model with data binding, scripting via QuickJS sandbox, and a Bridge API for interacting with platform services. Forms are defined in JSON and rendered dynamically in the workspace.
Data Forms V2 define the UI for viewing and editing extracted document data. They use a component tree (UINode) model with data binding, scripting via QuickJS sandbox, and a Bridge API for interacting with platform services. Forms are defined in JSON and rendered dynamically in the workspace.
Generate the complete V2 form JSON.
{
"version": "2",
"nodes": [],
"scripts": {},
"scriptModules": {},
"bridge": {
"permissions": ["data:read", "data:write", "navigation", "formState"],
"apiBaseUrl": "/api",
"maxExecutionMs": 2000
}
}
| Field | Purpose |
|---|---|
version | Must be "2" |
nodes | Array of root UINode components |
scripts | Named inline functions: "name": "function(ctx) { ... }" |
scriptModules | Named modules with metadata: source, description, inputs, returns, debounce |
bridge | Bridge API configuration: permissions, base URL, timeout |
{
"component": "card:cardPanel",
"props": { "title": "Section Title" },
"bindings": { "subtitle": "ctx.dataObjects?.length + ' items'" },
"computed": { "backgroundColor": "deriveColor" },
"events": {
"click": { "type": "script", "target": "kodexa.log.debug('clicked')" }
},
"children": [],
"slots": { "tab-1": [] },
"if": "ctx.dataObjects?.length > 0",
"show": "ctx.$item?.status !== 'archived'",
"for": {
"source": "ctx.dataObjects",
"itemAs": "$item",
"indexAs": "$index",
"key": "$item.uuid"
},
"key": "unique-key",
"class": "mt-4",
"style": { "backgroundColor": "#ffffff" },
"ref": "panelRef",
"meta": {
"label": "Panel Description",
"category": "layout"
}
}
| Component | Purpose | Key Props |
|---|---|---|
card:cardPanel | Container panel | title, groupTaxon, collapsible |
card:cardGroup | Group of panels | columns, gap |
card:tabs | Tabbed interface | tabs array |
card:horizontalLine | Divider | color, thickness |
| Component | Purpose | Key Props |
|---|---|---|
card:label | Text display | text, variant |
card:singleTaxon | Single taxon value | taxon path |
| Component | Purpose | Key Props |
|---|---|---|
card:dataAttributeEditor | Edit a data attribute | taxon path |
card:grid | Data grid/table | columns, dataSource |
card:taxonGrid | Taxon-based grid | groupTaxon, columns |
card:dataStoreGrid | Store data grid | storeRef |
card:transposedGrid | Transposed view | groupTaxon |
| Component | Purpose | Key Props |
|---|---|---|
card:dataObjectsTree | Data object tree | rootPath |
card:taxonTabs | Tabs per taxon | groupTaxon |
card:exceptions | Validation exceptions | showResolved |
{
"events": {
"click": {
"type": "script",
"target": "kodexa.log.debug('clicked', ctx.$item?.uuid)",
"condition": "ctx.$item?.status !== 'locked'",
"debounce": 300
},
"change": {
"type": "scriptRef",
"target": "validateField",
"params": { "fieldName": "invoice_number" }
},
"submit": {
"type": "emit",
"target": "form-submitted"
}
}
}
Event types: script (inline JS), scriptRef (named script), emit (bubble to parent).
data:read or data:write)kodexa.data.getDataObjects(filter) // Get data objects
kodexa.data.getDataObject(uuid) // Get single object
kodexa.data.getAttributes(uuid) // Get attributes
kodexa.data.getAttribute(uuid, path) // Get single attribute
kodexa.data.setAttribute(uuid, path, v) // Set attribute value
kodexa.data.deleteAttribute(uuid, path, opts?) // Delete attribute (see options below)
kodexa.data.addDataObject(parentUuid, p) // Add child object
kodexa.data.deleteDataObject(uuid) // Delete object
kodexa.data.getTaxonomies() // Get available taxonomies
deleteAttribute(uuid, path, options?) options:
| Option | Default | Behavior |
|---|---|---|
autoDeleteEmptyParent | false | When true, the parent data object is deleted if removing this attribute leaves it empty. Used by transposed grid cells. Default does not cascade — the parent stays as an empty shell. |
// Default: parent stays even if empty
kodexa.data.deleteAttribute(rowUuid, "amount");
// Cascade: parent is removed too if it's now empty
kodexa.data.deleteAttribute(rowUuid, "amount", { autoDeleteEmptyParent: true });
The card:exceptions component (and any helper that calls getUniqueExceptions(...)) now walks both data-object-level exceptions and attribute-level exceptions. This means exceptions migrated to attributes when a user interacts with a field will continue to surface in the panel. The helper isAutoResolved() distinguishes platform-generated closing comments from user overrides — useful when filtering "what did the user actually resolve?" vs. auto-cleared platform records.
navigation)kodexa.navigation.focusAttribute(uuid, path) // Focus on attribute
kodexa.navigation.scrollToNode(ref) // Scroll to component
kodexa.navigation.switchView(viewName) // Switch workspace view
formState)kodexa.form.get(key) // Get form state
kodexa.form.set(key, value) // Set form state
kodexa.form.getNodeRef(ref) // Get component reference
http:get or http:post)kodexa.http.get(path) // GET request
kodexa.http.post(path, body) // POST request
Activity API note (2026-05-02). The runtime field formerly known as
Activity.statusis nowActivity.lifecycleState. If a script reads or filters activities viakodexa.http.get('/api/activities/...'), uselifecycleState(notstatus).
kodexa.log.debug(...args)
kodexa.log.warn(...args)
kodexa.log.error(...args)
| Variable | Description |
|---|---|
ctx.dataObjects | Array of data objects at current scope |
ctx.tagMetadataMap | Map of taxonomy path to tag metadata |
ctx.$item | Current item in for loop |
ctx.$index | Current index in for loop |
ctx.$parent | Parent data context |
ctx.$root | Root data context |
{
"version": "2",
"nodes": [
{
"component": "card:cardPanel",
"props": {
"title": "Invoice Summary",
"groupTaxon": "invoice",
"collapsible": true
},
"children": [
{
"component": "card:cardGroup",
"props": { "columns": 2 },
"children": [
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/invoice_number" }
},
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/invoice_date" }
},
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/vendor/name" }
},
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/total_amount" },
"bindings": {
"class": "ctx.dataObjects?.[0]?.attributes?.total_amount > 10000 ? 'highlight-warning' : ''"
}
}
]
},
{
"component": "card:horizontalLine"
},
{
"component": "card:taxonGrid",
"props": {
"groupTaxon": "invoice/line_items",
"columns": [
{ "field": "description", "header": "Description", "width": 300 },
{ "field": "quantity", "header": "Qty", "width": 80 },
{ "field": "unit_price", "header": "Unit Price", "width": 120 },
{ "field": "line_total", "header": "Total", "width": 120 }
]
}
},
{
"component": "card:cardPanel",
"props": { "title": "Totals" },
"children": [
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/subtotal" }
},
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/tax_amount" }
},
{
"component": "card:dataAttributeEditor",
"props": { "taxon": "invoice/total_amount" }
}
]
},
{
"component": "card:exceptions",
"props": { "showResolved": false }
}
]
}
],
"scripts": {
"formatCurrency": "function(ctx) { return '$' + Number(ctx.value).toFixed(2); }",
"highlightOverdue": "function(ctx) { return new Date(ctx.value) < new Date() ? 'overdue' : ''; }"
},
"bridge": {
"permissions": ["data:read", "data:write", "navigation", "formState"],
"maxExecutionMs": 2000
}
}
| Mistake | Fix |
|---|---|
Missing version: "2" | Required to use V2 schema |
| Bridge permissions not declared | Scripts fail silently without required permissions |
Wrong taxon path in props.taxon | Must match taxonomy hierarchy: group/field |
if vs show confusion | if removes from DOM; show hides with CSS |
Missing key in for loops | Always provide unique key for list rendering |
Script exceeding maxExecutionMs | Optimize or increase timeout in bridge config |
Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub kodexa-ai/kodexa-metadata-skills --plugin kodexa-metadata-skills