Browse documentation

Connect a repository to a project and every push to the tracked branch applies the new .sql files in it. The migrations it creates are ordinary migration rows: they show up on Database → Migrations next to the ones authored in Studio, apply in the same transaction, and roll back the same way.

Note

The server takes SQL from a repository and nothing else. It does not clone, build or host application code.

One-time setup

  1. On GitHub: Settings → Developer settings → OAuth Apps → New OAuth App. Set the Authorization callback URL to your Studio's callback page:

    https://<your-domain>/studio/git/callback
  2. In Studio: Database → Migrations → Set up GitHub, and paste the client ID, the client secret and that same callback URL. They are stored through the settings service, so the secret is encrypted at rest — which requires CONFIG_ENCRYPTION_KEY to be set. Without it, saving is refused with a message that says so.

    The same three values can come from the environment instead:

    GITHUB_OAUTH_CLIENT_ID=...
    GITHUB_OAUTH_CLIENT_SECRET=...
    GITHUB_OAUTH_CALLBACK_URL=https://<your-domain>/studio/git/callback
  3. Connect GitHub, authorize, then Choose a repository and set the branch and the directory holding the migrations.

Connecting installs a push webhook on the repository, pointed at /api/git/webhook on the same origin as the callback URL. That URL has to be reachable from GitHub: on a machine GitHub cannot reach, the connection still works and Deploy now still deploys, but pushes will not.

The file convention

  • One migration is one .sql file directly inside the configured directory.

  • The file name without .sql is the migration's name and its ordering key. Use the usual timestamp prefix: 20260816120000_add_orders.sql.

  • Files are applied in plain lexicographic name order.

  • Everything after a -- @down line is the rollback SQL:

    CREATE TABLE orders (id uuid PRIMARY KEY);
     
    -- @down
    DROP TABLE orders;

    A file without the marker is forward-only. That is a fine choice; it simply cannot be rolled back from the Migrations page.

  • Anything that is not a .sql file is ignored, so a README.md or a _partials/ folder alongside the migrations is harmless.

What a deploy does

A file whose name already exists as a migration is skipped, so a deploy is idempotent: pushing twice does not apply anything twice, and re-running one after a failure resumes where it stopped.

The run stops at the first file that fails. A later migration nearly always depends on an earlier one, so continuing would apply it to a schema that never got the earlier change. The Migrations page shows which file stopped the run and why; fix it in the repository and push again, or press Deploy now.

Warning

Editing a file that has already been applied does nothing. Migrations are matched by name and the applied SQL is already recorded on the migration row — write a new file instead.

Security

  • The OAuth token and the webhook secret are AES-GCM ciphertext in platform.git_repo_links, and the table is revoked from anon, authenticated and service_role.
  • Every operator-facing endpoint is /api/admin/git/* behind the admin guard, and the Console maps the whole surface to the secrets.manage capability.
  • /api/git/webhook is the one route without a session, because GitHub has none. It is authenticated by the X-Hub-Signature-256 HMAC over the raw request body, checked before the payload is parsed.
  • Disconnecting deletes the webhook and the stored token. Migrations already applied stay applied.