# Build ER and UML diagrams

Use the ER and UML kits when your schema or class model already exists as data and you want Grafloria to render its cards, relationship notation, and interactive canvas. The kits return diagram specs; [`render()`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-core#render) mounts those specs in JavaScript, while framework bindings mount the same specs in their host components.

## Build an ER diagram

Pass entities and relationships to [`erDiagram()`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-diagram-kit-functions#erdiagram). Each entity becomes a table card with typed column rows and PK/FK badges. Relationships draw as orthogonal edges with cardinality markers; endpoints written as `ENTITY.column` attach to that column's row. The kit provides row selection by default.

The JavaScript example connects `CUSTOMER.id` to `ORDER.customer_id`. It renders two table cards, their key badges, and a one-to-many relationship with its label. Give the mounted canvas a real height so it has space to draw. The Angular sample uses column-qualified ends too, so each relationship endpoint attaches to the matching row.

The JavaScript samples create and append a 500-pixel host before mounting. React and Vue mount the same ER data through their framework hosts; Angular uses a separate AUTHOR-to-BOOK example to show column-qualified endpoints.

The React [`GrafloriaDiagram`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-react#grafloriadiagram) and Vue [`GrafloriaDiagram`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#grafloriadiagram) bindings accept the kit spec directly. Angular uses [`GrafloriaDiagramComponent`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-angular-components-classes#grafloriadiagramcomponent). Qwik uses [`GrafloriaDiagram`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-qwik#grafloriadiagram) with a serializable [`RenderSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-core#renderspec) of nodes and edges; that Qwik variant draws generic nodes and labeled edges, not the kits' HTML cards, key badges, cardinality markers, UML relationship markers, or multiplicity labels. The ER builder accepts [`ErDiagramOptions`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-diagram-kit-interfaces#erdiagramoptions); the UML builder accepts [`UmlDiagramOptions`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-diagram-kit-interfaces#umldiagramoptions).

:::code-group
```js title="JavaScript"
import { erDiagram, render } from '@grafloria/element';

const spec = erDiagram({
  entities: [
    { id: 'CUSTOMER', name: 'Customer', position: { x: 60, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'email', type: 'varchar' },
    ] },
    { id: 'ORDER', name: 'Order', position: { x: 390, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'customer_id', type: 'int', fk: true },
      { name: 'placed_at', type: 'date' },
    ] },
  ],
  relationships: [
    {
      from: 'CUSTOMER.id',
      to: 'ORDER.customer_id',
      label: 'places',
      cardinality: 'one-to-many',
    },
  ],
});

const host = document.createElement('div');
host.style.cssText = 'display:block; width:100%; height:500px';
document.body.append(host);
const instance = render(spec, host);
instance.fitView(40);
```
```ts title="Angular"
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { erDiagram } from '@grafloria/element';

@Component({
  selector: 'app-er-diagram',
  standalone: true,
  imports: [GrafloriaDiagramComponent],
  template: '<grafloria-diagram [spec]="spec" style="display:block; height:500px" />',
})
export class ErDiagramComponent {
  spec = erDiagram({
    entities: [
      { id: 'AUTHOR', name: 'Author', position: { x: 60, y: 80 }, columns: [
        { name: 'id', type: 'int', pk: true },
        { name: 'name', type: 'varchar' },
      ] },
      { id: 'BOOK', name: 'Book', position: { x: 390, y: 80 }, columns: [
        { name: 'id', type: 'int', pk: true },
        { name: 'author_id', type: 'int', fk: true },
        { name: 'title', type: 'varchar' },
      ] },
    ],
    relationships: [{ from: 'AUTHOR.id', to: 'BOOK.author_id', label: 'writes', cardinality: 'one-to-many' }],
  });
}
```
```tsx title="Qwik"
import { component$ } from '@builder.io/qwik';
import { GrafloriaDiagram } from '@grafloria/qwik';
import type { RenderSpec } from '@grafloria/element';

const spec: RenderSpec = {
  nodes: [
    { id: 'CUSTOMER', position: { x: 60, y: 80 }, size: { width: 240, height: 90 }, label: 'Customer · PK id · email' },
    { id: 'ORDER', position: { x: 390, y: 80 }, size: { width: 260, height: 90 }, label: 'Order · PK id · FK customer_id' },
  ],
  edges: [{ id: 'places', source: 'CUSTOMER', target: 'ORDER', label: 'places' }],
};

export default component$(() => (
  <div style={{ height: '500px' }}>
    <GrafloriaDiagram spec={spec} />
  </div>
));
```
```tsx title="React"
import { GrafloriaDiagram } from '@grafloria/react';
import { erDiagram } from '@grafloria/element';

const spec = erDiagram({
  entities: [
    { id: 'CUSTOMER', name: 'Customer', position: { x: 60, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'email', type: 'varchar' },
    ] },
    { id: 'ORDER', name: 'Order', position: { x: 390, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'customer_id', type: 'int', fk: true },
      { name: 'placed_at', type: 'date' },
    ] },
  ],
  relationships: [{ from: 'CUSTOMER.id', to: 'ORDER.customer_id', label: 'places', cardinality: 'one-to-many' }],
});

export function ErDiagramView() {
  return (
    <div style={{ height: 500 }}>
      <GrafloriaDiagram spec={spec} />
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaDiagram } from '@grafloria/vue';
import { erDiagram } from '@grafloria/element';

const spec = erDiagram({
  entities: [
    { id: 'CUSTOMER', name: 'Customer', position: { x: 60, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'email', type: 'varchar' },
    ] },
    { id: 'ORDER', name: 'Order', position: { x: 390, y: 80 }, columns: [
      { name: 'id', type: 'int', pk: true },
      { name: 'customer_id', type: 'int', fk: true },
      { name: 'placed_at', type: 'date' },
    ] },
  ],
  relationships: [{ from: 'CUSTOMER.id', to: 'ORDER.customer_id', label: 'places', cardinality: 'one-to-many' }],
});
</script>

<template>
  <div style="height:500px">
    <GrafloriaDiagram :spec="spec" />
  </div>
</template>
```
:::

To connect tables without pinning to a specific row, use the entity IDs as relationship ends, such as `{ from: 'CUSTOMER', to: 'ORDER', label: 'places' }`. Use column-qualified ends for FK-to-PK relationships: the column name must exist on the named entity or the kit throws an error when it builds the spec. The default cardinality is `one-to-many`; choose another supported cardinality when the model calls for it.

[Open the live ER diagram demo](https://grafloria.com/demos/diagrams/table-er.html) to see the rendered table cards, key badges, and relationship markers.

For multiple foreign keys between the same tables, self-references, junction tables, and optional or mandatory cardinalities, see the [advanced ER demo](https://grafloria.com/demos/diagrams/er-advanced.html). Its column-level ends and cardinality choices use the same entity and relationship data model.

## Build a UML class diagram

Pass class definitions and relationships to [`umlDiagram()`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-diagram-kit-functions#umldiagram). Classes render in compartments for the class name, attributes, and methods. An abstract class name is italicized; relationship kinds supply their UML marker and line style. Multiplicity values add positioned labels to the live links.

This example renders an abstract `Animal`, its `Dog` subclass, and an `Owner` aggregation. The kit draws the three-compartment cards, inheritance triangle, and hollow diamond; `render()` runs the kit's finalization hook when it mounts a spec.

The React and Vue samples pass the spec to their framework binding. Angular mounts it with [`GrafloriaDiagramComponent`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-angular-components-classes#grafloriadiagramcomponent).

:::code-group
```js title="JavaScript"
import { render, umlDiagram } from '@grafloria/element';

const spec = umlDiagram({
  classes: [
    {
      id: 'Animal',
      abstract: true,
      position: { x: 180, y: 50 },
      attributes: ['# name: String'],
      methods: ['+ speak(): void'],
    },
    {
      id: 'Dog',
      position: { x: 180, y: 300 },
      attributes: ['+ breed: String'],
      methods: ['+ fetch(): void'],
    },
    {
      id: 'Owner',
      position: { x: 500, y: 300 },
      attributes: ['+ name: String'],
      methods: ['+ adopt(dog): void'],
    },
  ],
  relationships: [
    { from: 'Dog', to: 'Animal', kind: 'inheritance' },
    { from: 'Owner', to: 'Dog', kind: 'aggregation', label: 'owns', multiplicity: ['1', '0..*'] },
  ],
});

const host = document.createElement('div');
host.style.cssText = 'display:block; width:100%; height:500px';
document.body.append(host);
const instance = render(spec, host);
instance.fitView(40);
```
```ts title="Angular"
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { umlDiagram } from '@grafloria/element';

@Component({
  selector: 'app-uml-diagram',
  standalone: true,
  imports: [GrafloriaDiagramComponent],
  template: '<grafloria-diagram [spec]="spec" style="display:block; height:500px" />',
})
export class UmlDiagramComponent {
  spec = umlDiagram({
    classes: [
      { id: 'Animal', abstract: true, position: { x: 180, y: 50 }, attributes: ['# name: String'], methods: ['+ speak(): void'] },
      { id: 'Dog', position: { x: 180, y: 300 }, attributes: ['+ breed: String'], methods: ['+ fetch(): void'] },
      { id: 'Owner', position: { x: 500, y: 300 }, attributes: ['+ name: String'], methods: ['+ adopt(dog): void'] },
    ],
    relationships: [
      { from: 'Dog', to: 'Animal', kind: 'inheritance' },
      { from: 'Owner', to: 'Dog', kind: 'aggregation', label: 'owns', multiplicity: ['1', '0..*'] },
    ],
  });
}
```
```tsx title="Qwik"
import { component$ } from '@builder.io/qwik';
import { GrafloriaDiagram } from '@grafloria/qwik';
import type { RenderSpec } from '@grafloria/element';

const spec: RenderSpec = {
  nodes: [
    { id: 'Animal', position: { x: 180, y: 50 }, size: { width: 220, height: 90 }, label: 'Animal · abstract · # name: String' },
    { id: 'Dog', position: { x: 180, y: 300 }, size: { width: 220, height: 90 }, label: 'Dog · + breed: String' },
    { id: 'Owner', position: { x: 500, y: 300 }, size: { width: 220, height: 90 }, label: 'Owner · + name: String' },
  ],
  edges: [
    { id: 'inherits', source: 'Dog', target: 'Animal', label: 'inherits' },
    { id: 'owns', source: 'Owner', target: 'Dog', label: 'owns' },
  ],
};

export default component$(() => (
  <div style={{ height: '500px' }}>
    <GrafloriaDiagram spec={spec} />
  </div>
));
```
```tsx title="React"
import { GrafloriaDiagram } from '@grafloria/react';
import { umlDiagram } from '@grafloria/element';

const spec = umlDiagram({
  classes: [
    { id: 'Animal', abstract: true, position: { x: 180, y: 50 }, attributes: ['# name: String'], methods: ['+ speak(): void'] },
    { id: 'Dog', position: { x: 180, y: 300 }, attributes: ['+ breed: String'], methods: ['+ fetch(): void'] },
    { id: 'Owner', position: { x: 500, y: 300 }, attributes: ['+ name: String'], methods: ['+ adopt(dog): void'] },
  ],
  relationships: [
    { from: 'Dog', to: 'Animal', kind: 'inheritance' },
    { from: 'Owner', to: 'Dog', kind: 'aggregation', label: 'owns', multiplicity: ['1', '0..*'] },
  ],
});

export function UmlDiagramView() {
  return (
    <div style={{ height: 500 }}>
      <GrafloriaDiagram spec={spec} />
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaDiagram } from '@grafloria/vue';
import { umlDiagram } from '@grafloria/element';

const spec = umlDiagram({
  classes: [
    { id: 'Animal', abstract: true, position: { x: 180, y: 50 }, attributes: ['# name: String'], methods: ['+ speak(): void'] },
    { id: 'Dog', position: { x: 180, y: 300 }, attributes: ['+ breed: String'], methods: ['+ fetch(): void'] },
    { id: 'Owner', position: { x: 500, y: 300 }, attributes: ['+ name: String'], methods: ['+ adopt(dog): void'] },
  ],
  relationships: [
    { from: 'Dog', to: 'Animal', kind: 'inheritance' },
    { from: 'Owner', to: 'Dog', kind: 'aggregation', label: 'owns', multiplicity: ['1', '0..*'] },
  ],
});
</script>

<template>
  <div style="height:500px">
    <GrafloriaDiagram :spec="spec" />
  </div>
</template>
```
:::

Relationship kinds supported by the UML kit are `inheritance`, `realization`, `association`, `directed-association`, `aggregation`, `composition`, and `dependency`. The kit draws each kind's notation, including dashed lines for realization and dependency, diamonds at the source end for aggregation and composition, and an open target arrow for directed association. Provide `multiplicity: ['from', 'to']` to add a chip at each endpoint. The [class diagram demo](https://grafloria.com/demos/diagrams/class-uml.html) shows the basic class-card and relationship pattern.

The [UML relationships demo](https://grafloria.com/demos/diagrams/uml-relationships.html) shows all seven relationship kinds, multiplicities, stereotypes, and their rendered markers.

## Use node and edge data directly

Use the kits when you want their table or class cards and relationship notation. If your ER or UML model is already expressed as graph data, mount [`NodeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-nodespec#nodespec) nodes and [`EdgeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-edgespec#edgespec) edges directly. The resulting canvas uses regular diagram nodes and links; it does not add the kits' HTML cards, PK/FK badges, UML markers, or multiplicity chips.

For the JavaScript and framework mounting patterns, see [Build runnable workflows](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows). Here, ordinary graph nodes carry class members as label text, so the canvas shows the relationships without UML compartments, markers, or multiplicity chips.

:::code-group
```js title="JavaScript"
import { render } from '@grafloria/element';

const nodes = [
  { id: 'CUSTOMER', position: { x: 60, y: 80 }, size: { width: 240, height: 90 }, label: 'Customer · id: int (PK) · email: varchar' },
  { id: 'ORDER', position: { x: 390, y: 80 }, size: { width: 260, height: 90 }, label: 'Order · id: int (PK) · customer_id: int (FK)' },
];
const edges = [{ id: 'places', source: 'CUSTOMER', target: 'ORDER', label: 'places' }];
const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '500px';
const instance = render({ nodes, edges }, host);
instance.fitView(40);
```
```ts title="Angular"
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-entity-graph',
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `<grafloria-diagram-canvas [nodes]="nodes" [edges]="edges" style="display:block; height:500px" />`,
})
export class EntityGraphComponent {
  nodes: NodeSpec[] = [
    { id: 'CUSTOMER', position: { x: 60, y: 80 }, size: { width: 240, height: 90 }, label: 'Customer · id: int (PK) · email: varchar' },
    { id: 'ORDER', position: { x: 390, y: 80 }, size: { width: 260, height: 90 }, label: 'Order · id: int (PK) · customer_id: int (FK)' },
  ];
  edges: EdgeSpec[] = [{ id: 'places', source: 'CUSTOMER', target: 'ORDER', label: 'places' }];
}
```
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'CUSTOMER', position: { x: 60, y: 80 }, size: { width: 240, height: 90 }, label: 'Customer · id: int (PK) · email: varchar' },
  { id: 'ORDER', position: { x: 390, y: 80 }, size: { width: 260, height: 90 }, label: 'Order · id: int (PK) · customer_id: int (FK)' },
];
const edges: EdgeSpec[] = [{ id: 'places', source: 'CUSTOMER', target: 'ORDER', label: 'places' }];

export function EntityGraphView() {
  return (
    <div style={{ height: 500 }}>
      <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} fitView />
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'Animal', position: { x: 100, y: 60 }, size: { width: 260, height: 110 }, label: 'Animal\n# name: String\n+ speak(): void' },
  { id: 'Dog', position: { x: 100, y: 280 }, size: { width: 260, height: 110 }, label: 'Dog\n+ breed: String\n+ fetch(): void' },
];
const edges: EdgeSpec[] = [{ id: 'inherits', source: 'Dog', target: 'Animal', label: 'inherits' }];
</script>

<template>
  <div style="height:500px">
    <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" fit-view />
  </div>
</template>
```
:::

## Options that matter

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `relationships` | ER relationship records or UML relationship records | `[]` | Describes the connections between entity or class IDs. ER relationship ends can include a column name; UML relationships can set a kind and multiplicities. |
| `cardinality` (ER) | Named cardinality or `{ tail: string; head: string }` | `'one-to-many'` | Chooses ER endpoint markers. Named values include one-to-one, many-to-many, one-to-zero-or-many, and one-to-one-or-many. |
| `multiplicity` (UML) | `[string, string]` | None | Adds the from-end and to-end multiplicity labels. |
| `rowSelection` | `boolean` | `true` | Enables member/column row selection. Set `false` to opt out. |
| `editable` | `boolean` | `false` | Adds in-canvas rename, add, and delete controls for entity columns or class members. Edits are undoable. |
| `height` (entity or class) | `number` | Content-sized | Fixes a card's height; content exceeding it scrolls inside the card. |

## Pitfalls

- Column-qualified ER relationship ends must name an existing entity and column. A missing entity or field causes `erDiagram()` to throw while building its spec.
- The UML multiplicity labels need a live diagram model. Mount the spec; `render()` runs its `finalize` hook, which adds the labels after the links exist.
- Framework components need a parent with a nonzero height. The examples set a 500-pixel canvas height; adapt that value to your layout.
- The kit's UML relationship vocabulary does not include self-association. For the ER kit's supported self-reference pattern, see the [advanced ER demo](https://grafloria.com/demos/diagrams/er-advanced.html); a custom UML self-loop needs edge-level modeling beyond this kit.

## Related

- [Lay out diagrams automatically](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/lay-out-diagrams) — arrange diagram nodes after building their data.
- [Edit data models visually](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/edit-data-models-visually) — edit tables and fields on the canvas.
- [Command history](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/command-history) — understand undoable diagram edits.
