PgDesigner

PGD Format Specification

Version: 1 Target: PostgreSQL 18 File Extension: .pgd Encoding: UTF-8 Validation: pgd-format.xsd (see lfdfq pgd-format.xsd)

Overview

PGD is a git-friendly XML format for describing PostgreSQL database schemas. It provides structural modeling for all major PostgreSQL DDL constructs (tables, indexes, types, sequences, views, functions, triggers, policies, roles, extensions, grants, comments) and includes diagram layout metadata for visual schema design.

Design Principles

  • Git-friendly — single-line attributes, predictable element ordering, clean diffs
  • Complete PG18 coverage — all DDL constructs modeled structurally
  • Raw SQL only where necessary — function bodies, CHECK expressions, WHERE predicates, partition bounds
  • Layout separated from model — diagram metadata in <layouts>, can be split to a separate file
  • No IDs — objects referenced by name (like SQL itself), no artificial numeric identifiers

Document Structure

<?xml version="1.0" encoding="UTF-8"?>
<pgd version="1" pg-version="18" default-schema="public">
  <project>...</project>
  <database>...</database>
  <roles>...</roles>
  <tablespaces>...</tablespaces>
  <extensions>...</extensions>
  <types>...</types>
  <sequences>...</sequences>
  <schema name="public">...</schema>
  <schema name="app">...</schema>
  <views>...</views>
  <functions>...</functions>
  <triggers>...</triggers>
  <rules>...</rules>
  <policies>...</policies>
  <comments>...</comments>
  <grants>...</grants>
  <layouts>...</layouts>
</pgd>

All top-level children are optional. The order shown above is the canonical order.

Root Element: <pgd>

AttributeTypeRequiredDefaultDescription
versionintyesFormat version (currently 1)
pg-versionstringnoTarget PostgreSQL major version (1418)
default-schemastringnopublicDefault schema when schema is omitted

1. Project Metadata: <project>

<project name="myproject" description="My database schema">
  <settings>
    <naming convention="snake_case" tables="plural"></naming>
    <defaults nullable="true" on-delete="restrict" on-update="restrict"></defaults>
    <lint ignore-rules="W015,I009"></lint>
  </settings>
</project>

<project> attributes

AttributeTypeRequiredDescription
namestringyesProject name
descriptionstringnoProject description

<naming> attributes

AttributeTypeDefaultValues
conventionenumcamelCasecamelCase, snake_case, PascalCase
tablesenumplural, singular (empty = no check)

<defaults> attributes

AttributeTypeDefaultDescription
nullablebooleantrueDefault column nullability
on-deleteenumrestrictDefault FK ON DELETE action
on-updateenumrestrictDefault FK ON UPDATE action

<lint> attributes

AttributeTypeDescription
ignore-rulesstringComma-separated lint rule codes to suppress project-wide

2. Database: <database>

<database name="mydb" encoding="UTF8" collation="en_US.UTF-8"
          ctype="en_US.UTF-8" template="template0" owner="admin"></database>
AttributeTypeRequiredDescription
namestringyesDatabase name
encodingstringnoCharacter encoding (e.g. UTF8)
collationstringnoLC_COLLATE setting
ctypestringnoLC_CTYPE setting
icu-localestringnoICU collation (PG15+)
localestringnoLocale shorthand
templatestringnoTemplate database
tablespacestringnoDefault tablespace
ownerstringnoDatabase owner role

3. Roles: <roles>

<roles>
  <role name="app_user" login="true" inherit="true">
    <in-role name="app_readers"></in-role>
  </role>
  <role name="app_readers" login="false"></role>
</roles>

<role> attributes

AttributeTypeDefaultDescription
namestring(required)Role name
loginbooleanfalseCan log in
inheritbooleantrueInherits privileges of granted roles
createdbbooleanfalseCan create databases
createrolebooleanfalseCan create roles
superuserbooleanfalseSuperuser privileges
replicationbooleanfalseCan initiate streaming replication
bypassrlsbooleanfalseBypasses row-level security
connection-limitint-1Max connections (-1 = unlimited)
password-encryptedbooleantruePassword stored encrypted
valid-untilstringExpiration timestamp (ISO 8601)

