Skip to main content

A2UI Render Validation Hook

Validates A2UI JSON output before sending to client renderers.

Trigger

  • Event: PostToolUse
  • Pattern: Agent output contains A2UI JSON

Validation Rules

Schema Validation

  1. Required Fields

    • type: "a2ui" (required)
    • version (required, e.g., "0.8")
    • components (required, non-empty array)
  2. Component Validation

    • Each component has unique id
    • Each component has valid type
    • Parent-child references are valid
    • Required properties are present
  3. Data Binding Validation

    • Binding paths reference valid data keys
    • No circular references
    • Template syntax is valid

Implementation

#!/usr/bin/env python3
"""A2UI Render Validation Hook"""

import json
import sys
from typing import Any

VALID_TYPES = {
"container", "card", "grid", "stack",
"text", "image", "icon", "divider",
"button", "text-field", "select", "checkbox", "slider",
"list", "table", "chart"
}

def validate_a2ui(data: dict) -> list[str]:
"""Validate A2UI JSON structure."""
errors = []

# Required fields
if data.get("type") != "a2ui":
errors.append("Missing or invalid 'type' field (expected 'a2ui')")

if "version" not in data:
errors.append("Missing 'version' field")

components = data.get("components", [])
if not components:
errors.append("'components' array is empty or missing")
return errors

# Validate components
ids = set()
parent_refs = set()

for comp in components:
comp_id = comp.get("id")
comp_type = comp.get("type")

# Unique IDs
if not comp_id:
errors.append(f"Component missing 'id': {comp}")
elif comp_id in ids:
errors.append(f"Duplicate component id: {comp_id}")
else:
ids.add(comp_id)

# Valid type
if comp_type not in VALID_TYPES:
errors.append(f"Invalid component type '{comp_type}' for id '{comp_id}'")

# Track parent-child refs
for child_id in comp.get("children", []):
parent_refs.add(child_id)

# Validate parent-child references
for ref in parent_refs:
if ref not in ids:
errors.append(f"Invalid child reference: '{ref}' not found in components")

return errors


def main():
"""Hook entry point."""
# Read input from stdin (Claude Code hook protocol)
input_data = json.load(sys.stdin)

tool_result = input_data.get("tool_result", "")

# Check if output contains A2UI JSON
if '"type": "a2ui"' not in tool_result and '"type":"a2ui"' not in tool_result:
# Not A2UI output, allow
print(json.dumps({"decision": "allow"}))
return

# Extract and parse A2UI JSON
try:
# Find JSON in output
start = tool_result.find("{")
end = tool_result.rfind("}") + 1
if start >= 0 and end > start:
a2ui_json = json.loads(tool_result[start:end])
errors = validate_a2ui(a2ui_json)

if errors:
print(json.dumps({
"decision": "block",
"reason": f"A2UI validation failed: {'; '.join(errors)}"
}))
else:
print(json.dumps({"decision": "allow"}))
else:
print(json.dumps({"decision": "allow"}))
except json.JSONDecodeError:
print(json.dumps({
"decision": "block",
"reason": "Invalid JSON in A2UI output"
}))


if __name__ == "__main__":
main()

Configuration

Add to ~/.claude/settings.json:

{
"hooks": {
"PostToolUse": [
{
"pattern": "a2ui",
"command": "python3 ~/.coditect/hooks/a2ui-render-validation.py"
}
]
}
}

Validation Errors

ErrorCauseFix
Missing typeNo "type": "a2ui"Add required field
Duplicate IDTwo components same IDUse unique IDs
Invalid typeUnknown component typeUse valid type from catalog
Invalid refChild ID doesn't existFix parent-child refs
Invalid JSONMalformed JSONFix JSON syntax

When to Skip

  • Non-A2UI outputs (auto-detected)
  • Draft/preview mode
  • Testing with --no-validate flag

Version: 1.0.0 Created: 2026-01-13