Schema Nodes

The x-flow-schema directive turns a node into a structured table display — header + one row per field + per-row labelled handles. It's the right primitive for ERD diagrams, GraphQL schema viewers, API payload designers, and anything where users drag connections between specific fields of one object and another.

INTERACTIVE
<div x-data="flowCanvas({
    nodes: [
        {
            id: 'user',
            position: { x: 0, y: 0 },
            data: {
                label: 'User',
                fields: [
                    { name: 'id',         type: 'uuid',      key: 'primary' },
                    { name: 'email',      type: 'text',      required: true },
                    { name: 'team_id',    type: 'uuid',      },
                    { name: 'created_at', type: 'timestamp' },
                ],
            },
        }
    ],
    edges: [
    ],
    background: 'dots',
    fitViewOnInit: true,
    controls: false,
    pannable: false,
    zoomable: false,
})" class="flow-container" style="height: 340px;">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node" x-flow-schema></div>
        </template>
    </div>
</div>

Usage

Add a fields block in your data structure to provide the relevant content.

<div x-data="flowCanvas({
  nodes: [
    {
      id: 'user',
      position: { x: 0, y: 0 },
      data: {
        label: 'User',
        fields: [
          { name: 'id',         type: 'uuid',      key: 'primary' },
          { name: 'email',      type: 'text',      required: true },
          { name: 'team_id',    type: 'uuid',      key: 'foreign' },
          { name: 'created_at', type: 'timestamp' },
        ],
      },
    },
  ],
  edges: [],
})">
  <div x-flow-viewport>
    <template x-for="node in nodes" :key="node.id">
      <div x-flow-node="node" x-flow-schema></div>
    </template>
  </div>
</div>

The directive reads node.data.label and node.data.fields at init and re-runs on any mutation to those properties. Each field becomes one row with:

  • a target handle on the left (for incoming edges)
  • the field name + optional icon prefix
  • a type pill on the right
  • a source handle on the right (for outgoing edges)

Both handles carry data-flow-handle-id="<field.name>" — edges between schema nodes set sourceHandle and targetHandle to field names, and AlpineFlow's handle infrastructure resolves the coordinates automatically.

Field shape

interface FlowSchemaField {
  name: string;
  type: string;
  key?: 'primary' | 'foreign';
  required?: boolean;
  icon?: string;
}

Only name and type are load-bearing. The rest drive CSS decorations:

Flag Class added Default theme
key: primary flow-schema-row--pk PK badge (amber)
key: foreign flow-schema-row--fk FK badge (violet)
required flow-schema-row--required red asterisk suffix
icon renders .flow-schema-row-icon span inline prefix (emoji / text)

Connecting fields

An edge between two schema nodes specifies which field on each side it attaches to:

{
  id: 'user-team',
  source: 'user',
  sourceHandle: 'team_id',
  target: 'team',
  targetHandle: 'id',
}

Users drag from a row's right-edge handle to another row's left-edge handle — AlpineFlow records sourceHandle / targetHandle from the handle ids automatically.

INTERACTIVE
<div x-data="flowCanvas({
    nodes: [
        {
            id: 'user',
            position: { x: 0, y: 0 },
            data: {
                label: 'User',
                fields: [
                    { name: 'id',         type: 'uuid',      key: 'primary' },
                    { name: 'email',      type: 'text',      required: true },
                    { name: 'team_id',    type: 'uuid',      key: 'foreign' },
                    { name: 'created_at', type: 'timestamp' },
                ],
            },
        },
        {
            id: 'team',
            position: { x: 360, y: 40 },
            data: {
                label: 'Team',
                fields: [
                    { name: 'id',   type: 'uuid', key: 'primary' },
                    { name: 'name', type: 'text', required: true },
                    { name: 'plan', type: 'enum' },
                ],
            },
        },
    ],
    edges: [
        { id: 'user-team', source: 'user', sourceHandle: 'team_id', target: 'team', targetHandle: 'id' },
    ],
    background: 'dots',
    fitViewOnInit: true,
    controls: false,
    pannable: false,
    zoomable: false,
})" class="flow-container" style="height: 340px;">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node" x-flow-schema></div>
        </template>
    </div>
</div>

Customizing rendering

You rarely have to fork the directive to customize it — style fields with metadata + CSS, augment the rendered output per-render with hooks (class resolvers and decorators), or skip the directive and roll your own template. The full guide, with live examples, lives in the Schema addon docs:

Customizing rendering

See Also

  • Schema Addon — field CRUD with edge cascade, reference inference, JSON serialization, and inspector directives built on top of this primitive