Vibe Coding for Salesforce: Real Test Data Without Leaking It
The AI writes the scripts. Real data reaches my dev org. And the AI never sees a single customer’s name.
Every Salesforce developer knows this moment. You’ve built something you’re proud of: a new trigger, a flow, a screen your users have been asking for. You open your Developer sandbox to try it.
And there’s nothing there. No Accounts. No Contacts. Nothing that looks like the real business.
I’ve been in that spot more times than I can count, and it always plays out the same way. I create three test records by hand, everything passes, and the real problems wait quietly for UAT.
The usual answer is a paid seeding app. They work well, but they cost money, and not every team gets the budget for one.
So now I do it myself, for free. The scripts are boring glue code, which makes them a perfect job for vibe coding. The catch is keeping customer data away from the AI. Here’s how I do both.
Four words before we start
- Full Copy sandbox: production’s setup and all of its data.
- Partial Copy sandbox: setup plus a sample of data, picked by a template.
- Developer org: a Developer sandbox or scratch org. Setup, no data.
- Vibe coding: you describe what you want in plain English, and AI writes code you review.
Why not just refresh?
A Developer sandbox refresh brings your setup, not your records. A Full Copy refreshes about once a month. And you only need a few hundred connected records.
200 is the number I start with. It’s exactly one trigger batch, so it’s enough to expose a query or DML statement inside a loop.
Step 1: Tell the AI what you need
Name the orgs, the objects, how many records, and what to mask. Then add three rules:
Only run read queries against uat-full.
Never open or print the exported data files.
Show me every command before you run it.The middle line is the one people miss. If the assistant opens Contact.json to "check the export", real customer data lands in its context window. The AI can run commands once you approve them. It just never reads what they produce.
Step 2: Export a connected slice
Pick the safest source you can. A sandbox already masked with Salesforce Data Mask beats production. If you must use production, masking isn’t optional: privacy laws still apply in a dev org.
# Save the slice as a query file: 200 Accounts + children
cat > account-slice.soql <<'SOQL'
SELECT Name, Industry,
(SELECT FirstName, LastName, Email, Phone FROM Contacts),
(SELECT Name, StageName, CloseDate, Amount
FROM Opportunities)
FROM Account ORDER BY CreatedDate DESC LIMIT 200
SOQLsf data export tree \
--query account-slice.soql \
--plan \
--output-dir data \
--target-org uat-full--plan writes one JSON file per object, plus a plan file. Contacts point to their Account by a reference name, not a Salesforce Id, so the links survive the move.
Tree commands are built for sets under about 3,000 records. Need more? See SFDMU near the end.
Step 3: Mask the personal data
This runs on your machine, between export and import. You run it, not the AI:
// mask.js: fake but unique, so duplicate rules
// and unique fields don't reject the import
const fs = require('fs');
const masks = {
'data/Account.json': (r, i) => {
r.Name = `Account ${i + 1}`;
},
'data/Contact.json': (r, i) => {
r.FirstName = 'Test';
r.LastName = `Contact ${i + 1}`;
r.Email = `contact${i + 1}@example.invalid`;
r.Phone = '555-0100';
},
// Deal names often contain the customer's name
'data/Opportunity.json': (r, i) => {
r.Name = `Opportunity ${i + 1}`;
},
};
for (const [file, mask] of Object.entries(masks)) {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
data.records.forEach(mask);
fs.writeFileSync(file, JSON.stringify(data, null, 2));
}Add an entry for every other field your security team calls confidential. Description fields are the usual suspects.
Step 4: Import and count
# The plan loads Accounts first, then children
sf data import tree \
--plan data/Account-Contact-Opportunity-plan.json \
--target-org my-dev# Should match the number of Contacts you exported
sf data query --target-org my-dev \
--query "SELECT COUNT() FROM Contact"The plan file is named after your objects. Check the data folder.
Get Bharath Mulpuri’s stories in your inbox
Join Medium for free to get updates from this writer.
The import fires your triggers, flows and validation rules, just like a user saving records. That’s good for testing, but it can stop the load. Read the error before you let the AI “fix” it by switching automation off.
Three mistakes to catch before you press Enter
AI assistants tend to make the same few. Look for these in every command:
Id,OwnerIdorRecordTypeIdin the query. Those Ids don't exist in your dev org.- A missing required field, so the import fails on the first record.
- A missing or swapped
--target-org. That's how test data lands in your Full Copy.
The prompt you can copy
Paste this into any AI coding assistant:
Help me copy test data between Salesforce orgs with
the sf CLI. Source alias: uat-full. Target: my-dev.1. Write a SOQL file for 200 recent Accounts with
their Contacts and Opportunities. No Id, OwnerId
or RecordTypeId fields.
2. Export it with sf data export tree and --plan.
3. Write a Node script that replaces names, emails
and phones in the exported JSON with fake,
unique values. I will run it myself.
4. Import with sf data import tree --plan to my-dev.
5. Check imported counts match the exported counts.Rules: only run read queries against uat-full.
Never open, print or summarize the data files.
Show each command and wait for my OK first.
It works for these scenarios
- Reproducing a production bug.
- Testing a trigger or flow at real volume before UAT does.
- Demoing with believable records, not “Test Account 1”.
- Onboarding a new developer on day one.
Other ways to do this
This is the lightest option. Depending on your data size and budget, you might also look at:
- SFDMU, a free plugin for the sf CLI, for bigger or repeatable loads across many related objects.
- Salesforce Data Mask, to mask a Full or Partial Copy at the source.
- Synthetic data with Snowfakery, when no real data is allowed at all.
- Paid seeding tools like Gearset, Prodly or Odaseva, when you want a UI and scheduled refreshes.
An empty sandbox is where good work goes untested. Two hundred real, masked, connected records give it somewhere to land.
No paid app required.
If you’ve been stuck in an empty sandbox too, a clap helps other Salesforce developers find it. Want a follow-up on SFDMU? Tell me in the comments. Another one you might like: my post on building a reusable Apex retry framework.