<in-role> child element

AttributeTypeDescription
namestringParent role to grant membership in

4. Tablespaces: <tablespaces>

<tablespaces>
  <tablespace name="fast_ssd" location="/mnt/ssd/pg_data" owner="postgres"></tablespace>
</tablespaces>
AttributeTypeRequiredDescription
namestringyesTablespace name
locationstringyesFilesystem path
ownerstringnoOwner role

5. Extensions: <extensions>

<extensions>
  <extension name="uuid-ossp" schema="public"></extension>
  <extension name="pgcrypto"></extension>
</extensions>
AttributeTypeRequiredDescription
namestringyesExtension name
schemastringnoInstall into schema
versionstringnoSpecific version
cascadebooleannoAuto-install dependencies

6. Types: <types>

Contains four sub-sections: <enum>, <composite>, <domain>, <range>.

6.1 Enum

<enum name="status" schema="public">
  <label>active</label>
  <label>inactive</label>
  <label>deleted</label>
</enum>
AttributeTypeRequiredDescription
namestringyesType name
schemastringnoSchema

Child <label> elements contain enum value strings in order.

6.2 Composite

<composite name="address" schema="public">
  <field name="street" type="text"></field>
  <field name="city" type="varchar" length="100"></field>
  <field name="zip" type="varchar" length="10"></field>
</composite>

<field> attributes:

AttributeTypeRequiredDescription
namestringyesField name
typestringyesData type
lengthintnoType length (for varchar, char)
collationstringnoCollation

6.3 Domain

<domain name="email" schema="public" type="varchar" length="255" default="''">
  <not-null></not-null>
  <constraint name="email_check"><![CDATA[VALUE ~ '^.+@.+$']]></constraint>
</domain>
AttributeTypeRequiredDescription
namestringyesDomain name
schemastringnoSchema
typestringyesBase type
lengthintnoType length
precisionintnoNumeric precision
scaleintnoNumeric scale
collationstringnoCollation
defaultstringnoDefault expression (raw SQL)

Child <not-null/> — presence means NOT NULL. Child <constraint> — named CHECK constraint; text content is the expression (CDATA).

6.4 Range

<range name="float_range" subtype="float8"></range>
AttributeTypeRequiredDescription
namestringyesType name
schemastringnoSchema
subtypestringyesUnderlying element type
subtype-opclassstringnoOperator class
collationstringnoCollation
canonicalstringnoCanonical function
subtype-diffstringnoDifference function
multirange-type-namestringnoMultirange type name (PG14+)

7. Sequences: <sequences>

<sequences>
  <sequence name="order_seq" schema="public" as="bigint"
            start="1000" increment="1" cache="10"></sequence>
</sequences>
AttributeTypeDefaultDescription
namestring(required)Sequence name
schemastringSchema
asstringType: smallint, integer, bigint
startlong1Start value
incrementlong1Increment
minlongMinimum value
maxlongMaximum value
cachelong1Cache size
cyclebooleanfalseWrap around on limit
owned-bystringOwner column (table.column)

8. Schemas: <schema>

A schema contains tables and indexes. Multiple <schema> elements can appear at the top level.

<schema name="public">
  <table name="users">...</table>
  <table name="orders">...</table>
  <index name="idx_orders_user_id" table="orders">...</index>
</schema>
AttributeTypeRequiredDescription
namestringyesSchema name

Note: Indexes are stored at the schema level, not inside tables.


9. Tables: <table>

