betavoidbase is in public beta. It runs, the API is PocketBase's and is not moving, and the version is still 0.x for everything around that.Help us get it to 1.0

pb_migrations

The schema as code. Each file runs once, in filename order, and is recorded so it never runs again. This is how the collection you designed in the panel on your machine reaches production, and how the next person to clone the repository gets the same database you have.

A migration

A file exports nothing; it calls migrate with the change and the way back. Name files so they sort in the order they should run, which is why the convention is a timestamp prefix: 1725712800_posts.js.

/// <reference path="../pb_data/types.d.ts" />

migrate((app) => {
  const posts = new Collection({
    type: "base",
    name: "posts",
    listRule: "published = true",
    viewRule: "published = true",
    createRule: "@request.auth.id != ''",
    updateRule: "@request.auth.id = author.id",
    deleteRule: "@request.auth.id = author.id",
    fields: [
      { name: "title", type: "text", required: true, max: 200 },
      { name: "body", type: "editor" },
      { name: "published", type: "bool" },
      { name: "author", type: "relation", required: true, collectionId: app.findCollectionByNameOrId("users").id, maxSelect: 1 },
      { name: "created", type: "autodate", onCreate: true },
      { name: "updated", type: "autodate", onCreate: true, onUpdate: true },
    ],
  });
  app.save(posts);
}, (app) => {
  app.delete(app.findCollectionByNameOrId("posts"));   // the way back, for parity
});

Changing an existing collection is the same shape:

migrate((app) => {
  const posts = app.findCollectionByNameOrId("posts");
  posts.fields.addAt(3, new BoolField({ name: "featured" }));
  app.save(posts);
}, (app) => {
  const posts = app.findCollectionByNameOrId("posts");
  posts.fields.removeById(posts.fields.getByName("featured").id);
  app.save(posts);
});

When they run

Locally, on the next start. On Cloudflare, on the first request after a deploy, in file order, each recorded in the instance's own migrations table. A deploy therefore carries its schema with it, and there is nothing to remember to run.

The down function is kept for parity but there is no command to run it. Roll a mistake forward with another migration rather than back, which is what you would want in production anyway.

Getting one written for you

Designing a collection in the admin panel is faster than writing it out, so the usual loop is: design it in the panel, then export it. The panel's Collections screen has an export, and the JSON it produces can be applied to another instance directly:

voidbase import collections.json --url https://blog-api.example.workers.dev --admin you@example.com:your-password

That is the right tool for copying a schema between instances you already have. For a schema that travels with the repository and applies itself on deploy, write the migration file: it is the version-controlled answer, and it is the one that works for someone cloning the project tomorrow.

Where to read on

The format is PocketBase's, and its JS migrations page documents every collection option, field type and helper, all of which apply here unchanged.

Found something wrong on this page?

Fix it yourself. The link below opens this file in GitHub's editor and forks the repository for you if you need one, and your change becomes a pull request without leaving the browser.