Start free
Andrew Hanna

Andrew Hanna

How to seed a Salesforce sandbox with realistic data

How to seed a Salesforce sandbox with realistic data

Short answer: seeding a Salesforce sandbox well is three jobs, not one. Pull a relationship-aware slice of production rather than a record count, load it parents-first with stable external IDs so the same set can be reloaded, and anonymise deterministically so the data still behaves like real data. Then version the seed definition next to your metadata, because the thing that kills seed sets is not the first load, it is the schema change three sprints later.

What is sandbox seeding?

Sandbox seeding is copying a subset of production records into a lower environment so the org has realistic data to develop and test against, with sensitive values masked before they land. It is a data problem, not a metadata problem: a sandbox refresh brings the configuration, and in most cases leaves you with an empty or unusable org.

Which sandboxes actually need seeding?

The ones that do not copy data for you. Per Salesforce's sandbox licences and storage limits: Developer (200 MB) and Developer Pro (1 GB) copy metadata only and refresh daily, so those are the ones you seed. Partial Copy (5 GB, refresh every 5 days) brings a template-selected sample and you fill the gaps. Full (production-sized, refresh every 29 days) brings everything, which is exactly why masking matters more there, not less. Scratch orgs always start empty, so seeding is mandatory if you develop on them.

How do you pick a relationship-aware slice?

The common failure is selecting by volume: take 500 Accounts, take 500 Contacts, hope they connect. They do not, and testers spend their week building the records they were promised.

Select by graph instead, starting from a small set of root records:

  1. Pick the roots deliberately. Twenty to fifty Accounts that between them cover your record types, currencies, countries, territories and edge cases. One giant customer, one brand-new one, one with a broken address.
  2. Walk down the children. Contacts, Opportunities, line items, Cases, custom objects, and the junction objects between them.
  3. Walk back up the lookups. If an Opportunity points at a Pricebook, a Campaign or a custom Contract, those parents come too, or the insert fails.
  4. Add the reference data separately. Products, price book entries and configuration records are a full copy of a small table, not a slice of a graph.

A few hundred well-connected records beat a hundred thousand disconnected ones for every purpose except performance testing.

In what order do the records have to load?

Strict dependency order, parents before children, and every object keyed on something stable.

  • Never carry Salesforce IDs across orgs. They do not survive. Put a text external ID field on each seeded object, populate it from the source record, and upsert on it. Now the load is idempotent: run it twice and you update rather than duplicate.
  • Two-pass the circular references. Self-lookups such as Account.ParentId, and pairs where each object points at the other, cannot be satisfied in one insert. Load the records with the lookup blank, then run a second pass that only sets the lookup.
  • Map ownership. Owner and any User lookup will not resolve: sandbox usernames are suffixed and your production users may not exist there. Remap them before load.
  • Junctions last. Load a junction object only after both of its masters exist.
  • Bypass automation, do not deactivate it. Triggers, flows, validation and duplicate rules all fire on a seed load. A bypass switch your automation checks beats deploying deactivations into an org.

How do you anonymise without making the data useless?

Masking that produces noise is as bad as no masking, because nobody trusts a test that failed on garbage. Match the technique to the field:

  • Deterministic pseudonyms for anything used in joins, matching or deduplication. The same input must always produce the same output, so one customer is one customer across every object.
  • Shuffle within the column where distribution matters more than the value, such as revenue bands or close dates you report on.
  • Redact outright for fields no test touches. Free-text notes, description fields and attachments are where PII actually hides.
  • Neutralise anything routable. Emails, phone numbers and external ids used by integrations should be rewritten to values that cannot reach a real person, and the sandbox's email deliverability setting checked before the first load.

Salesforce ships Anonymize for Salesforce natively, and Gearset, Odaseva and Flosum all sell seeding tooling with masking built in. Whatever you use, the rule set has to be signed off once, in writing, by whoever owns data protection.

What happens when the schema changes?

This is the part almost nobody covers, and it is why most seed sets are dead within a quarter. A seed definition is a fixed picture of your schema: field lists, picklist values, required fields, validation rules. Ship a new required field and every load after it fails.

Treat the seed set as source, not as saved config in someone's tool:

  • Version the seed definition in the repo, next to the metadata it depends on, so a schema change and its seed change land in the same pull request.
  • Derive columns from a describe call rather than pinning a manual field list, so new fields do not silently drop out.
  • Run the load in CI against a scratch org on any pull request that touches objects, fields or validation rules. A failed seed load is a legitimate build failure: it means you just shipped something that breaks data creation.
  • Re-derive after every refresh. Picklist values get renamed and record types get retired; a seed set that was correct in March is a queue of validation errors in June.

In Serpent, a data operation is a first-class pipeline action rather than a manual chore, which is what lets the seed run sit in the same pipeline as the deployment it supports.

Finally, tie the cadence to your releases: each iteration for development sandboxes, each test cycle for UAT, always after a refresh. Because the load is idempotent and version-controlled, that is a pipeline run rather than a project. More Salesforce DevOps guides.

FAQ

How much data should a seeded sandbox hold?

Enough to cover every record type, path and edge case, usually a few hundred connected records. Volume only matters for load testing.

Can I just use Data Loader?

Yes, for a small set. You will be responsible for load order, ID remapping and masking yourself, and for repeating all three by hand on every refresh.

Does a sandbox refresh keep my seeded data?

No. A refresh replaces the sandbox, so anything you seeded is gone. Plan to re-run the seed as a standard post-refresh step.

Related Articles

Curious about faster shipping before you dive in? Let's talk

Commitment free!