<table name="users" comment="Application users">
  <column name="id" type="bigint" nullable="false">
    <identity generated="always"></identity>
  </column>
  <column name="email" type="varchar" length="255" nullable="false"></column>
  <column name="name" type="text"></column>
  <column name="created_at" type="timestamptz" nullable="false" default="now()"></column>
  <pk name="pk_users">
    <column name="id"></column>
  </pk>
  <unique name="uq_users_email">
    <column name="email"></column>
  </unique>
  <check name="chk_email"><![CDATA[email <> '']]></check>
</table>

<table> attributes

AttributeTypeDefaultDescription
namestring(required)Table name
unloggedbooleanfalseUNLOGGED table
temporarybooleanfalseTEMPORARY table
on-commitstringpreserve-rows, delete-rows, drop
tablespacestringTablespace name
commentstringTable comment (shorthand for COMMENT ON TABLE)
row-level-securitybooleanfalseEnable RLS
force-row-level-securitybooleanfalseForce RLS for table owner
partition-ofstringParent table name (Variant A, deprecated)
inheritsstringComma-separated parent table names
usingstringTable access method (e.g. heap)
generatestringPGDesigner: false to skip DDL generation
lint-ignorestringPGDesigner: comma-separated lint rule codes

Child elements (in order)

  1. <column> — column definitions (must come first)
  2. <pk> — primary key (at most one)
  3. <fk> — foreign keys
  4. <unique> — unique constraints
  5. <check> — check constraints
  6. <exclude> — exclusion constraints
  7. <with> — storage parameters
  8. <partition-by> — partition key definition
  9. <partition-bound> — partition bound (Variant A, deprecated)
  10. <partition> — child partitions (Variant B)

10. Columns: <column>

<column name="price" type="numeric" precision="10" scale="2"
        nullable="false" default="0" comment="Product price in cents"></column>
AttributeTypeDefaultDescription
namestring(required)Column name
typestring(required)Data type (append [] for arrays: integer[])
lengthintLength for varchar(n), char(n), bit(n)
precisionintTotal digits for numeric(p,s)
scaleintDecimal digits for numeric(p,s)
nullablebooleantrueAllows NULL (false = NOT NULL)
defaultstringDefault expression (raw SQL)
collationstringCOLLATE collation_name
storageenumplain, external, extended, main, default
compressionenumlz4, pglz, default (PG14+)
commentstringColumn comment (shorthand for COMMENT ON COLUMN)

Supported Data Types

All PostgreSQL built-in types are supported as string values:

CategoryTypes
Integersmallint, integer, bigint
Serialsmallserial, serial, bigserial
Decimalnumeric, decimal, real, double precision, money
Charactervarchar, character varying, char, character, text
Binarybytea
Booleanboolean
Date/Timedate, time, timetz, timestamp, timestamptz, interval
UUIDuuid
JSONjson, jsonb
XMLxml
Networkinet, cidr, macaddr, macaddr8
Geometricpoint, line, lseg, box, path, polygon, circle
Text Searchtsvector, tsquery
Bit Stringbit, varbit, bit varying
Rangeint4range, int8range, numrange, tsrange, tstzrange, daterange
Multirangeint4multirange, int8multirange, nummultirange, tsmultirange, tstzmultirange, datemultirange
Otherpg_lsn, pg_snapshot, oid, regclass, regtype

Custom types (enums, composites, domains) are referenced by name. Schema-qualified types use dot notation: myschema.my_type.

10.1 Identity Column: <identity>

<column name="id" type="bigint" nullable="false">
  <identity generated="always">
    <sequence start="100" increment="1" cache="10"></sequence>
  </identity>
</column>

<identity> attributes:

AttributeTypeRequiredValues
generatedenumyesalways, by-default

<sequence> child (optional):

AttributeTypeDescription
startlongStart value
incrementlongIncrement
minlongMinimum value
maxlongMaximum value
cachelongCache size
cyclebooleanWrap around

10.2 Generated Column: <generated>

<column name="full_name" type="text">
  <generated expression="first_name || ' ' || last_name" stored="true"></generated>
