DEV Community

Cover image for Schema migrations in SurrealDB: A local dev workflow
Mark Gyles for SurrealDB

Posted on • Originally published at surrealdb.com

Schema migrations in SurrealDB: A local dev workflow

Author: Chiru Boggavarapu

This post walks through a proper migration workflow for local development using SurrealKit, an official tool from the SurrealDB team that handles schema sync, rollouts, seeding, and testing.

If you've spent any time working with SurrealDB, you know that with great flexibility comes the responsibility of managing your schema carefully. You're iterating quickly, adding tables, refining field definitions, and removing what you no longer need, and before long your local database can drift out of sync with what your code expects.


What is SurrealKit?

SurrealKit is a CLI tool that manages your SurrealDB schema through .surql files. You define your schema as files, and SurrealKit keeps your database in sync with them.

It has two main modes:

  • Sync: a fast, declarative approach for local and dev environments. Your files are the source of truth. Add something and it gets created, change it and it gets updated, remove it and it gets deleted.
  • Rollout: a more controlled migration path for shared or production databases, with planning, staged execution, and rollback support.

For local dev, you'll mostly live in sync. Rollouts come into play when you're pushing to shared environments.


Getting Started

Cargo is the Rust package manager. It downloads a Rust package's dependencies, compiles the packages, and makes distributable packages.

Install via Cargo

cargo install surrealkit
Enter fullscreen mode Exit fullscreen mode

Or grab a prebuilt binary from the GitHub releases page if you don't want to compile it.

Initialise a project

surrealkit init
Enter fullscreen mode Exit fullscreen mode

This creates a /database directory with the scaffolding you need. SurrealKit connects to your database using environment variables, so add these to your .env:

.env

SURREALDB_HOST=localhost:8000
SURREALDB_NAME=myapp
SURREALDB_NAMESPACE=development
SURREALDB_USER=root
SURREALDB_PASSWORD=secret
Enter fullscreen mode Exit fullscreen mode

It supports both DATABASE_HOST and PUBLIC_DATABASE_HOST variants, which is handy if you're working in a SvelteKit or similar setup where env vars are split by visibility.


The Local Dev Workflow

Writing Your Schema

Schema files live in database/schema/. Each file is a .surql file with your table definitions, indexes, access rules — whatever you'd normally write in SurrealQL.

The structure is entirely up to you. You might have one file per table, or group related things together. Either way, SurrealKit tracks what's in those files and reconciles it with what's actually in your database.

Syncing Changes

When you've made a change and want to apply it:

surrealkit sync
Enter fullscreen mode Exit fullscreen mode

That's it. SurrealKit diffs your schema files against the current database state and applies what's changed. If you deleted a table definition from your files, it removes it from the database too. Everything stays in sync.

For active development sessions, watch mode is where you'll spend most of your time:

surrealkit sync --watch
Enter fullscreen mode Exit fullscreen mode

This watches your database/schema/ directory and resyncs automatically whenever you save a file. It handles deletions too — if you remove a definition, it gets cleaned up. No manual intervention needed.


Moving to Shared Environments

The sync approach is great for local databases you own completely. But the moment you're working against a shared or staging database, you need more control. That's what rollouts are for.

Baseline First

Before your first rollout on an existing database:

surrealkit rollout baseline
Enter fullscreen mode Exit fullscreen mode

This records the current state so SurrealKit knows what it's working from.

Plan → Start → Complete

When you're ready to ship a schema change:

surrealkit rollout plan --name add_user_indexes
Enter fullscreen mode Exit fullscreen mode

This generates a TOML manifest in database/rollouts/ describing exactly what will change. You can review it, commit it, get it reviewed. Treat it like a pull request for your schema.

When you're ready to apply, you'll reference the manifest by its full filename. SurrealKit names these with a timestamp prefix: 20260302153045 translates to 2nd March 2026 at 15:30:45. This is the time the plan was generated, and it's there so rollouts always have a guaranteed sort order. If two people generate a plan on the same day, the timestamps keep them distinct and sequential. You'll see the full name in your database/rollouts/ directory after running plan.

When you're ready to apply:

surrealkit rollout start 20260302153045__add_user_indexes
Enter fullscreen mode Exit fullscreen mode

This runs the non-destructive part of the migration. Once your application is deployed and you're confident everything is working:

surrealkit rollout complete 20260302153045__add_user_indexes
Enter fullscreen mode Exit fullscreen mode

This finishes the migration, including any cleanup of legacy objects.

If something goes wrong mid-rollout:

surrealkit rollout rollback 20260302153045__add_user_indexes
Enter fullscreen mode Exit fullscreen mode

You can also validate a manifest without touching the database:

surrealkit rollout lint 20260302153045__add_user_indexes
Enter fullscreen mode Exit fullscreen mode

And check what state a rollout is in:

surrealkit rollout status
Enter fullscreen mode Exit fullscreen mode

SurrealKit tracks rollout state in the database itself, so it's always resumable.


Seeding

SurrealKit has a built-in seeding system for populating your local database with test data:

surrealkit seed
Enter fullscreen mode Exit fullscreen mode

Seed files live alongside your schema in the /database directory. Useful for onboarding new team members quickly — they clone the repo, init SurrealKit, run sync and seed, and they're good to go.


Testing

One of the more interesting features is the declarative testing framework. You write TOML test suites in database/tests/suites/ and run them with:

surrealkit test
Enter fullscreen mode Exit fullscreen mode

Each suite runs in an isolated ephemeral namespace, so tests can't interfere with each other or your actual data. You can test SQL assertions, permission rules, schema metadata, and even HTTP API endpoints.

A simple example, checking that a guest can't create an order:

name = "security_smoke"

[[cases]]
name = "guest_cannot_create_order"
kind = "sql_expect"
actor = "guest"
sql = "CREATE order CONTENT { total: 10 };"
allow = false
error_contains = "permission"
Enter fullscreen mode Exit fullscreen mode

You can also test permission matrices across a whole table at once, which is handy for verifying your access rules haven't regressed after a schema change.

For CI, add --json-out to get machine-readable output:

surrealkit test --json-out database/tests/report.json
Enter fullscreen mode Exit fullscreen mode

The command exits non-zero on any failure, so it integrates cleanly with GitHub Actions and other CI/CD systems seamlessly.


Putting It Together

Here's what a typical day looks like with this setup:

  1. Pull the latest changes
  2. Run surrealkit sync to update your local DB
  3. Edit schema files in database/schema/
  4. Use surrealkit sync --watch while you work
  5. When you're done, run surrealkit test to make sure nothing broke
  6. Commit your schema files alongside your code

For production changes, swap step 4 for surrealkit rollout plan and follow the plan → start → complete flow.

Would love to hear how you get on with SurrealKit. Jump into Discord and let us know how it goes or if you need a hand along the way.

Top comments (0)