Migration Files And Ownership
Coil migrations are intentionally split by owner. That is what keeps module upgrades, auth validation, and customer-app changes from collapsing into one undocumented SQL pile.
Use this page when you want to answer:
- who owns which migration step
- where those steps are declared
- why customer-app migrations can be manual even when module migrations are executable
- how to run the real commands and interpret the output
The Practical Answer
If your linked backend or customer app needs extra tables, projections, or backfills, the answer today is:
- yes, customer-owned migration work is first-class in the composed plan
- no, Coil does not yet auto-run arbitrary customer SQL files for you
That is why migration ownership is explicit in the docs. The platform records and surfaces the contract; the customer app still owns the concrete rollout step for purely customer-managed schema work.
The Ownership Model
Current owners are:
CoreModule(String)CustomerApp(String)AuthPackage(String)
The customer-app-facing summary categories are:
ModuleAuthPackageCustomerApp
What Each Owner Means
Module migrations
These are executable migration steps shipped by official or customer-linked modules.
The runtime composes them through module manifests and migration plans before migrate apply.
Auth package migrations
Auth packages contribute validation and compatibility checks to the migration/release story.
In the plan summary they appear as:
auth:<package>
That means "validate this auth package boundary before release", not "run arbitrary hidden SQL".
Customer app migrations
These are the steps the customer app itself owns and must explain explicitly.
That is why the demo customer binaries report manual_customer_migration_entries.
What The Platform CLI Shows You
The platform CLI gives you the composed migration plan across modules and auth.
Real command:
cargo run -p coil-cli -- migrate plan --config apps/shoppr/platform.dev.toml
Real output includes rows like:
owner: module:commerce
step: 001_catalog_products
order: 10
online_safe: true
description: create catalog products and variants tables
and:
owner: auth:shoppr-auth
step: version-check
order: 0
online_safe: true
description: validate auth package `shoppr-auth` schema, model, and capability bindings before release
That tells you two important things:
- executable module work is visible as planned steps
- auth-package validation is also part of the migration contract even when it is not raw SQL
Shoppr Example
Shoppr’s workspace binary is the clearest example because it reports both executable and manual customer-owned migration work.
Real command:
cd apps/shoppr
cargo run -p shoppr -- validate
Real output today:
Shoppr validation passed
app id: shoppr
route surfaces: 49
jobs: 16
migration contracts: 20
manual customer migrations: none
That is the honest current state of the demo:
- Shoppr has real composed migration contracts
- Shoppr currently has no manual customer migration entries
How To Declare A Customer Migration
The manifest shape is:
[[customer_migrations]]
id = "customer.content"
order = 90
description = "Creates customer app landing-page projections"
Each field means:
id- the stable operator-facing name for the migration entry
order- where it appears relative to module and auth-owned steps
description- the human explanation that shows up in planning and rollout output
Use a customer migration entry for:
- customer-owned projection tables
- backfills
- external-system cutover checkpoints
- manual schema work that belongs to the customer app rather than an official module
Real customer-binary behaviour:
validate(...)reportsmanual_customer_migration_entriesmigrate_apply(...)builds the runtime bootstrap and applies executable steps- customer-owned manual entries are derived from the composed manifest and still surfaced to the operator explicitly
That is the right migration story for a real product: executable where possible, explicit where human ownership is still required.
What The Customer Binary Adds
The customer binary owns the app-shaped migration workflow:
cd apps/shoppr
cargo run -p shoppr -- migrate apply --dry-run
If the app cannot build a valid runtime bootstrap, the command fails early. For example, the current
Shoppr dry-run path still requires runtime secrets such as OBJECT_STORE_URL to be present.
That is useful, not annoying. It stops the app from pretending it can apply migrations for a runtime it cannot actually boot.
Real Workflow For Customer-Owned Schema Changes
Use this sequence when your linked backend needs new schema or projection work:
- add a
[[customer_migrations]]entry toapp.toml - describe the change clearly in
description - run
coil migrate planto inspect the composed owner/order view - run
shoppr validateorgitly validate - execute the actual customer-owned SQL or data step in your deployment workflow
- re-run
release doctorandrelease plan
That is the current Coil workflow. The important thing is to be explicit about ownership rather than pretending the platform already has a hidden arbitrary-SQL runner.