</column>
AttributeTypeDefaultDescription
expressionstring(required)SQL expression
storedbooleantruetrue = STORED, false = VIRTUAL (PG18+)

11. Constraints

11.1 Primary Key: <pk>

<pk name="pk_users" without-overlaps="false">
  <column name="id"></column>
</pk>
AttributeTypeDefaultDescription
namestringConstraint name
without-overlapsbooleanfalseWITHOUT OVERLAPS (PG17+, temporal)
deferrablebooleanfalseDEFERRABLE
initiallyenumimmediate, deferred
enforcedbooleantrueNOT ENFORCED (PG18+)

11.2 Foreign Key: <fk>

<fk name="fk_orders_user" to-table="users" on-delete="cascade" on-update="no action">
  <column name="user_id" references="id"></column>
</fk>
AttributeTypeDefaultDescription
namestringConstraint name
to-tablestring(required)Referenced table (can be schema.table)
to-schemastringReferenced table schema (if different)
on-deleteenumrestrictrestrict, cascade, set-null, set-default, no action
on-updateenumrestrictSame as on-delete
deferrablebooleanfalseDEFERRABLE
initiallyenumimmediate, deferred
matchenumsimplesimple, full, partial
enforcedbooleantrueNOT ENFORCED (PG18+)
periodstringColumn name for temporal FK (PG17+)

<column> child attributes:

AttributeTypeDescription
namestringLocal column name
referencesstringReferenced column name

11.3 Unique: <unique>

<unique name="uq_users_email" nulls-distinct="false">
  <column name="email"></column>
</unique>
AttributeTypeDefaultDescription
namestringConstraint name
nulls-distinctbooleantruefalse = NULLS NOT DISTINCT (PG15+)
without-overlapsbooleanfalseWITHOUT OVERLAPS (PG17+)
deferrablebooleanfalseDEFERRABLE
initiallyenumimmediate, deferred
enforcedbooleantrueNOT ENFORCED (PG18+)

11.4 Check: <check>

<check name="chk_positive_amount" no-inherit="false"><![CDATA[amount > 0]]></check>
AttributeTypeDefaultDescription
namestringConstraint name
no-inheritbooleanfalseNO INHERIT
enforcedbooleantrueNOT ENFORCED (PG18+)

The element text content is the CHECK expression (use CDATA for special characters).

11.5 Exclude: <exclude>

<exclude name="excl_booking_overlap" using="gist">
  <element column="room_id" with="="></element>
  <element column="period" with="&amp;&amp;"></element>
</exclude>
AttributeTypeDefaultDescription
namestringConstraint name
usingstringgistIndex method
deferrablebooleanfalseDEFERRABLE
initiallyenumimmediate, deferred
enforcedbooleantrueNOT ENFORCED (PG18+)

<element> attributes:

AttributeTypeDescription
columnstringColumn name
expressionstringExpression (alternative to column)
withstringOperator (e.g. =, &&, <>)

12. Storage Parameters: <with>

<with>
  <param name="fillfactor" value="90"></param>
  <param name="autovacuum_enabled" value="false"></param>
</with>

Used in <table>, <partition>, <materialized-view>, and <index>.


13. Partitioning

PGD supports two variants for partition children. Variant B (nested) is the current standard.

13.1 Partition Key: <partition-by>

<partition-by type="range">
  <column name="created_at"></column>
</partition-by>
AttributeTypeRequiredValues
typeenumyesrange, list, hash

Children: <column> (by name) and/or <expression> (raw SQL).

13.2 Child Partitions: <partition> (Variant B)

<table name="events">
  <column name="id" type="bigint" nullable="false"></column>
  <column name="created_at" type="timestamptz" nullable="false"></column>
  <pk name="pk_events">
    <column name="id"></column>
    <column name="created_at"></column>
  </pk>
  <partition-by type="range">
    <column name="created_at"></column>
  </partition-by>
  <partition name="events_2024">
    <bound>FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')</bound>
  </partition>
  <partition name="events_2025">
    <bound>FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')</bound>
  </partition>
  <partition name="events_default">
    <bound>DEFAULT</bound>
  </partition>
