QUESTPIE
Build Your BackendBusiness Logic

Seeds

Database seeding for initial data, development fixtures, and test datasets.

Seeds populate your database with data using the full AppContext — collections, globals, services, and all infrastructure. Unlike migrations (which operate on raw SQL), seeds work through your application's typed API, ensuring data integrity and hook execution.

Defining a Seed

Place seed files in seeds/. Each file exports a seed definition using the seed() factory:

seeds/site-settings.seed.ts
import { seed } from "questpie";

export default seed({
  id: "siteSettings",
  description: "Default site settings",
  category: "required",
  async run({ globals, createContext, log }) {
    log("Seeding site settings...");

    const ctx = await createContext({ accessMode: "system", locale: "en" });
    await globals.site_settings.update(
      { shopName: "My Shop", tagline: "Welcome to our store" },
      ctx,
    );

    log("Site settings seeded");
  },
});

Seed Options

OptionTypeDescription
idstringUnique identifier for tracking execution
descriptionstringHuman-readable description (shown in CLI output)
categorySeedCategoryWhen this seed should run
run(ctx) => Promise<void>Seed logic — populate data
undo(ctx) => Promise<void>Optional: remove seeded data
dependsOnstring[]IDs of seeds that must run before this one

Categories

Categories control when seeds run:

CategoryPurposeRuns In
"required"System bootstrap (admin user, default roles, settings)Every environment
"dev"Development/demo data (sample content, test users)Development only
"test"Test fixtures (known datasets for integration tests)Test suites only

When you run bunx questpie seed:run, the "required" seeds always execute. Add --category dev to also run development seeds.

Seed Context

The run and undo handlers receive a SeedContext — the full AppContext plus seed-specific helpers:

PropertyDescription
collectionsTyped collection API (create, update, find, delete)
globalsTyped globals API
dbRaw database instance
queuePublish background jobs
emailSend emails
servicesUser-defined services
logSeed output logger (prints to CLI)
createContextCreate locale-specific contexts for multi-locale data

Multi-Locale Seeding

Use createContext to seed data in multiple locales:

seeds/site-settings.seed.ts
import { seed } from "questpie";

export default seed({
  id: "siteSettings",
  category: "required",
  async run({ globals, createContext, log }) {
    const ctxEn = await createContext({ accessMode: "system", locale: "en" });
    const ctxSk = await createContext({ accessMode: "system", locale: "sk" });

    await globals.site_settings.update(
      { shopName: "Sharp Cuts", tagline: "Precision grooming" },
      ctxEn,
    );
    await globals.site_settings.update(
      { tagline: "Precízna starostlivosť" },
      ctxSk,
    );

    log("Site settings seeded (EN + SK)");
  },
});

Idempotency

Seeds should be idempotent — safe to re-run without creating duplicates. Check for existing data before inserting:

seeds/demo-posts.seed.ts
import { seed } from "questpie";

export default seed({
  id: "demoPosts",
  description: "Demo blog posts",
  category: "dev",
  dependsOn: ["siteSettings"],
  async run({ collections, createContext, log }) {
    const ctx = await createContext({ accessMode: "system", locale: "en" });

    // Idempotency check
    const existing = await collections.posts.find(
      { where: { slug: { eq: "hello-world" } }, limit: 1 },
      ctx,
    );
    if (existing.totalDocs > 0) {
      log("Demo posts already exist, skipping");
      return;
    }

    await collections.posts.create(
      {
        title: "Hello World",
        slug: "hello-world",
        content: "Welcome to our blog!",
        isPublished: true,
      },
      ctx,
    );
    log("Demo posts created");
  },
});

Dependencies

Use dependsOn to declare execution order. The seed runner resolves dependencies via topological sort:

export default seed({
  id: "demoData",
  category: "dev",
  dependsOn: ["siteSettings"], // Runs after siteSettings seed
  async run({ collections, createContext, log }) {
    // siteSettings is guaranteed to have run first
  },
});

Undo

Seeds can optionally define an undo function to remove seeded data:

export default seed({
  id: "demoData",
  category: "dev",
  async run({ collections, createContext, log }) {
    // ... create demo data
  },
  async undo({ collections, createContext, log }) {
    const ctx = await createContext({ accessMode: "system" });
    log("Cleaning demo data...");
    await collections.posts.delete({ where: {} }, ctx);
    await collections.services.delete({ where: {} }, ctx);
    log("Demo data cleaned");
  },
});

Running Seeds

CLI Commands

# Run all pending seeds (required category by default)
bunx questpie seed:run

# Run seeds of a specific category
bunx questpie seed:run --category dev

# Run a specific seed by ID
bunx questpie seed:run --only siteSettings

# Force re-run even if already executed
bunx questpie seed:run --force

# Dry-run: validate seeds without committing changes
bunx questpie seed:run --validate

Execution Tracking

Seeds are tracked in the database. Once a seed has been executed, it will not run again unless you use --force. This prevents duplicate data when re-running seeds.

The seed runner wraps each seed in a transaction. If a seed fails, its changes are rolled back and the seed is not marked as executed.

  • Jobs — Background tasks
  • Services — Reusable logic
  • CLI — All CLI commands
  • Codegen — How seeds are discovered

On this page