Skip to content
PtahPtah

Validate and format schema files

Two native verbs check schema files before anything connects to a database. ptah schema validate reports structural problems in a desired schema, once per target dialect. ptah schema fmt rewrites HCL schema files into HashiCorp HCL’s canonical layout, or reports the ones that are not in it.

Use them as a pre-commit hook and as the first job of a pipeline. Neither one takes a --db-url, so both run on a machine with no database and finish in the time a linter takes.

Command Reads Answers
ptah schema validate the supported sources listed below Is this schema structurally sound for the dialects I target?
ptah schema fmt .hcl files on disk Are these files in canonical HCL layout?
Source Selector Source-specific limitation
SQL file --schema-file schema.sql Validates the Ptah DDL parser subset for the selected dialect.
YAML file --schema-file schema.yaml Validates Ptah YAML schema objects and documented dialect overrides.
HCL file --schema-file schema.hcl Validates the Atlas-compatible HCL subset plus Ptah extensions.
DBML file --schema-file schema.dbml DBML cannot express every Ptah object.
Go annotations --root-dir ./models Reads the native Go annotation model from a Go source tree.
OCI artifact --schema-file oci://registry.example/app:v1 Validates the canonical HCL stored in the artifact.
Composite source repeat --schema-file with compatible inputs Uses the union of the selected formats; conflicting definitions fail.

The source-support manifest records focused command evidence separately from accepted transport. A supported input that lacks a focused command test is not a claim that its format can express every Ptah object.

Prerequisites: an installed ptah binary (Install Ptah) and a desired schema as local files.

Save this as broken.yaml. It declares one table with two problems: an index over a column the table does not have, and a foreign key to a table nothing declares.

tables:
orders:
columns:
id:
type: SERIAL
primary: true
customer_id:
type: INTEGER
not_null: true
foreign: customers(id)
indexes:
idx_orders_status:
fields: [status]

Save this beside it as schema.hcl. It declares a sound schema in a layout that is not canonical:

schema "public" {}
table "customers" {
schema = schema.public
column "id" {
type = int
}
column "email" {
type = varchar(255)
null = false
}
}

--dialect is required, because a declaration valid for one target can be invalid for another:

Terminal window
ptah schema validate --schema-file broken.yaml --dialect postgres

Expected output includes:

postgres: index "idx_orders_status": names column "status", which table "orders" does not declare
postgres: schema: invalid foreign key: field "customer_id" references unknown table "customers"
2 structural problems

The run exits 1. Every line names the dialect it was found under, then the object, then what is wrong with it. A schema with nothing wrong prints nothing and exits 0, so a hook can read the status alone.

Validate against every dialect you ship to

Section titled “Validate against every dialect you ship to”

--dialect is repeatable, and each value is checked separately with its own lines:

Terminal window
ptah schema validate --schema-file broken.yaml --dialect postgres --dialect mysql

Expected output includes:

postgres: index "idx_orders_status": names column "status", which table "orders" does not declare
postgres: schema: invalid foreign key: field "customer_id" references unknown table "customers"
mysql: index "idx_orders_status": names column "status", which table "orders" does not declare
mysql: schema: invalid foreign key: field "customer_id" references unknown table "customers"
4 structural problems

A problem that only one target has is reported under that target alone. A schema declaring a foreign key validates on postgres and fails on clickhouse with clickhouse: schema: clickhouse does not support foreign keys, because ClickHouse models none.

--root-dir reads Go annotations instead of a file, and --schema-file is repeatable. Naming both merges them into one composite desired schema. --server-version refines the capability set a dialect stands for, for example --dialect postgres --server-version 17.

ptah schema fmt walks the paths it is given, or the current directory when it is given none, and rewrites every .hcl file whose layout is not canonical. It prints the files it changed:

Terminal window
ptah schema fmt .

Expected output includes:

schema.hcl

Only files whose content changed are printed, so a run that changes nothing prints nothing. The rewrite is HashiCorp HCL’s own canonical layout — indentation, alignment and spacing — and it changes no value the file declares. schema.hcl becomes:

schema "public" {}
table "customers" {
schema = schema.public
column "id" {
type = int
}
column "email" {
type = varchar(255)
null = false
}
}

--check rewrites nothing. It prints the files that are not canonically formatted and refuses:

Terminal window
ptah schema fmt --check .

Expected output includes:

schema.hcl
error: 1 file(s) are not canonically formatted; run `ptah schema fmt` to rewrite them

That run exits 2, not 1: an unformatted file is reported as a command failure rather than as an expected negative result. A pipeline step that treats any non-zero status as a failure needs no special handling; one that distinguishes 1 from 2 has to know this.

Message on stderr Cause Exit
error: --dialect is required: validation is per target, and a declaration valid for one dialect can be invalid for another schema validate with no --dialect. 2
error: schema fmt nosuch.hcl: stat nosuch.hcl: no such file or directory schema fmt given a path that does not exist. 2
error: 1 file(s) are not canonically formatted; run \ptah schema fmt` to rewrite them` schema fmt --check found files to rewrite. 2

See Exit codes for the contract these follow.

  • ptah schema validate checks structure, not renderability. A declaration the renderer refuses for the same dialect can validate cleanly: a SERIAL column validates against clickhouse and exits 0, while ptah schema render --dialect clickhouse over the same source exits 2 with clickhouse: SERIAL has no auto-increment equivalent. Render as well as validate before trusting a target.
  • ptah schema fmt reads .hcl files only. A YAML, SQL or DBML schema file in the same directory is left alone and not reported, so a formatting gate over a mixed directory covers the HCL half of it.
  • ptah schema fmt takes no --config and no --env. It works on paths, not on the schema sources a project configuration names.

Run ptah schema validate --help and ptah schema fmt --help for the flag sets with their environment variables. Native commands places both verbs in the tree, and Exit codes carries a row for each.