</table>

<partition> attributes:

AttributeTypeDescription
namestringChild table name
tablespacestringTablespace override

<partition> children:

ElementDescription
<bound>Partition bound clause (raw SQL: FOR VALUES ... or DEFAULT)
<partition-by>Sub-partition key (for multi-level partitioning)
<partition>Sub-partition children (recursive)
<with>Storage parameters

13.3 Multi-level Partitioning

<partition name="events_2024">
  <bound>FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')</bound>
  <partition-by type="list">
    <column name="region"></column>
  </partition-by>
  <partition name="events_2024_us">
    <bound>FOR VALUES IN ('us')</bound>
  </partition>
  <partition name="events_2024_eu">
    <bound>FOR VALUES IN ('eu')</bound>
  </partition>
</partition>

13.4 Variant A (Deprecated)

Variant A uses partition-of attribute on separate top-level table elements. This is deprecated; use Variant B (nested <partition> elements) instead. MigratePartitions() converts Variant A to Variant B automatically.


14. Indexes: <index>

Indexes are stored at the schema level, not inside tables.

<index name="idx_orders_user_id" table="orders">
  <column name="user_id"></column>
</index>

<index name="idx_orders_email_lower" table="orders" unique="true" using="btree">
  <expression><![CDATA[lower(email)]]></expression>
  <where><![CDATA[deleted_at IS NULL]]></where>
</index>

<index name="idx_orders_covering" table="orders" unique="true">
  <column name="order_number"></column>
  <include>
    <column name="status"></column>
    <column name="total"></column>
  </include>
</index>

<index> attributes

AttributeTypeDefaultDescription
namestring(required)Index name
tablestring(required)Table name
schemastringTable schema (if different from parent <schema>)
uniquebooleanfalseUNIQUE index
usingenumbtreebtree, hash, gist, spgist, gin, brin
nulls-distinctbooleantruefalse = NULLS NOT DISTINCT (PG15+)
tablespacestringTablespace
concurrentlybooleanfalseCONCURRENTLY (used by ALTER generator)

Index <column> attributes

AttributeTypeDescription
namestringColumn name
orderenumasc, desc
nullsenumfirst, last
opclassstringOperator class (e.g. jsonb_path_ops, text_pattern_ops)

<expression> — expression index key (CDATA)

<where> — partial index predicate (CDATA)

<include> — covering index columns (PG11+)

Contains <column> elements (only name attribute used).


15. Views: <views>

15.1 View: <view>

<view name="v_active_users" schema="public">
  <query><![CDATA[SELECT id, name FROM users WHERE deleted_at IS NULL]]></query>
</view>
AttributeTypeDefaultDescription
namestring(required)View name
schemastringSchema
recursivebooleanfalseRECURSIVE view
security-barrierbooleanfalsesecurity_barrier option
security-invokerbooleanfalsesecurity_invoker option (PG15+)
check-optionstringlocal, cascaded

15.2 Materialized View: <materialized-view>

<materialized-view name="mv_daily_stats" schema="public" tablespace="fast_ssd">
  <query><![CDATA[SELECT date, count(*) FROM events GROUP BY date]]></query>
</materialized-view>
AttributeTypeDefaultDescription
namestring(required)View name
schemastringSchema
tablespacestringTablespace
usingstringAccess method
with-databooleantruefalse = WITH NO DATA

16. Functions: <functions>

<function name="get_user_count" schema="public" returns="integer"
          language="sql" volatility="stable" parallel="safe">
  <body><![CDATA[SELECT count(*)::integer FROM users;]]></body>
</function>

