Salesforce-Data-TreecipeSalesforce-Data-Treecipe is a Visual Studio Code extension designed to streamline the process of generating production-like data during development in order to support building Quality in. This extension auto-generates a recipe yaml file based on the running, local project structure. Said differently, what is already in the "source" for the project. From the generated "Fake-Data Generating YAML Files", additional commands can be used following the recipe generation to build Collections API datasets that can be committed and reused as needed. Users have two choices of "Fake Data" implementations:
Table of Contents
Prerequisites for SnowfakeryIf using snowfakery as Faker service instead of faker-js."faker-js" can be natively installed with VS Code extensions and does not require machine setup steps. VS Code Extension Installation
"How To" YouTube Walkthroughs:Get started by walking through the below commandsNote: press
Note: Select Faker Implementation is also available from the Command Palette at any time to switch between the 1. Salesforce Treecipe: Initiate Configuration FileThis command initiates the creation of a configuration file that is required before using other features of the extension. The command creates a root directory folder called "treecipe" and within it a configuration file called "treecipe.config.json". This file is auto generated based on the field configurations detailed selection made when prompted "Select objects directory". The end result treecipe.config.json file is expected to look like the below:
How It Works:
Once the configuration file is generated, you can begin using the Generate Treecipe command. Optional:
|
| Emitted line | Catches |
|---|---|
expectAtLeast('USA', new List<String>{ 'Ohio', 'Texas' }) |
A value removed from the combination |
expectNotAllowed('USA', new List<String>{ 'Ontario' }) |
A value that drifted into the combination |
expectNone('Antarctica') |
A controlling value that must unlock nothing gaining values |
The forbidden list is the complement: every value the dependent field declares that this controlling value does not unlock. That is deliberately weaker than expectExactly — a value an admin legitimately adds to the field after generation is tolerated, while a value moving into the wrong bucket still fails. Tightening a line to expectExactly is a deliberate edit — note that regenerating overwrites the file, so hand edits are lost.
Record type scoped specs
A record type assigns its own subset of picklist values to the controlling and dependent fields, so the combinations reachable through one record type are narrower than what the field itself declares. Where an object has a recordTypes/ directory, the per-object class gets those narrowed combinations too, alongside the field-level ones. The sample below shows the shape of what is emitted rather than metadata you should expect to see:
public static SDTPicklistDependencySpec specFor_Dependency_Example_c_Neighborhood_c_recordType_Cleveland_Only() {
return SDTPicklistDependencySpec.forRecordType('Dependency_Example__c', 'Neighborhood__c', 'Cleveland_Only')
.controlledBy('City__c')
.expectAtLeast('cle', new List<String>{ 'ohiocity', 'tremont' })
.expectNotAllowed('cle', new List<String>{ 'willowick' })
.expectUnavailable('eastlake');
}
- The controlling values are the field's, intersected with what the record type assigns to the controlling field; the unlocked values are intersected with what it assigns to the dependent field
- A controlling value the record type does assign but whose unlocked values it assigns none of becomes
expectNone— it must exist under that record type and unlock nothing - A controlling value the record type does not assign becomes
expectUnavailable— under that record type the value is absent rather than empty, andexpectNonewould demand it exist - A field the record type's XML never mentions is treated as unassigned for that record type, not as fully assigned: the combination is skipped and reported as a warning, and the field-level spec still covers the field
- The scoped specs are collected by
recordTypeSpecs()on the per-object class andSDTPLDSpecs.allRecordTypeScoped()on the aggregator
They are not asserted by SDTPLDSpecsTest, and that is deliberate. Apex Schema describe returns picklist values without any record type filtering, so the describe-backed source cannot answer a record-type-scoped spec — it rejects one outright rather than checking it against the whole field and reporting a scope it never verified as green. The scoped specs deploy with the rest of the contract and are ready for an ISDTPicklistDependencySource that can read record-type-filtered values; until then they are a captured contract, not a running one. An object with no recordTypes/ directory generates exactly what it did before.
Chained dependencies
Where a dependent picklist is itself the controlling field of another (Country__c → State__c → City__c), the generated spec for the lower link carries a dependsOn naming the spec above it:
public static SDTPicklistDependencySpec specFor_Chain_Example_c_City_c() {
return SDTPicklistDependencySpec.forField('Chain_Example__c', 'City__c')
.controlledBy('State__c')
.dependsOn(specFor_Chain_Example_c_State_c())
.expectAtLeast('Ohio', new List<String>{ 'Cleveland', 'Columbus' })
.expectNotAllowed('Ohio', new List<String>{ 'Austin', 'Toronto' });
}
When the upstream spec fails, the break is reported once where it actually is, and the downstream spec reports a single UPSTREAM_FAILURE naming the spec to fix first — rather than repeating the same describe mismatch for every dependent below it.
Notes:
- A field with a
controllingFieldbut novalueSettingsmarkup is reported as a warning and skipped; the rest of the run continues - A record type that assigns no values to the controlling or the dependent field of a dependency is reported the same way, and only that combination is skipped
- If no dependent picklists are found, an informational message is shown and no file is written
- If the generated classes already exist, you are prompted before they are overwritten
- A per-object class left over from an object that no longer declares a dependent picklist is removed and named in the summary, so the org stops asserting a contract your metadata no longer describes
- Upgrading from 2.12.x–2.14.x: the Apex classes were unprefixed then. If a
PicklistDependencyFrameworkfolder or anSFTreecipePicklistDependencySpecs.clsis still in your project, the command warns and names what to delete — locally and in any org you deployed them to. Nothing is deleted for you
The generated assertions read the org's real metadata. Schema describe is not isolated by @IsTest, so no test setup data and no SeeAllData are involved.
End-to-end in one command
After generating, the command offers to deploy and run the tests against an org right away. Accept it and you are prompted for the target org, the classes are deployed, the tests run, and the results land in the output channel and the treecipe directory — generation through to verified results without leaving the command.
The offer comes after generation rather than before, because generating is useful on its own: reviewing what changed, or working without an org to hand, needs the files and nothing else. Dismissing the prompt leaves you with a completed generation, not a cancelled command.
This path always deploys, unlike "Run Picklist Dependency Check" below, which deploys only when the test class is missing. The classes were just rewritten, so the org copy is stale by definition — a conditional deploy would run yesterday's contract against today's metadata.
Once generated, you can also run the check any time with "Run Picklist Dependency Check" below, and browse the structure it generated with the Explorer.
Once the classes are in your org
Everything above is the VS Code side. For the org side — what each deployed class is, how to read a generated spec against the Field Dependencies grid in Setup — including which argument is which, and how a record-type-scoped spec differs from the field-level one — how to run the tests from Setup or the Developer Console, how to trigger a failure on purpose to prove the gate works, and how to decide whether to fix the org or regenerate the specs — see the Picklist Dependency In-Org Guide. It is written for an admin or developer looking at SDTPLDSpecsTest in an org, and assumes nothing about this extension.
6. Salesforce Treecipe: Run Picklist Dependency Check
This command deploys and runs the generated picklist dependency tests against an org and reports the result in VS Code.
Prerequisite: "Generate Picklist Dependency Tests" must have been run, and the Salesforce CLI (sf) must be installed with at least one authorized org.
The command:
- Lists your authenticated orgs and prompts you to pick the target
- Checks whether the
SDTPicklistDependencyTestssuite in that org still containsSDTPLDSpecsTest, and offers to deploy if it does not — nothing is deployed without explicit confirmation. Membership is what is checked rather than the suite's existence: a suite whose member class was deleted still exists, and running it would pass having asserted nothing - Runs the suite with
sf apex run test --suite-names SDTPicklistDependencyTests - Writes a per-method report to the Picklist Dependency Check output channel and shows a pass/fail summary notification
- Saves the results into
treecipe/PicklistDependencyResults/check-<org>-<timestamp>/asresults.jsonandreport.md
A failing method names the object, field, controlling value, and the specific missing values.
Notes:
- If no orgs are authenticated, you are told how to authorize one rather than shown an empty picker
- Declining the deploy prompt exits cleanly and deploys nothing
- The output channel is cleared on each run, so what you see always belongs to the run that just finished
- Because the channel is cleared, every run is also written to disk under
treecipe/PicklistDependencyResults/— one timestamped folder per run, so results stay committable and diffable between runs. Passing runs are saved too, not only failures
7. Salesforce Treecipe: Open Picklist Dependency Explorer
This command opens a read-only visual view of your picklist dependency structure. It answers "which controlling value unlocks what" without you reading generated Apex or a markdown dump.
It is a picture of the structure, and only that. The panel reads no check results: no pass/fail badges, no status filter, no last-run banner, and no links into a generated .cls or a run's report.md. Whether your org still agrees with the structure is what "Run Picklist Dependency Check" answers, and it reports that in its own output channel and report.
Prerequisite: run Generate Picklist Dependency Tests first. The panel renders the spec manifest that command writes, so what you see is exactly what your generated tests assert. No org, no CLI and no check run are required — and if you have not generated yet, the panel offers a metadata preview instead (see below).
The command:
- Reads
treecipe/PicklistDependencySpecs/manifest.json— the machine-readable description of the specs that were generated — and builds the dependency structure from it: object → controlling field → controlling value → the values it unlocks, and the values it must not unlock. Your source metadata is not re-walked, so a panel row always corresponds to a spec method that exists - Renders chained dependencies as a connected graph — a field controlled by another dependent picklist is nested under it rather than repeated as a flat row
- Lists any field the generator skipped as its own row marked not covered, with the reason — rather than leaving it out, where it would be indistinguishable from a field with no dependency
- Clicking any combination reveals the generating field's source XML path, with a Reveal in Explorer action that opens the
.field-meta.xml, and a Copy reference action - Nests each record type's narrowed combinations under the field they narrow, collapsed until you open them — the same dependency as the record type actually exposes it
The panel names no Apex. Which class or spec method was generated is not a fact about a dependency, so it is not on screen — it is in manifest.json and in the generated .cls files themselves.
Finding your way around a large org:
A toolbar sits above the structure:
- Find object, field or controlling value matches on object, field, controlling field, record type, and the controlling value heading every combination row — everything the panel actually shows you, and nothing it does not. Searching for a field name reaches the object holding it, so you do not have to know which object that was; searching for
Canadareaches every object with a row that says whatCanadaunlocks — and when exactly one object matches, it opens by itself. The values a combination unlocks are not matched: a value is findable where it is a controller, which in a chain means one level down. A value only a dropped row carried is not matched either — the haystack is rebuilt from what survived the ceiling, so a match always has a row on screen to show for it - Contents lists every section and object the panel is showing, and scrolls to any of them
- Expand all / Collapse all, bounded at 25 visible objects — past that the panel asks for a narrower filter rather than freezing
Filtering only ever hides rows. Nothing about a row is recomputed, and nothing is inferred from a row being hidden.
Copy reference on any combination copies its stable key — Object__c.Field__c [RecordType] @ Controlling Value — and pasting that back into the find box reopens exactly that combination. It is stable across re-renders, so it is something you can put in a review comment or a ticket.
Notes:
- The panel is a VS Code webview — no local HTTP server, no open port, no extra runtime dependency. Its content security policy allows only the extension's own inline style and script, so it loads nothing from the network
- Built as you open it. An object's rows are built when you expand it, not when the panel loads, and the model itself is bounded: at most 250 objects, 25 dependent picklists per object, 200 combinations per field, 25 record type scopes per field, 200 declared values per field, and 20,000 rendered combinations in total. Anything the ceiling drops is counted and stated in a notice at the top. What survives a cap is whatever the manifest declares first — the same order on every axis, so the same manifest always truncates to the same rows and a row you cannot find was cut rather than moved. Dependency chains are capped whole rather than field by field, so an object can show more dependent picklists than the per-object limit suggests; the notice says so. The one exception keeps an object carrying a skipped field past the cap, since that is the only row nothing asserts
- Where a field declares more values than the panel renders, the "must not unlock" list is withheld rather than shortened — a complement drawn against a partial list would understate what the spec forbids, and the row says so
- It follows your active color theme, light, dark or high contrast
- No org, no CLI, no check run? None of them is involved. The panel renders from
manifest.jsonon disk and nothing else, so it works the same in a fresh clone as in a workspace that runs the check nightly - Not generated yet? You get a message naming the generate command, plus a "Preview from metadata (not generated)" action. The preview scans your source metadata exactly as previous versions did, and banners every row as read straight off the XML rather than from a generated manifest
- A corrupt
manifest.json? The parse failure is reported and the same preview is offered — never a blank panel - Metadata changed since you generated? The panel says so in a banner naming the generate command, and keeps showing what the generated Apex actually asserts. It never silently re-derives the structure from metadata your tests have not been regenerated against
- No dependent picklists at all? You get an empty state naming the objects directory that was scanned
- Record-type-scoped rows are the same dependency, narrowed. Each scope says so beside its own rows rather than relying on a note elsewhere in the panel, and a value the record type does not assign is shown as not available rather than as unlocking nothing, which is a different claim
- Scoped combinations are counted separately in the header (
N combination(s) + M record-type-scoped), because a record type narrows a field-level dependency rather than adding one of its own - Commit
manifest.jsonalongside your generated.clsfiles. It is the record of what was generated, and reviewing it as a diff shows dependency changes in the same commit as the Apex that asserts them
8. Salesforce Treecipe: Update Picklist Dependency Metadata from Specs
This command runs opposite to Generate. It reads your generated Apex specs — including whatever you edited into them — and reconciles your source metadata to match, so you can deploy the change and watch a failing dependency check go green.
Prerequisite: Generate Picklist Dependency Tests must have been run, so there are specs to read.
Why it exists. The check tells you Account.Region__c @ cle: missing [plant]. That failure is indexed by controlling value; your metadata is indexed by dependent value. So unlocking plant under cle means editing the plant block — nowhere near where the message points. That transpose is what this command does for you.
The command:
- Reads every generated
SDTPLDSpecs_<Object>.clsand parses the specs back out - Transposes each one into the
valueSettingsshape your metadata stores - Shows you every pair it would add or remove, in the words the failure used —
cle unlocks plant— before writing anything - Writes only if you approve; declining leaves every file untouched
- Offers to deploy the changed files to an org. Declining says explicitly that the changes are in your working tree and were not deployed
Notes:
- Your spec's silences are respected. A spec asserts what a controlling value must unlock and what it must not. Anything it names neither way — and any controlling value it never mentions — it makes no claim about, and the command leaves it alone.
expectNoneandexpectExactlyare the exception: both state their list completely, so anything else under that controlling value is removed - Your formatting survives. Only the
valueSettingsregion and, where needed,valueSetDefinitionare rewritten. The XML declaration, indentation, unrelated markup and trailing newline are preserved. Both<valueSettings>layouts are supported and whichever your file uses is kept - Reconciling twice changes nothing the second time, and after a writeback, running Generate produces byte-identical Apex — the two directions agree
- The controlling field is reconciled too. A
valueSettingsentry can only name a controlling value the controlling picklist actually offers, so when a spec asserts a value that field does not declare, it is added to that field's ownvalueSetDefinitionin the same run — otherwise the write would describe a combination nobody can reach, and you would find out on deploy - A global-value-set-backed field can be rewired, but adding a new value is refused with a message naming the set to add it to first. The shared
.globalValueSet-meta.xmlis never edited — its blast radius reaches every field pointing at it. The same rule applies to a controlling field - An orphaning cascade — removing a value that is itself another picklist's controlling field — names the downstream field and skips that field. Every unaffected field still writes
- A spec class that cannot be parsed aborts naming the file and writes nothing. An unparseable class is never treated as "this object has no dependencies"
- Record-type-scoped specs are not written back. A record type narrows what it exposes; applying that to
valueSettingswould assert the narrowing against every record type
9. Salesforce Treecipe: Open Recipe Cockpit (preview)
A panel for browsing a generated recipe: every object, every field, the field's type and the faker expression behind it, with a filter and one-click jumps into the recipe file.
Prerequisite: Generate Treecipe must have been run at least once. With no generated run the panel says so and names that command.
It is a preview. The first time you run it in a workspace, a warning asks you to opt in. The switch is salesforce-data-treecipe.recipeCockpitEnabled, written for this workspace only, and you can turn it off in Settings. What is built and what is still missing is tracked under the recipe-cockpit label. It can describe the recipe's objects in an org you choose; comparing the recipe with that org field by field is the next part and is not here yet.
How it works:
- It reads what Generate Treecipe already wrote. Every run leaves a
treecipeObjectsWrapper-<timestamp>.jsonnext to its recipe files undertreecipe/GeneratedRecipes/. The panel reads that file rather than re-parsing your metadata, and opens on the latest run - Switch runs from the selector next to the filter box. Each run is labelled with when it was generated (UTC) and which faker backend wrote it
- Filter narrows fields as you type. It matches field names, labels, types, controlling fields and faker expressions. Typing an object's name shows all of its fields. An object with nothing matching stays listed, collapsed and marked no matching fields, so a filter never looks like missing data
- Click an object or field name to open the recipe
.ymlat that exact line - Fields that only the recipe file carries are shown too. Standard-field mappings such as
Account.Name, and the record type line, are written straight into the recipe and are not in the wrapper file. They appear marked read from the recipe file - Describe in an org… lists every org the Salesforce CLI has authorized (
sf org login webadds one) and describes each object of the recipe on screen in the org you pick. A summary line and each object's header show how many fields the org has for it, or why it could not be described. Describes are cached for the VS Code session, so asking again for the same org does not call the API again
VIDEO WALKTHROUGHS
Initiate Treecipe Configuration with expected Objects directory
https://github.com/user-attachments/assets/f8401f28-a04c-4abc-a56f-c860cce96dee
Generate Treecipe based on treecipe.config.jcon (keep an eye out for OOTB fields and "REMOVE ME" lines)
https://github.com/user-attachments/assets/fd127b55-d434-4a73-9d65-cf4172fbce6f
Run Snowfakery by existing recipe yaml file
https://github.com/user-attachments/assets/d7dfcf70-70f8-4ce3-b254-280e2bbb0b7d
Insert Data Set by Directory
https://github.com/user-attachments/assets/a0491f86-9360-4450-afae-f71fe07dbc21
Troubleshooting, Exception Handling, and Reporting Bugs
See below for troubleshooting when specific commands are not working:
Salesforce Treecipe - Generate Treecipe - generateRecipeFromConfigurationDetail:
- Ensure "Initiate Configuration File" was successfully run
- Ensure path in treecipe.config.json uses forward-slashes
- Ensure "defaultFakerService" is set to "snowfakery"
Salesforce Treecipe: Initiate Configuration File - initiateTreecipeConfigurationSetup:
- Ensure expected project directory was selected
NOTE:
All commands are wrapped in try-catch and will prompt a "Report a Bug" dialog. This generates a GitHub Issue template with a stack trace.
Video Walkthrough:
https://github.com/user-attachments/assets/dff4a3cb-e244-4959-9dec-dcf094f713c2
Contributing
This project and codebase will be open-sourced shortly :)
License
This extension is licensed under the MIT License.
Install Snowfakery CLI
Snowfakery CLI Installation and Usage
Overview
Snowfakery is a tool for generating synthetic data. This document provides instructions for installing and using the Snowfakery CLI on any operating system.
Prerequisites
Python 3.8+ is required:
python --versionpip (Python package manager): https://pip.pypa.io/en/stable/installation/
Installation
pip install snowfakery
Verify the Installation
snowfakery --version
Usage (Without Salesforce Data Treecipe Extension)
snowfakery generate <path_to_your_snowfakery_recipe>
Official documentation: https://snowfakery.readthedocs.io/
Uninstalling Snowfakery
pip uninstall snowfakery