Use the ER diagram kit when you want readers to rename tables and columns in place, add or remove columns, and see relationships stay attached to their fields as the schema changes.
The kit turns entity and relationship data into table cards with typed columns, key badges, and crow’s-foot relationships. Set editable: true to add inline editing controls; a TABLE.column relationship endpoint pins its edge to that column’s row. erDiagram builds the spec, and render or a framework diagram component mounts it.
Build an editable ER diagram
Use this pattern when your data is a relational schema and the diagram itself should be the editor. The example includes a field-level foreign-key relationship from ORDERS.customer_id to CUSTOMERS.id, plus a table-level relationship from PRODUCTS to ORDERS.
The mounted diagram shows three entity cards, their columns and key badges, and orthogonal crow’s-foot edges. Double-click a table header or column name to edit it; use the card’s add and delete controls to change columns. Each edit is an undoable step, and the field-level edge remains attached to its column when rows move.
Create the same kit spec in JavaScript, Angular, React, or Vue, then mount it with the framework’s diagram component. The JavaScript example mounts the spec directly. Each sample gives the canvas a real height.
React’s GrafloriaDiagram and Vue’s GrafloriaDiagram mount the kit spec directly. Angular’s diagram component takes the spec as an input.
jsimport { erDiagram, render } from '@grafloria/element';
const spec = erDiagram({
editable: true,
entities: [
{ id: 'PRODUCTS', name: 'Products', position: { x: 80, y: 96 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'sku', type: 'varchar' },
{ name: 'price', type: 'decimal' },
] },
{ id: 'CUSTOMERS', name: 'Customers', position: { x: 80, y: 360 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'name', type: 'varchar' },
{ name: 'email', type: 'varchar' },
] },
{ id: 'ORDERS', name: 'Orders', position: { x: 500, y: 150 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'status', type: 'varchar' },
{ name: 'customer_id', type: 'int', fk: true },
{ name: 'total', type: 'decimal' },
] },
],
relationships: [
{ from: 'ORDERS.customer_id', to: 'CUSTOMERS.id', id: 'fk_customer', fromSide: 'left', toSide: 'right' },
{ from: 'PRODUCTS', to: 'ORDERS', label: 'ordered as', fromSide: 'right', toSide: 'left' },
],
});
const host = document.createElement('div');
host.style.height = '640px';
document.body.append(host);
const instance = render(spec, host);
instance.fitView(40);
window.addEventListener('pagehide', () => instance.dispose(), { once: true });
tsimport { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { erDiagram } from '@grafloria/element';
const spec = erDiagram({
editable: true,
entities: [
{ id: 'PRODUCTS', name: 'Products', position: { x: 80, y: 96 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'sku', type: 'varchar' },
{ name: 'price', type: 'decimal' },
] },
{ id: 'CUSTOMERS', name: 'Customers', position: { x: 80, y: 360 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'name', type: 'varchar' },
{ name: 'email', type: 'varchar' },
] },
{ id: 'ORDERS', name: 'Orders', position: { x: 500, y: 150 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'status', type: 'varchar' },
{ name: 'customer_id', type: 'int', fk: true },
{ name: 'total', type: 'decimal' },
] },
],
relationships: [
{ from: 'ORDERS.customer_id', to: 'CUSTOMERS.id', id: 'fk_customer', fromSide: 'left', toSide: 'right' },
{ from: 'PRODUCTS', to: 'ORDERS', label: 'ordered as', fromSide: 'right', toSide: 'left' },
],
});
@Component({
selector: 'app-erd-editor',
standalone: true,
imports: [GrafloriaDiagramComponent],
template: '<grafloria-diagram [spec]="spec" style="display:block; height:640px" />',
})
export class ErdEditorComponent {
readonly spec = spec;
}
tsximport { GrafloriaDiagram } from '@grafloria/react';
import { erDiagram } from '@grafloria/element';
const spec = erDiagram({
editable: true,
entities: [
{ id: 'PRODUCTS', name: 'Products', position: { x: 80, y: 96 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'sku', type: 'varchar' },
{ name: 'price', type: 'decimal' },
] },
{ id: 'CUSTOMERS', name: 'Customers', position: { x: 80, y: 360 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'name', type: 'varchar' },
{ name: 'email', type: 'varchar' },
] },
{ id: 'ORDERS', name: 'Orders', position: { x: 460, y: 200 }, columns: [
{ name: 'id', type: 'int', pk: true },
{ name: 'status', type: 'varchar' },
{ name: 'customer_id', type: 'int', fk: true },
{ name: 'total', type: 'decimal' },
] },
],
relationships: [
{ from: 'ORDERS.customer_id', to: 'CUSTOMERS.id', id: 'fk_customer', fromSide: 'left', toSide: 'right' },
{ from: 'PRODUCTS', to: 'ORDERS', label: 'ordered as', fromSide: 'right', toSide: 'left' },
],
});
export default function ErdEditor() {
return (
<div style={{ height: '640px' }}>
<GrafloriaDiagram spec={spec} />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaDiagram } from '@grafloria/vue'; import { erDiagram } from '@grafloria/element'; const spec = erDiagram({ editable: true, entities: [ { id: 'PRODUCTS', name: 'Products', position: { x: 80, y: 96 }, columns: [ { name: 'id', type: 'int', pk: true }, { name: 'sku', type: 'varchar' }, { name: 'price', type: 'decimal' }, ] }, { id: 'CUSTOMERS', name: 'Customers', position: { x: 80, y: 360 }, columns: [ { name: 'id', type: 'int', pk: true }, { name: 'name', type: 'varchar' }, { name: 'email', type: 'varchar' }, ] }, { id: 'ORDERS', name: 'Orders', position: { x: 460, y: 200 }, columns: [ { name: 'id', type: 'int', pk: true }, { name: 'status', type: 'varchar' }, { name: 'customer_id', type: 'int', fk: true }, { name: 'total', type: 'decimal' }, ] }, ], relationships: [ { from: 'ORDERS.customer_id', to: 'CUSTOMERS.id', id: 'fk_customer', fromSide: 'left', toSide: 'right' }, { from: 'PRODUCTS', to: 'ORDERS', label: 'ordered as', fromSide: 'right', toSide: 'left' }, ], }); </script> <template> <div style="height:640px"> <GrafloriaDiagram :spec="spec" /> </div> </template>
The kit spec is regular diagram data plus a finalization step that installs the row interactions and, when editing is enabled, the inline editing behavior. Framework bindings mount that same spec; the JavaScript call returns a live instance, which the sample uses to fit the diagram and dispose it when its host page leaves.
For a model that is not a relational schema, use the generic canvas: React’s GrafloriaFlow, Vue’s GrafloriaFlow, or Angular’s DiagramCanvasComponent.
For a non-relational model, the generic canvas changes the task: readers move entity nodes and create or remove connections, rather than editing table and column names or rows. See the React quick start for typed flow data and framework binding patterns, and How Grafloria works for the shared model and Angular controlled-binding example.
Options that shape the schema
| Option | Type | Default | What it does |
|---|---|---|---|
entities | Array of entity specs | Required | Declares each table’s id, optional display name, columns, and optional position. Each column can specify a name, type, primary-key flag, and foreign-key flag. |
relationships | Array of relationship specs | Empty | Declares links between entity ids or between ENTITY.column endpoints. A column endpoint anchors the edge to that row. |
editable | boolean | false | Adds table rename, column rename, add-column, and delete-column controls. Every edit is one undoable step. |
rowSelection | boolean | true | Enables column-row selection and the kit’s row selection events. Set it to false to opt out. |
Pitfalls
- Use the exact entity id and column name in a field endpoint such as
ORDERS.customer_id. The kit throws if an entity or referenced column does not exist. editableis opt-in. Without it, the diagram remains the read-only ER card view.- Deleting a column removes a relationship attached to that column. Other field relationships stay connected to their columns as those rows shift.
- Give the canvas host a resolved height. The renderer fills its parent; a zero-height parent leaves no visible diagram.
See it running
Open the live ERD editor demo to try inline table and column edits, column insertion and deletion, row selection, and field-level relationships.
Related
- Build ER and UML diagrams for read-only ER and UML diagrams from data.
- Command history and edits for how diagram edits enter the undo history.
- Validate connections for rules that allow or veto connections.
Was this page helpful?