<function name="create_user" schema="public" returns="bigint"
          language="plpgsql" volatility="volatile" security="definer">
  <arg name="p_name" type="text"></arg>
  <arg name="p_email" type="text"></arg>
  <arg name="p_role" type="text" default="'user'"></arg>
  <body><![CDATA[
DECLARE
  v_id bigint;
BEGIN
  INSERT INTO users (name, email, role) VALUES (p_name, p_email, p_role) RETURNING id INTO v_id;
  RETURN v_id;
END;
  ]]></body>
</function>

<function> attributes

AttributeTypeDefaultDescription
namestring(required)Function name
schemastringSchema
kindstringfunctionfunction, procedure, aggregate
returnsstringReturn type (omit when using <returns-table>)
languagestring(required)plpgsql, sql, python, etc.
volatilityenumvolatileimmutable, stable, volatile
securityenuminvokerinvoker, definer
parallelenumunsafeunsafe, restricted, safe
strictbooleanfalseRETURNS NULL ON NULL INPUT
leakproofbooleanfalseLEAKPROOF
windowbooleanfalseWindow function
costintExecution cost estimate
rowsintEstimated rows returned

<arg> attributes

AttributeTypeDefaultDescription
namestringArgument name
typestring(required)Data type
modestringinin, out, inout, variadic
defaultstringDefault expression

<returns-table> (alternative to returns attribute)

<returns-table>
  <column name="id" type="bigint"></column>
  <column name="name" type="text"></column>
</returns-table>

Aggregate functions (kind="aggregate")

Additional attributes: sfunc, stype, finalfunc, combinefunc, initcond, sortop.


17. Triggers: <triggers>

<trigger name="trg_users_updated" schema="public" table="users"
         timing="before" events="update" for-each="row">
  <execute function="set_updated_at"></execute>
</trigger>

<trigger name="trg_audit_insert" schema="public" table="orders"
         timing="after" events="insert,update,delete" for-each="row">
  <referencing new-table="new_orders" old-table="old_orders"></referencing>
  <when><![CDATA[NEW.status <> OLD.status]]></when>
  <execute function="audit_changes"></execute>
</trigger>

<trigger> attributes

AttributeTypeDefaultDescription
namestring(required)Trigger name
schemastringSchema
tablestring(required)Table name
timingenum(required)before, after, instead-of
eventsstring(required)Comma-separated: insert, update, delete, truncate
for-eachenumrowrow, statement
constraintbooleanfalseCONSTRAINT trigger
deferrablebooleanfalseDEFERRABLE (constraint triggers)
initiallyenumimmediate, deferred
of-columnsstringUPDATE OF columns (comma-separated)

<referencing> (transition tables)

AttributeTypeDescription
old-tablestringOLD TABLE alias
new-tablestringNEW TABLE alias

<when> — trigger condition (CDATA)

<execute>

AttributeTypeRequiredDescription
functionstringyesFunction name to execute
argumentsstringnoArguments string

18. Policies: <policies> (Row-Level Security)

<policies>
  <policy name="users_own_data" schema="public" table="users"
          type="permissive" command="all" to="app_user">
    <using><![CDATA[id = current_user_id()]]></using>
    <with-check><![CDATA[id = current_user_id()]]></with-check>
  </policy>
</policies>
AttributeTypeDefaultDescription
namestring(required)Policy name
schemastringSchema
tablestring(required)Table name
typeenumpermissivepermissive, restrictive
commandenumallall, select, insert, update, delete
tostringpublicComma-separated role names

<using> — USING expression (CDATA)

<with-check> — WITH CHECK expression (CDATA)


19. Comments: <comments>

For comments on objects that don’t have a comment attribute shorthand (or for long-form comments on any object).

<comments>
  <comment on="schema" name="app">Application schema</comment>
  <comment on="function" schema="public" name="get_user_count">Returns total user count</comment>
  <comment on="extension" name="pgcrypto">Cryptographic functions</comment>
</comments>
AttributeTypeRequiredDescription
onenumyesObject type (see below)
schemastringnoSchema (where applicable)
tablestringnoTable (for column, trigger, policy, constraint)
namestringyesObject name

