Seedfast

Seedfast

Seed Your Database

Before you begin

Before you seed your database for the first time, Seedfast needs a database it can reach, at least one table inside it, and an account. The installation guide covers Homebrew and npm. For the login itself, and for the API key a pipeline needs in its place, see authentication.

The commands and screens below come from real runs against a subscription billing schema, two schemas and eight tables, on PostgreSQL 16 with nothing in it but the DDL. Here are two of those eight tables, enough to read the transcripts against.

CREATE TABLE crm.customers (
  id          bigserial PRIMARY KEY,
  name        text NOT NULL UNIQUE,
  domain      text NOT NULL UNIQUE,
  segment     text NOT NULL CHECK (segment IN ('startup', 'smb', 'enterprise')),
  created_at  timestamptz NOT NULL
);

CREATE TABLE billing.invoices (
  id               bigserial PRIMARY KEY,
  subscription_id  bigint NOT NULL REFERENCES billing.subscriptions (id),
  number           text NOT NULL UNIQUE,
  status           text NOT NULL CHECK (status IN ('paid', 'overdue', 'disputed')),
  issued_at        date NOT NULL,
  due_at           date NOT NULL,
  total_amount     numeric(12, 2) NOT NULL CHECK (total_amount >= 0),
  currency         char(3) NOT NULL
);

Connect your database

seedfast connect

The prompt asks for a connection string in the ordinary postgresql://user:password@localhost:5432/mydb form and stores it for later runs. A script or a CI job has nobody to answer that prompt, so put the same string in the SEEDFAST_DSN environment variable instead, the way the CI/CD guide does.

Commands on this page assume a global install. Without one, run them through npx, npx seedfast connect and npx seedfast seed, which works the same on macOS, Windows and Linux.

Seed your database for the first time

Run seedfast seed on its own and Seedfast proposes a scope, then waits for you. With --scope "a sentence" on the command line, that sentence takes the place of your approval.

The proposal and how to change it

seedfast seed

The first plan for this schema came back about twenty seconds in. The screen below is that plan with the progress spinner cut.

Seeding scope

  • billing.invoices       50 records
  • billing.plans          15 records
  • billing.subscriptions  150 records
  • crm.addresses          50 records
  • crm.contacts           50 records
  • crm.customers          15 records
  • crm.support_tickets    50 records
  • crm.ticket_messages    50 records
  Total: 430 records

Do you agree with this scope?

> Yes
  No, seed 500 records per table

The highlighted line is the one Enter takes. Select the second line instead and a field opens where you type what you would rather have, which in this session was the sentence "I need at least 500 invoices, keep everything else the same", and the plan came back with invoices at 500 and the other seven lines standing exactly as they were. Ctrl+C at the menu cancels the run.

Your feedback: I need at least 500 invoices, keep everything else the same
Seeding scope

  • billing.invoices       500 records
  • billing.plans          15 records
  • billing.subscriptions  150 records
  • crm.addresses          50 records
  • crm.contacts           50 records
  • crm.customers          15 records
  • crm.support_tickets    50 records
  • crm.ticket_messages    50 records
  Total: 880 records

Do you agree with this scope?

> Yes
  No, social media with posts and followers

Approving the second plan finished the run roughly 45 seconds later, and the CLI printed a link to the dashboard. Afterwards a count per table came back with the eight numbers the approved plan listed, including the 500 invoices.

One sentence on the command line

seedfast seed --scope "seed 50 customers with contacts and invoices"

Nothing in this path stops to ask for approval. Under --output plain that run logged the following, trimmed to the plan, three of the five table lines and the closing summary.

[2026-09-06T17:15:29+02:00] INFO: Seeding started
[2026-09-06T17:16:03+02:00] INFO: Tables in scope: crm.customers, crm.contacts, billing.plans, billing.subscriptions, billing.invoices
[2026-09-06T17:16:03+02:00] INFO: Planned: 205 records across 5 tables
[2026-09-06T17:16:03+02:00] INFO: Auto-approving plan (scope provided)
[2026-09-06T17:16:30+02:00] INFO: Table crm.customers completed: 50 rows in 14.677s
[2026-09-06T17:16:44+02:00] INFO: Table crm.contacts completed: 50 rows in 13.619s
[2026-09-06T17:16:52+02:00] INFO: Table billing.invoices completed: 50 rows in 10.026s
[2026-09-06T17:16:52+02:00] INFO: Seeding completed: 5/5 tables succeeded, 205 rows, 84.60s

The plan pulled in billing.plans and billing.subscriptions as well, because an invoice cannot be written without them. The whole run took 85 seconds. When a script has to read the outcome rather than print it, --output json reports the same run as a single object carrying rows_planned and rows_actual per table.

Check the result

Whether the run did what the sentence asked shows up in a row count, one per table, trimmed here to the five tables that were in scope.

SELECT 'crm.customers' AS t, count(*) FROM crm.customers
UNION ALL SELECT 'crm.contacts', count(*) FROM crm.contacts
UNION ALL SELECT 'billing.plans', count(*) FROM billing.plans
UNION ALL SELECT 'billing.subscriptions', count(*) FROM billing.subscriptions
UNION ALL SELECT 'billing.invoices', count(*) FROM billing.invoices;

Against the database the run above wrote into, it returned five rows.

           t           | count
-----------------------+-------
 crm.customers         |    50
 crm.contacts          |    50
 billing.plans         |     5
 billing.subscriptions |    50
 billing.invoices      |    50

The stated number, 50 customers, held in every run. Counts the sentence does not fix are chosen by the run, so name the table and the number whenever either one matters. The three tables it never mentioned, crm.addresses, crm.support_tickets and crm.ticket_messages, stayed empty. Read scoping for what else the flag accepts.

Troubleshooting

Connection refused, or the connect step times out. Reach the database with psql and that same connection string first. Fix the string until psql connects; the CLI takes it unchanged.

A table you wanted is still empty. Since tables the sentence does not name stay out of the plan, which is what left three of these eight at zero rows, either name the table or start seedfast seed with no scope and add it at the menu.

The run sits there in CI. In CI the run stalls at that menu waiting for a keypress nobody is there to press. Adding --scope logs Auto-approving plan (scope provided) and the run continues.

For a next step, put a table and a number into the sentence and compare what lands against scope examples, which takes this same schema through progressively more detailed scopes.