Migrations create and modify the database tables PhoenixKit needs to function.
Usage
To use migrations in your application you'll need to generate an Ecto.Migration that wraps
calls to PhoenixKit.Migration:
mix ecto.gen.migration add_phoenix_kit
Open the generated migration in your editor and call the up and down functions on
PhoenixKit.Migration:
defmodule MyApp.Repo.Migrations.AddPhoenixKit do
use Ecto.Migration
def up, do: PhoenixKit.Migrations.up()
def down, do: PhoenixKit.Migrations.down()
endThis will run all of PhoenixKit's versioned migrations for your database.
Now, run the migration to create the table:
mix ecto.migrate
Migrations between versions are idempotent. As new versions are released, you may need to run additional migrations. To do this, generate a new migration:
mix ecto.gen.migration upgrade_phoenix_kit_to_v2
Open the generated migration in your editor and call the up and down functions on
PhoenixKit.Migration, passing a version number:
defmodule MyApp.Repo.Migrations.UpgradePhoenixKitToV2 do
use Ecto.Migration
def up, do: PhoenixKit.Migrations.up(version: 2)
def down, do: PhoenixKit.Migrations.down(version: 2)
endIsolation with Prefixes
PhoenixKit supports namespacing through PostgreSQL schemas, also called "prefixes" in Ecto. With prefixes your auth tables can reside outside of your primary schema (usually public) and you can have multiple separate auth systems.
To use a prefix you first have to specify it within your migration:
defmodule MyApp.Repo.Migrations.AddPrefixedPhoenixKitTables do
use Ecto.Migration
def up, do: PhoenixKit.Migrations.up(prefix: "auth")
def down, do: PhoenixKit.Migrations.down(prefix: "auth")
endThe migration will create the "auth" schema and all tables within that schema. With the database migrated you'll then specify the prefix in your configuration:
config :phoenix_kit,
prefix: "auth",
...That config entry is written automatically by
mix phoenix_kit.install --prefix "auth" and does two things:
Runtime queries target the schema. Every PhoenixKit Ecto schema compiles the prefix in (via
PhoenixKit.SchemaPrefix), so reads and writes hitauth.phoenix_kit_*directly — nosearch_pathsetup on the database role is needed for core. This is compile-time configuration: set it inconfig/config.exs(notruntime.exs); changing it recompiles the phoenix_kit dependency on the nextmix compile. A build compiled before the config change refuses to boot with a clear compile-env mismatch error (it never silently queriespublic) — anymix phx.server,ecto.migrate, or release build after the install triggers the recompile.Caveat: PhoenixKit feature modules (
phoenix_kit_catalogue,phoenix_kit_projects, …) define their own Ecto schemas, which only honor the prefix once that module also adoptsPhoenixKit.SchemaPrefix. Until the modules you use have it, a prefixed install running feature modules still needs the schema on the role'ssearch_path:ALTER ROLE my_app_role SET search_path = auth, public;Tooling finds the install.
mix phoenix_kit.update/status/gen.migrationresolve the prefix from config when--prefixisn't passed.
Oban needs the prefix too — its tables are created inside the same schema, so the host's Oban config must carry it (the installer adds this for new prefixed installs):
config :my_app, Oban,
prefix: "auth",
repo: MyApp.Repo,
...In some cases, for example if your "auth" schema already exists and your database user in production doesn't have permissions to create a new schema, trying to create the schema from the migration will result in an error. In such situations, it may be useful to inhibit the creation of the "auth" schema:
defmodule MyApp.Repo.Migrations.AddPrefixedPhoenixKitTables do
use Ecto.Migration
def up, do: PhoenixKit.Migrations.up(prefix: "auth", create_schema: false)
def down, do: PhoenixKit.Migrations.down(prefix: "auth")
endThe prefix must be a conventional lower-case identifier ([a-z_][a-z0-9_]*) —
it is interpolated into SQL, and anything else is rejected at the up/down
entry points.
One requirement for upgrading an existing prefixed install: the migrating
role needs CREATE on the install's schema, because newer versions ensure a
schema-local uuid_generate_v7() there (older releases created it wherever
search_path pointed, typically public). If the schema is DBA-owned and
the role only has USAGE, have the DBA grant CREATE for the migration or
pre-create the function in the schema.
Required Postgres extensions
The migration chain needs three extensions: citext (case-insensitive
emails), pgcrypto (UUIDv7 generation), and pg_trgm (trigram search).
When they are already installed the chain never issues CREATE EXTENSION
(so no database-level CREATE privilege is needed). When one is missing, the
migrating role must be allowed to create it — on locked-down databases have
a DBA pre-provision them instead:
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;Migrating Without Ecto
If your application uses something other than Ecto for migrations, be it an external system or another ORM, it may be helpful to create plain SQL migrations for PhoenixKit database schema changes.
The simplest mechanism for obtaining the SQL changes is to create the migration locally and run
mix ecto.migrate --log-migrations-sql. That will log all of the generated SQL, which you can
then paste into your migration system of choice.
Summary
Callbacks
Migrates storage down to the previous version.
Identifies the last migrated version.
Migrates storage up to the latest version.
Functions
Run the down changes for all migrations between the current version and the initial version.
Idempotently brings repo up to the latest PhoenixKit migration version.
Check the latest version the database is migrated to.
Run the up changes for all migrations between the initial version and the current version.
Callbacks
@callback down(Keyword.t()) :: :ok
Migrates storage down to the previous version.
@callback migrated_version(Keyword.t()) :: non_neg_integer()
Identifies the last migrated version.
@callback up(Keyword.t()) :: :ok
Migrates storage up to the latest version.
Functions
Run the down changes for all migrations between the current version and the initial version.
Example
Run all migrations from current version down to the first:
PhoenixKit.Migration.down()Run migrations down to and including a specified version:
PhoenixKit.Migration.down(version: 1)Run migrations in an alternate prefix:
PhoenixKit.Migration.down(prefix: "auth")
@spec ensure_current( Ecto.Repo.t(), keyword() ) :: :ok
Idempotently brings repo up to the latest PhoenixKit migration version.
Designed for test helpers and re-runnable boot paths where the database is long-lived but the calling process restarts on every invocation.
Why this exists
The natural-looking pattern
Ecto.Migrator.run(repo, [{0, PhoenixKit.Migration}], :up, all: true)is broken for re-runnable contexts: Ecto.Migrator records "version
0 applied" in schema_migrations after the first call and filters
{0, PhoenixKit.Migration} out of pending on every subsequent call.
PhoenixKit.Migration.up/1 is never re-invoked, so newly-shipped
Vxxx migrations don't get applied even though PhoenixKit's own marker
(the comment on the phoenix_kit table) is itself idempotent.
ensure_current/2 works around that by passing a fresh wall-clock
version (:os.system_time(:microsecond)) to Ecto.Migrator.up/4 on
every call. Ecto sees a "new" migration each time and invokes the
inner runner; PhoenixKit's marker then short-circuits if there's
nothing new to apply. The schema_migrations table accumulates one
row per call — cosmetic noise acceptable for the test-DB use case.
Microsecond precision keeps the collision and clock-skew windows
small enough that an NTP correction would have to rewind the clock
by µs at exactly the wrong moment to hide a newly-shipped migration.
For one-shot production migrations, prefer the documented
mix ecto.migrate path with a hand-rolled migration that calls
PhoenixKit.Migration.up/1 directly.
Options
Forwarded verbatim to Ecto.Migrator.up/4 and through to
PhoenixKit.Migration.up/1. Common values:
:log— Ecto-level migration log (:infodefault;falseto silence):prefix— runs PhoenixKit's tables under a non-default schema
Return contract
Returns :ok on success. Failures (advisory-lock contention,
migration crashes, connection errors) propagate as raises from
Ecto.Migrator.up/4; ensure_current/2 does not wrap them in
{:error, _}.
Example
# In test/test_helper.exs
PhoenixKit.Migration.ensure_current(MyApp.Test.Repo, log: false)
Check the latest version the database is migrated to.
Example
PhoenixKit.Migration.migrated_version()
Run the up changes for all migrations between the initial version and the current version.
Example
Run all migrations up to the current version:
PhoenixKit.Migration.up()Run migrations up to a specified version:
PhoenixKit.Migration.up(version: 2)Run migrations in an alternate prefix:
PhoenixKit.Migration.up(prefix: "auth")Run migrations in an alternate prefix but don't try to create the schema:
PhoenixKit.Migration.up(prefix: "auth", create_schema: false)