Supported on values: schema, table, column, index, sequence, view, materialized-view, function, trigger, policy, type, domain, extension, role, database, constraint.

The element text content is the comment string.

Note: Tables and columns support a comment attribute shorthand directly on <table> and <column> elements. The <comments> section is for other object types or when you prefer centralized comments.


20. Grants: <grants>

<grants>
  <grant on="schema" name="app" privileges="usage" to="app_user"></grant>
  <grant on="all-tables-in-schema" schema="app" privileges="select,insert,update,delete" to="app_user"></grant>
  <grant on="table" schema="public" name="users" privileges="select" to="readonly"></grant>
  <grant-role role="app_readers" to="app_user" with-inherit="true"></grant-role>
</grants>

<grant> attributes

AttributeTypeRequiredDescription
onenumyesTarget type (see below)
schemastringnoSchema
namestringnoObject name (omitted for all-*-in-schema)
privilegesstringyesComma-separated: select, insert, update, delete, truncate, references, trigger, maintain, usage, create, execute, connect, temporary, all
tostringyesRole name or public
with-grant-optionbooleannoWITH GRANT OPTION

Supported on values: schema, table, all-tables-in-schema, sequence, all-sequences-in-schema, function, all-functions-in-schema, type, database.

<grant-role> attributes

AttributeTypeDefaultDescription
rolestring(required)Role to grant
tostring(required)Grantee role
with-adminbooleanfalseWITH ADMIN OPTION
with-inheritbooleantrueWITH INHERIT (PG16+)
with-setbooleantrueWITH SET (PG16+)

21. Rules: <rules> (Deprecated)

<rules>
  <rule name="rule_name" schema="public" table="users" event="insert" instead="true">
    <![CDATA[INSERT INTO audit_log (table_name) VALUES ('users')]]>
  </rule>
</rules>
AttributeTypeRequiredDescription
namestringyesRule name
schemastringnoSchema
tablestringyesTable name
eventstringyesselect, insert, update, delete
insteadstringnotrue = INSTEAD rule
wherestringnoCondition

Note: Rules are deprecated in PostgreSQL in favor of triggers. This element is retained for reverse-engineering compatibility.


22. Layouts: <layouts> (PGDesigner Metadata)

Diagram layout metadata is separated from the data model. It stores table positions, visual groups, and notes for the ERD canvas.

<layouts>
  <layout name="Default Diagram" default="true">
    <entity schema="public" table="users" x="100" y="200" color="#C1D8EE"></entity>
    <entity schema="public" table="orders" x="400" y="200"></entity>
    <group name="Core Tables" color="#E8F5E9">
      <member schema="public" table="users"></member>
      <member schema="public" table="orders"></member>
    </group>
    <note x="50" y="50" w="200" h="80" color="#FFF9C4">Design notes here</note>
  </layout>
</layouts>

<layout> attributes

AttributeTypeDefaultDescription
namestring(required)Layout name
defaultbooleanfalsePrimary layout shown on open

<entity> attributes

