ORM and external loaders
When the desired schema lives in an ORM or framework rather than in Go annotations or a static file, Ptah runs an external program and reads its standard output as the desired schema. Any tool that prints a schema as SQL, HCL, or YAML becomes a Ptah source — including the Atlas ecosystem’s ORM provider loaders.
This is Ptah’s open, local, MIT equivalent of Atlas’s data "external_schema"
source and its ORM provider loaders. Provider compatibility depends on the
emitted schema syntax; the recipes below are verified end to end with Ptah.
The contract
Section titled “The contract”An ORM loader is any program that prints the complete desired schema as SQL, HCL, or YAML to stdout. Ptah runs it directly — never through a shell — parses the selected format, and uses the result wherever a desired schema is needed:
ptah schema render --schema-cmd "<loader>" --dialect postgresptah schema compare --schema-cmd "<loader>" --db-url "$DATABASE_URL"ptah schema drift --schema-cmd "<loader>" --db-url "$DATABASE_URL"ptah migrations plan --schema-cmd "<loader>" --db-url "$DATABASE_URL"Primary keys, foreign keys, unique constraints, and indexes in the emitted DDL are all captured, so the loaded schema diffs and migrates faithfully.
Run a loader with --schema-cmd
Section titled “Run a loader with --schema-cmd”--schema-cmd takes the program as a single string:
ptah schema render --schema-cmd "./scripts/export-schema" --dialect postgres- Format: stdout is parsed as SQL by default. Set
--schema-format hclor--schema-format yamlwhen the loader emits another supported source format;ymlis accepted as a YAML alias. HCL and YAML payloads can carryapi_name,openapi_name,graphql_name,proto_name,api_type, andapi_expose. SQL payloads cannot author those export-only attributes, so public names, types, and exposure are derived from the persistence schema. - No shell: the program runs with an explicit argument vector split on
whitespace, so pipes, globbing, and variable expansion are not available and
arguments cannot contain spaces. Wrap a more complex invocation in a small
script and point
--schema-cmdat that. - Lifecycle: execution is bounded by a timeout, and Ptah owns the loader’s process tree — a process group on Unix, a kill-on-close Job Object on Windows — so descendants are cleaned up on cancellation, timeout, and after a successful parent exit.
- Errors: if the program exits non-zero, its stderr is surfaced in the error as a bounded, secret-redacted, terminal-safe tail. Stdout is bounded, and empty or whitespace-only output is rejected so a broken provider cannot be interpreted as an intentionally empty desired schema.
Configure it in ptah.yaml
Section titled “Configure it in ptah.yaml”Instead of the flag, declare the loader once in an external_schema block.
Unlike --schema-cmd — a single string split on whitespace — the config form
takes an explicit argument list, so arguments may contain spaces:
external_schema: program: ["go", "run", "ariga.io/atlas-provider-gorm", "load", "--path", "./models"] format: sql # optional, defaults to sql working_dir: ./app # optional; defaults to the current directory env: ["APP_ENV=dev"] # optional extra KEY=VALUE entriesptah schema render, ptah schema compare, ptah schema drift,
ptah migrations plan, and ptah migrations generate read this block when
--schema-cmd is not passed (the flag always wins). Auto-discovered config is
not permission to execute repository-controlled code, so a config-sourced
loader also requires --allow-external-schema. Those commands read the other
settings they consume, such as url and schemas, and honor --env to
select an env block:
ptah schema drift \ --config ptah.yaml \ --allow-external-schemaSet --schema-cmd= explicitly to disable the configured source for one
invocation.
Constraints on the block:
external_schema.envcannot overridePATHorPWD. Put the executable path inprogramexplicitly and useworking_dirto select the command directory.- Relative
working_dirvalues must remain inside the process working directory after symlink resolution. Use an explicit absolute path for a deliberately external loader. - The block mirrors the desired-schema role of Atlas’s
data "external_schema". The same loader can also be declared directly inatlas.hclas adata "external_schema"source and selected as an env’s desired schema; see Atlas project config. All three spellings —--schema-cmd,ptah.yamlexternal_schema, and theatlas.hcldata source — share one execution path and one opt-in gate.
GORM (verified)
Section titled “GORM (verified)”The committed examples/orm-loaders/gorm
recipe contains a small GORM model module. It invokes the official standalone
atlas-provider-gorm command
through load-schema.sh at the pinned version v0.6.1. The script uses an
ephemeral copy of the model module, so the provider and its transitive
dependencies do not become part of the application module’s dependency graph.
Download the model dependency and render the schema through the checked-in
ptah.yaml:
cd examples/orm-loaders/gormgo mod downloadptah schema render \ --config ptah.yaml \ --allow-external-schema \ --dialect postgresThe generated schema includes the users and pets tables with their primary
keys, the unique index on email, and the pets → users foreign key.
SQLAlchemy (verified)
Section titled “SQLAlchemy (verified)”The committed examples/orm-loaders/sqlalchemy
recipe pins both the SQLAlchemy provider and SQLAlchemy itself. Its models
declare users and pets, a unique user email, and a cascading
pets.user_id → users.id foreign key.
Create the isolated Python environment and render it through the checked-in
ptah.yaml:
cd examples/orm-loaders/sqlalchemypython3.12 -m venv .venv.venv/bin/python -m pip install -r requirements.lock.txtptah schema render \ --config ptah.yaml \ --allow-external-schema \ --dialect postgresThe lock file records the complete environment verified with Python 3.12,
atlas-provider-sqlalchemy==0.4.1, and SQLAlchemy==2.0.50. The provider
emitted PostgreSQL DDL, and Ptah parsed and rendered both tables, both primary
keys, the unique email constraint, and the foreign key with
ON DELETE CASCADE.
Other ORMs
Section titled “Other ORMs”The Atlas ecosystem publishes schema loaders for many ORMs (GORM, Django,
SQLAlchemy, Sequelize, TypeORM, Hibernate, and more). Each one prints SQL DDL
to stdout, so compatible output can plug into --schema-cmd /
external_schema like the verified recipes above. Any tool that can emit a
complete SQL, HCL, or YAML schema can work too, including a hand-written
script that runs a framework’s schema dumper.
The Ptah side is always identical — you supply the loader command:
external_schema: program: ["<the loader command and its arguments>"]Consult the provider’s documentation for the exact loader command (the Atlas
provider packages are named atlas-provider-<orm>), then run it once by hand
to confirm it prints a complete supported schema before wiring it into Ptah.
GORM and SQLAlchemy are verified against Ptah here; treat other providers as
starting points and check their emitted schemas parse cleanly.
Compose with other sources
Section titled “Compose with other sources”An external command composes with Go roots and schema files, so you can merge an ORM export with a vendored HCL file:
ptah schema render \ --schema-cmd "./scripts/export-schema" \ --schema-file ./vendor/thirdparty.hcl \ --dialect postgresThe merge and conflict rules are on Composite desired schema.
Next steps
Section titled “Next steps”- Combining the loader with more sources? Composite desired schema.
- Drift-checking a live database against the ORM? Versioned migrations covers plan and apply.
- Declaring the loader per environment? Configuration.