Convert PlantUML to PostgreSQL
Generate production-ready PostgreSQL SQL code from your PlantUML diagrams in seconds.
How the PlantUML to PostgreSQL conversion works
PlantUML entity diagrams carry more meaning natively than Mermaid: a leading * marks a field mandatory, which the generator turns straight into NOT NULL with no directive needed, and stereotypes like <<UNIQUE>> and <<FK>> annotate keys inline. Directives still exist for what PlantUML can't say — they just live in ' comments instead of %%.
PlantUML entity diagrams are the standard in many enterprise and Java-adjacent teams, with first-class tooling in IDEs and Confluence. Entities declare mandatory fields with a leading *, and stereotypes like <<FK>> mark key columns.
Entities are `entity Name { … }` blocks — a leading `*` marks mandatory (NOT NULL) fields, `<<FK>>` marks foreign keys, and relationships use the same cardinality arrows as Mermaid (`User ||--o{ Post`). Quoted display names (`entity "User Account" as ua`) become table comments.
Paste your PlantUML source into the editor above and the engine parses it into an
intermediate schema — tables, columns, relationships, indexes — then generates
PostgreSQL DDL from that model. Anything the diagram
syntax can't express (NOT NULL, defaults, enums, CHECK constraints, composite indexes)
you add with comment directives like ' ::NN, ::DEFAULT(now()) on the line
above a column. The PlantUML reference documents the
full vocabulary.
Worked example: a library loan schema
A complete PlantUML diagram and the exact PostgreSQL output the generator produces for it — different from the starter diagram in the editor above, so you can see a second real conversion end to end:
@startuml
entity Member {
* id : int <<PK>>
* email : string <<UNIQUE>>
name : string
joined_at : timestamp
}
entity Book {
* id : int <<PK>>
* isbn : string <<UNIQUE>>
title : string
' ::ENUM(available, loaned, lost)
status : string
}
entity Loan {
* id : int <<PK>>
member_id : int <<FK>>
book_id : int <<FK>>
' ::DEFAULT(now())
loaned_at : timestamp
}
Member ||--o{ Loan : borrows
Book ||--o{ Loan : "loaned as"
@enduml CREATE TYPE "book_status_enum" AS ENUM ('available', 'loaned', 'lost');
CREATE TABLE "member" (
"id" SERIAL PRIMARY KEY,
"email" TEXT NOT NULL UNIQUE,
"name" TEXT,
"joined_at" TIMESTAMPTZ
);
CREATE TABLE "book" (
"id" SERIAL PRIMARY KEY,
"isbn" TEXT NOT NULL UNIQUE,
"title" TEXT,
"status" "book_status_enum"
);
CREATE TABLE "loan" (
"id" SERIAL PRIMARY KEY,
"member_id" INTEGER,
"book_id" INTEGER,
"loaned_at" TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE "loan"
ADD CONSTRAINT "fk_loan_member_id"
FOREIGN KEY ("member_id") REFERENCES "member"("id");
ALTER TABLE "loan"
ADD CONSTRAINT "fk_loan_book_id"
FOREIGN KEY ("book_id") REFERENCES "book"("id"); Both email and isbn came out NOT NULL UNIQUE purely from the * and <<UNIQUE>> markers in the diagram — no directives involved, unlike the Mermaid syntax where NOT NULL always needs %% ::NN. The ' ::ENUM directive produced a native CREATE TYPE, ' ::DEFAULT(now()) landed on loaned_at, and both FKs emitted as named ALTER TABLE constraints.
What the generator emits for PostgreSQL
- CREATE TABLE statements with double-quoted identifiers
- SERIAL primary keys for single integer PK columns
- CREATE TYPE … AS ENUM for ::ENUM(...) columns
- ALTER TABLE … ADD CONSTRAINT foreign keys with ON DELETE / ON UPDATE actions
- CREATE INDEX / CREATE UNIQUE INDEX, including partial indexes with a WHERE clause
- COMMENT ON TABLE and COMMENT ON COLUMN for documentation carried over from the diagram
Type mapping: PlantUML to PostgreSQL
Diagram column types are logical — the generator maps each one to the idiomatic PostgreSQL type. These are the exact mappings the engine uses:
| Diagram type | PostgreSQL type | Notes |
|---|---|---|
int | INTEGER | SERIAL when it is the auto-increment primary key |
string | TEXT | use varchar(n) in the diagram for VARCHAR(n) |
text | TEXT | |
decimal | NUMERIC | decimal(10,2) passes precision through |
boolean | BOOLEAN | |
date | DATE | |
timestamp | TIMESTAMPTZ | timezone-aware by default |
uuid | UUID | native PostgreSQL UUID type |
bigint | BIGINT | |
blob | BYTEA |
Keys, relationships, and enums
A single `int id PK` column becomes `"id" SERIAL PRIMARY KEY`. Composite primary keys (e.g. on junction tables) emit a table-level `PRIMARY KEY (a, b)` clause without auto-increment.
Relationships emit `ALTER TABLE … ADD CONSTRAINT fk_<table>_<column> FOREIGN KEY … REFERENCES …` after all tables are created, so declaration order never matters. `::RELATIONSHIP[onDelete: CASCADE]` adds the corresponding ON DELETE action.
PostgreSQL gets a real enum: `::ENUM(active, inactive)` emits `CREATE TYPE status_enum AS ENUM ('active', 'inactive')` before the table, and the column uses that type. This gives you database-level validation without CHECK constraints.
Frequently asked questions
What does the leading * on a PlantUML field actually generate?
NOT NULL. PlantUML's mandatory-field marker is real schema semantics, so * email : string <<UNIQUE>> becomes "email" TEXT NOT NULL UNIQUE with no directive needed — one of the places PlantUML input is terser than Mermaid.
Where do directives go in PlantUML source?
In PlantUML comments: a line starting with ' (apostrophe) above the column, e.g. ' ::ENUM(available, loaned, lost). Same ::vocabulary as Mermaid's %% form; only the comment token differs, so the file still renders in PlantUML tooling.
How are enums handled in the generated PostgreSQL DDL?
With native CREATE TYPE … AS ENUM statements. The enum type is created before the table that uses it, and the column references the type — not a CHECK constraint or a plain VARCHAR.
Does the converter use SERIAL or IDENTITY columns?
SERIAL. A single integer primary key becomes SERIAL PRIMARY KEY (BIGSERIAL for bigint). Columns that are both PK and FK — the one-to-one child pattern — stay plain INTEGER so the value can mirror the parent key.