AttributeTypeDefaultDescription
schemastringSchema name
tablestring(required)Table name
xint0X position (pixels)
yint0Y position (pixels)
colorstringBackground color (CSS hex: #RRGGBB)
widthint0Width override (0 = auto)
heightint0Height override (0 = auto)

<group> attributes

AttributeTypeDescription
namestringGroup name
colorstringBackground color

<member> attributes

AttributeTypeDescription
schemastringSchema name
tablestringTable name

<note> attributes

AttributeTypeDescription
xintX position
yintY position
wintWidth
hintHeight
colorstringBackground color

Text content is the note body.


Conventions

Boolean Attributes

Boolean values are represented as "true" / "false" strings. Omitting a boolean attribute uses its default (documented per element).

Raw SQL Content

Several elements contain raw SQL as text content, typically wrapped in CDATA sections:

  • <check> — CHECK expression
  • <expression> — index expression
  • <where> — partial index predicate, trigger WHEN
  • <bound> — partition bound clause
  • <query> — view/materialized view query
  • <body> — function body
  • <using>, <with-check> — RLS policy expressions
  • <constraint> (domain) — CHECK expression
  • <rule> — rule actions

Naming

Objects are referenced by name throughout the format (no numeric IDs). Cross-schema references use dot notation (schema.table) in the to-table and owned-by attributes.

Git-Friendly Design

  • Each attribute on a single line for clean diffs
  • Predictable element ordering (columns before constraints, schemas before tables)
  • No auto-generated IDs that change across saves
  • CDATA sections for SQL to avoid XML escaping noise

Empty Containers

Container elements (<roles>, <extensions>, <sequences>, etc.) may be present but empty, indicating the section exists but has no entries.


Complete Example

<?xml version="1.0" encoding="UTF-8"?>
<pgd version="1" pg-version="18" default-schema="public">
  <project name="example" description="Example database">
    <settings>
      <naming convention="snake_case" tables="plural"></naming>
      <defaults nullable="true" on-delete="restrict" on-update="restrict"></defaults>
    </settings>
  </project>
  <roles>
    <role name="app_user" login="true"></role>
  </roles>
  <tablespaces></tablespaces>
  <extensions>
    <extension name="uuid-ossp"></extension>
  </extensions>
  <types>
    <enum name="user_status">
      <label>active</label>
      <label>inactive</label>
    </enum>
  </types>
  <sequences>
    <sequence name="order_number_seq" start="1000" increment="1"></sequence>
  </sequences>
  <schema name="public">
    <table name="users" comment="Application users">
      <column name="id" type="bigint" nullable="false">
        <identity generated="always"></identity>
      </column>
      <column name="email" type="varchar" length="255" nullable="false"></column>
      <column name="name" type="text"></column>
      <column name="status" type="user_status" nullable="false" default="'active'"></column>
      <column name="created_at" type="timestamptz" nullable="false" default="now()"></column>
      <pk name="pk_users">
        <column name="id"></column>
      </pk>
      <unique name="uq_users_email">
        <column name="email"></column>
      </unique>
    </table>
    <table name="orders">
      <column name="id" type="bigint" nullable="false">
        <identity generated="always"></identity>
      </column>
      <column name="user_id" type="bigint" nullable="false"></column>
      <column name="order_number" type="bigint" nullable="false"
              default="nextval('order_number_seq')"></column>
      <column name="total" type="numeric" precision="12" scale="2"
              nullable="false" default="0"></column>
      <column name="created_at" type="timestamptz" nullable="false" default="now()"></column>
      <pk name="pk_orders">
        <column name="id"></column>
      </pk>
      <fk name="fk_orders_user" to-table="users" on-delete="cascade" on-update="no action">
        <column name="user_id" references="id"></column>
      </fk>
      <check name="chk_positive_total"><![CDATA[total >= 0]]></check>
    </table>
    <index name="idx_orders_user_id" table="orders">
      <column name="user_id"></column>
    </index>
    <index name="idx_orders_created_at" table="orders">
      <column name="created_at" order="desc"></column>
    </index>
  </schema>
  <views>
    <view name="v_active_users">
      <query><![CDATA[SELECT id, email, name FROM users WHERE status = 'active']]></query>
    </view>
  </views>
  <functions></functions>
  <triggers></triggers>
  <policies></policies>
  <comments>
    <comment on="schema" name="public">Default public schema</comment>
  </comments>
  <grants>
    <grant on="all-tables-in-schema" schema="public" privileges="select" to="app_user"></grant>
  </grants>
  <layouts>
    <layout name="Default Diagram" default="true">
      <entity table="users" x="100" y="100"></entity>
      <entity table="orders" x="400" y="100"></entity>
    </layout>
  </layouts>
</pgd>