From am-plugin
Use this skill whenever the user wants to design, create, edit, or visualize a database schema using DBML (Database Markup Language). Triggers include: any mention of 'dbml', 'database schema', 'ERD', 'entity relationship diagram', 'table structure', 'data model', 'database design', 'db schema', or requests to model tables, relationships, foreign keys, or database architectures. Also use when converting SQL DDL to DBML, generating schemas from descriptions, or when the user has the DBML Live Preview VS Code extension installed. Do NOT use for writing raw SQL queries, Prisma schemas, actual database migrations, or ORM model code.
npx claudepluginhub astyanm/claude-am-plugin --plugin am-pluginThis skill uses the workspace's default tool permissions.
DBML (Database Markup Language) is a human-readable DSL for defining database structures. Files use the `.dbml` extension and can be visualized as ERD diagrams in VS Code via the **DBML Live Preview** extension (`nicolas-liger.dbml-viewer`).
Verifies tests pass on completed feature branch, presents options to merge locally, create GitHub PR, keep as-is or discard; executes choice and cleans up worktree.
Guides root cause investigation for bugs, test failures, unexpected behavior, performance issues, and build failures before proposing fixes.
Writes implementation plans from specs for multi-step tasks, mapping files and breaking into TDD bite-sized steps before coding.
Share bugs, ideas, or general feedback.
DBML (Database Markup Language) is a human-readable DSL for defining database structures. Files use the .dbml extension and can be visualized as ERD diagrams in VS Code via the DBML Live Preview extension (nicolas-liger.dbml-viewer).
DBML is database-agnostic: focus on structure, not SQL dialect specifics.
| Task | Approach |
|---|---|
| New schema from scratch | Write .dbml file following structure below |
| Convert SQL → DBML | Translate CREATE TABLE / ALTER TABLE into DBML syntax |
| Convert description → DBML | Extract entities, attributes, relationships from natural language |
| Edit existing schema | Parse .dbml file, modify tables/refs, write back |
Every .dbml file follows this general order:
// 1. Project definition (optional but recommended)
Project project_name {
database_type: 'PostgreSQL'
Note: 'Description of the project'
}
// 2. Enum definitions
Enum status_type {
active
inactive
archived
}
// 3. Table definitions
Table users {
id integer [pk, increment]
name varchar [not null]
status status_type [default: 'active']
created_at timestamp [default: `now()`]
}
// 4. Standalone relationships (alternative to inline refs)
Ref: posts.user_id > users.id
// 5. Table groups (optional)
TableGroup core {
users
posts
}
Always start with a Project block to give context:
Project ecommerce {
database_type: 'PostgreSQL' // or 'MySQL', 'SQLite', 'SQL Server'
Note: '''
# E-Commerce Database
Designed for a multi-tenant SaaS platform.
Last updated: 2025-01.
'''
}
Table schema_name.table_name as Alias [headercolor: #3498DB] {
column_name column_type [settings]
}
schema_name is optional (defaults to public)as Alias is optional — useful for shortening long names in Refsheadercolor changes the table header color in the ERD visualizationTable users {
id integer [pk]
Note: 'Stores all registered users'
}
Table example {
column_name column_type [setting1, setting2, ...]
}
Use any standard SQL type as a single word. If the type contains spaces, wrap it in double quotes.
| Category | Types |
|---|---|
| Integer | integer, int, smallint, bigint, serial, bigserial |
| Decimal | decimal(10,2), numeric, float, double, real, money |
| String | varchar, varchar(255), char(10), text, citext |
| Boolean | boolean, bool |
| Date/Time | date, time, timestamp, timestamptz, interval |
| Binary | bytea, blob, binary |
| JSON | json, jsonb |
| UUID | uuid |
| Array | "integer[]", "varchar[]" |
| Custom | "bigint unsigned", any enum name |
| Setting | Description | Example |
|---|---|---|
pk or primary key | Primary key | id integer [pk] |
increment | Auto-increment | id integer [pk, increment] |
not null | NOT NULL constraint | name varchar [not null] |
null | Explicitly nullable | bio text [null] |
unique | Unique constraint | email varchar [unique, not null] |
default: value | Default value | status varchar [default: 'active'] |
note: 'text' | Column note/description | role varchar [note: 'User role'] |
ref: > table.col | Inline relationship | user_id int [ref: > users.id] |
Table example {
// Number
rating integer [default: 10]
// String (single quotes)
source varchar [default: 'direct']
// Expression (backticks)
created_at timestamp [default: `now()`]
valid_until date [default: `now() + interval '30 days'`]
// Boolean / null
is_active boolean [default: true]
deleted_at timestamp [default: null]
}
| Symbol | Meaning | Example |
|---|---|---|
> | Many-to-one | posts.user_id > users.id |
< | One-to-many | users.id < posts.user_id |
- | One-to-one | users.id - profiles.user_id |
<> | Many-to-many | students.id <> courses.id |
1. Inline (in column settings):
Table posts {
id integer [pk]
user_id integer [ref: > users.id]
}
2. Standalone Ref:
Ref: posts.user_id > users.id
3. Standalone named Ref with block:
Ref user_posts {
posts.user_id > users.id
}
Ref: posts.user_id > users.id [delete: cascade, update: no action]
Available actions: cascade, restrict, set null, set default, no action
Ref: order_items.(order_id, product_id) > products.(order_id, id)
DBML supports direct many-to-many with <>, but for explicit junction tables:
Table students_courses {
student_id integer [ref: > students.id]
course_id integer [ref: > courses.id]
indexes {
(student_id, course_id) [pk]
}
}
Table bookings {
id integer [pk]
country varchar
booking_date date
created_at timestamp
indexes {
created_at [name: 'idx_created_at', note: 'Date index']
booking_date [type: hash]
(country, booking_date) [unique]
(id, country) [pk] // composite primary key
(`lower(country)`) // expression index
}
}
| Setting | Description |
|---|---|
name: 'index_name' | Custom index name |
unique | Unique index |
pk | Primary key (for composite PKs) |
type: btree or type: hash | Index type |
note: 'text' | Index description |
Enum order_status {
pending [note: 'Order just placed']
processing [note: 'Payment confirmed']
shipped
delivered
cancelled
}
Table orders {
id integer [pk]
status order_status [not null, default: 'pending']
}
For enums in a specific schema:
Enum v2.order_status {
pending
shipped
}
If enum values contain spaces or special characters, use double quotes:
Enum color {
"bright red"
"dark blue"
}
Groups are purely visual — they cluster tables together in the ERD:
TableGroup core_tables [color: #2196F3] {
users
profiles
roles
}
TableGroup content_tables {
posts
comments
tags
}
Define common columns once, inject into multiple tables:
TablePartial timestamps {
created_at timestamp [not null, default: `now()`]
updated_at timestamp [not null, default: `now()`]
}
TablePartial soft_delete {
deleted_at timestamp [null]
is_deleted boolean [default: false]
}
Table users {
id integer [pk, increment]
name varchar [not null]
~timestamps // injects created_at and updated_at
~soft_delete // injects deleted_at and is_deleted
}
Table posts {
id integer [pk, increment]
title varchar [not null]
~timestamps
}
Table users {
id integer [pk]
Note: '''
# Users Table
Contains all registered users.
- Soft-deleted users have `deleted_at` set
- Email must be verified before `is_active = true`
'''
}
Table orders {
total decimal(10,2) [note: 'Total in USD, before tax']
}
Free-floating notes not attached to any table:
Note single_line_note {
'This is a single line note'
}
Note detailed_note {
'''
# Architecture Decision
We use soft deletes across all tables.
'''
}
// Single-line comment
Table users { // inline comment
id integer [pk]
}
Note: DBML does NOT support multi-line /* */ comments.
Table tenants {
id uuid [pk, default: `gen_random_uuid()`]
name varchar [not null]
slug varchar [unique, not null]
plan varchar [default: 'free']
created_at timestamp [default: `now()`]
}
Table users {
id uuid [pk, default: `gen_random_uuid()`]
tenant_id uuid [not null, ref: > tenants.id]
email varchar [unique, not null]
role varchar [not null, default: 'member']
created_at timestamp [default: `now()`]
indexes {
(tenant_id, email) [unique]
}
}
Table users {
id integer [pk, increment]
email varchar [unique, not null]
password_hash varchar [not null]
}
Table roles {
id integer [pk, increment]
name varchar [unique, not null]
}
Table permissions {
id integer [pk, increment]
name varchar [unique, not null, note: 'e.g. users:read, posts:write']
}
Table user_roles {
user_id integer [ref: > users.id]
role_id integer [ref: > roles.id]
indexes {
(user_id, role_id) [pk]
}
}
Table role_permissions {
role_id integer [ref: > roles.id]
permission_id integer [ref: > permissions.id]
indexes {
(role_id, permission_id) [pk]
}
}
Enum order_status {
pending
paid
shipped
delivered
refunded
}
Table products {
id integer [pk, increment]
name varchar [not null]
price decimal(10,2) [not null]
stock integer [not null, default: 0]
}
Table orders {
id integer [pk, increment]
user_id integer [not null, ref: > users.id]
status order_status [not null, default: 'pending']
total decimal(10,2) [not null]
created_at timestamp [default: `now()`]
}
Table order_items {
id integer [pk, increment]
order_id integer [not null, ref: > orders.id]
product_id integer [not null, ref: > products.id]
quantity integer [not null, default: 1]
unit_price decimal(10,2) [not null]
}
snake_case, plural (users, order_items)snake_case, singular (user_id, created_at)snake_case (order_status, user_role)referenced_table_singular_id (user_id, order_id)table1_table2 alphabetically (courses_students)timestamps, soft_delete, audit_fields)not null on columns that should never be emptydefault values where appropriate[note: ...] on non-obvious columnsBefore finalizing a .dbml file, verify:
[pk] or composite via indexes)'...' or triple single quotes '''...'''/* */ multi-line comments (use // only)~partial_namedefault: 'value'default: \now()``> many-to-one, < one-to-many, - one-to-one, <> many-to-many).dbml extension (e.g., schema.dbml)