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.