How to write a pool seed script
This guide writes a seedScript — the hook org pool-prepare runs against each freshly created
pool org before marking it available. What a seed hook is for, and how a later claim uses what it
did, is in what a seed hook is for
and how a pool works.
Prerequisites
- A pool definition to attach the script to — see maintain the pool.
- A DevHub made pooling-ready (
npm run org:pool:activate -- --target-org <devhub>).
Create the module
-
Add a file under
config/scratch-org-setup/, named for the pool it seeds: -
Export a single async
seed(ctx)function.ctxgives you:targetOrg— the freshly created org's username.devHubConnection— an authenticated@salesforce/coreConnectionto the DevHub.targetOrgConnection— an authenticatedConnectionto the org being seeded.run(file, args)— spawnsnode <file> ...argsas a child process and returns the child. Route anything that pushes metadata, runs setup Apex or shells out to the CLI through it, never a direct import (why).
Pre-do claim-time work
Run the CLI's own initialization pipeline against the fresh org, so a later claim pays only for the difference:
// config/scratch-org-setup/ci-pool-seed.mjs
// Full scratch-org initialization via the CLI's reuse path — no data import, CI runs bring
// their own test data. Consumer-owned; invoked by `org pool-prepare` per pool-def `seedScript`.
export async function seed({ targetOrg, run }) {
await run("scripts/dia-scripts/index.mjs", [
"org",
"scratch-create",
"--reuse",
"--target-org",
targetOrg,
"--no-prompt",
"--do-not-open-org",
"--skip-data-import",
]);
}
Do org work claim-time init never does
Assign a permission set that a scratch-definition features entry provisioned — a plain API call,
so it runs in-process without run:
export async function seed({ targetOrgConnection }) {
const [user] = (
await targetOrgConnection.query(
`SELECT Id FROM User WHERE Username = '${targetOrgConnection.getUsername()}'`,
)
).records;
const [permSet] = (
await targetOrgConnection.query(
"SELECT Id FROM PermissionSet WHERE Name = 'FeatureProvisionedAccess'",
)
).records;
await targetOrgConnection.sobject("PermissionSetAssignment").create({
AssigneeId: user.Id,
PermissionSetId: permSet.Id,
});
}
Deploy metadata outside sfdx-project.json's packageDirectories — a CLI invocation, so through
run:
export async function seed({ targetOrg, run }) {
await run("config/scratch-org-setup/deploy-side-metadata.mjs", [
"--target-org",
targetOrg,
]);
}
Wire it into the pool definition
Point the pool's seedScript key at the new file (repo-root-relative):
Loading the definition fails fast if the file does not exist; every key is in the pool definition file reference.
Test it
Prepare a small pool and watch the seed run:
Each seeding's output is tagged with the org username, since parallel seedings interleave on stderr.
A throwing seed() fails only that org's preparation; succeedOnError in the pool definition
decides whether the run continues with the rest of the batch.