Seeding
Everything a site needs to exist can be created from code: the custom-database schema, its records, static collections, your product catalogue, your blog posts and your coupons.
Two ways in, backed by the same functions:
- The CLI —
businessdash sync-data-model,sync-records,sync-content. Right at a terminal. @businessdash/sdk/seed— plain async functions. Right everywhere else: a Nuxt module, a SvelteKit hook, an Angular builder, a Vite plugin, a Nextinstrumentation.ts, or a CI step that already holds the credentials. A command you have to shell out to and screen-scrape is not an API.
What can be seeded
| Target | Function | Endpoint |
|---|---|---|
| Custom-database schema | pushDataModel | data-model/plan + draft-schema |
| Custom-database records | seedRecords | data-model/records |
| Static collections + rows | seedCollection, seedCollectionRows | collections, collections/{slug}/rows |
| Products, variants, cross-variants | seedProducts | storefront/products |
| Blog posts | seedBlogPosts | blog/posts |
| Coupons | seedCoupons | coupons |
A whole seed in one file
// businessdash.seed.ts
import { defineSeed } from '@businessdash/sdk/seed'
import { schema } from './businessdash.data-model.config'
export default defineSeed({
dataModel: schema,
records: {
projects: [{ title: 'Riverside retrofit', status: 'active' }],
},
products: [
{
name: 'Thermal survey',
description: 'Whole-property infrared survey.',
variants: [
{
name: 'Standard',
price: 249,
crossVariantTitles: [
{
title: 'Turnaround',
selectionMode: 'single',
options: [
{ value: '5 working days', price: 0 },
{ value: 'Next day', price: 95 },
],
},
],
},
],
},
],
posts: [
{ title: 'Why retrofit surveys pay for themselves', slug: 'retrofit-surveys', content: '…' },
],
coupons: [{ code: 'WELCOME10', discountType: 'percent', value: 10 }],
})
Run it from wherever your framework lets you run code at build time:
import { runSeed, seedTargetFromEnv } from '@businessdash/sdk/seed'
import seed from './businessdash.seed'
await runSeed(seedTargetFromEnv(), seed, { onProgress: console.log })
seedTargetFromEnv() reads the same three variables the CLI does —
BD_API_KEY, BD_SITE_ID and BD_PACKAGE_API_BASE_URL (or
NEXT_PUBLIC_BD_PACKAGE_API_BASE_URL) — so a project with a working CLI needs
no new configuration.
Or run the same file from a terminal:
npx tsx node_modules/@businessdash/sdk/dist/cli.js seed
npx tsx node_modules/@businessdash/sdk/dist/cli.js seed --dry-run
--dry-run reports what each step would do and uploads nothing.
Four behaviours worth knowing before you run it
These are deliberate, and each exists because the alternative fails badly in an unattended deploy.
It plans before it pushes, always
pushDataModel computes the migration plan even when it is going to push,
because a push whose consequences nobody computed is how a destructive
migration reaches a live database. dryRun: true computes the plan and uploads
nothing — the right thing to run on a pull request.
It refuses a destructive plan by default
runSeed throws when the plan contains destructive or exposure changes, and
names the change rather than just refusing. The usual caller here is an
automated deploy, and an automated deploy should not be the thing that drops a
customer's column. If you have already decided, pass
failOnDangerousPlan: false.
A push does not apply
Schemas land in the draft slot. Promotion stays a deliberate step in the dashboard, under Site Builder → Site Data → Database. Nothing in this module can promote a draft.
Re-running is safe
An existing coupon code or post slug is reported, not thrown — so a conflict on the third of ten does not abandon the remaining seven. A bad API key still throws, because a half-seeded store that reports success is worse than an obvious failure.
Products are not pushed to Stripe
seedProducts writes your own tables. Pushing the catalogue to your Stripe
account is a separate, explicit call:
await client.storefront.syncToStripe()
It is separate because it mints objects in an external account you are billed against, and that should never be a side effect of a seed script.
Products are also created unpublished unless you pass isLive: true, and
posts unpublished unless you pass publishNow: true. Seeding a site should not
push a half-configured catalogue to a live storefront, or drafts to every
follower's inbox.
Scopes
| Seeding | Scope |
|---|---|
| Schema, records | data_model:write |
| Static collections | collections:write |
| Products | storefront:write |
| Blog posts | blog:write |
| Coupons | storefront:write |
blog:write is not publishable — authoring a post is an operator action,
never something a browser token should be able to do.