
Platform Custom Field Generate
- 2.3k installs
- 763 repo stars
- Updated July 24, 2026
- forcedotcom/sf-skills
Generates Salesforce custom field metadata on objects so a solo builder can add fields to the data model without hand-writing the field XML.
About
An official Salesforce skill that generates custom field metadata for objects - the XML defining a field's type, label, and properties. A solo builder reaches for it while shaping a Salesforce data model when they need to add fields to standard or custom objects and would rather have the agent produce correct field metadata than write it by hand.
- Generates Salesforce custom field metadata
- Extends objects with new fields
- Removes hand-editing of field XML
- Official forcedotcom skill set
Platform Custom Field Generate by the numbers
- 2,347 all-time installs (skills.sh)
- +611 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #220 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/forcedotcom/sf-skills --skill platform-custom-field-generateAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.3k |
|---|---|
| repo stars | ★ 763 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 24, 2026 |
| Repository | forcedotcom/sf-skills ↗ |
What it does
Generates Salesforce custom field metadata on objects so a solo builder can add fields to the data model without hand-writing the field XML.
Who is it for?
Adding fields to a Salesforce data model
Skip if: Non-Salesforce schema work
What you get
- custom field metadata
Files
Salesforce Custom Field Generator and Validator
Overview
Generates and validates Salesforce CustomField metadata XML, with special handling for the highest-failure-rate types — Roll-Up Summary and Master-Detail. The agent must verify the constraints below before outputting XML to prevent Metadata API deployment errors.
---
1. Universal Mandatory Attributes
Every generated field must include these tags:
| Attribute | Requirement | Notes |
|---|---|---|
<fullName> | Required | Field name only: derive from <label> — capitalize each word, replace spaces with _, append __c. Must start with a letter. E.g., label Total Contract Value → Total_Contract_Value__c. ⚠️ This rule is for the FIELD name. Picklist VALUE `<fullName>` is different — keep it exactly as the user spelled it, spaces and all, no `__c` (e.g. Closed Won, NOT Closed_Won). See `references/advanced-picklists.md` (ref §3). |
<label> | Required | The UI name (Title Case) |
<description> | Always include | Explain the business reason why this field exists. |
<inlineHelpText> | Always include | Actionable end-user guidance that adds value beyond the label (e.g., "Enter the value in USD including tax", not "The amount"). |
<description> and <inlineHelpText> are mandatory outputs even though the Metadata API does not enforce them — omitting them produces low-quality metadata.
File path (SFDX source format): save each field as force-app/main/default/objects/<Object>/fields/<FieldName>__c.field-meta.xml, where <Object> is the object's API name (Account, Opportunity, or a custom Inventory_Item__c). A correct XML at the wrong path is never seen by the Metadata API.
External ID Configuration
Trigger: If the user mentions "integration," "importing data," "external system ID," or "unique key from [System Name]," set <externalId>true</externalId>.
Applicable Types: Text, Number, Email
---
2. Precision, Scale, and Length Rules
To ensure deployment success, follow these mathematical constraints:
Precision vs. Scale Rules
precisionis the total digits;scaleis the decimal digits- Rule:
precision ≤ 18ANDscale ≤ precision - Calculation: Digits to the left of decimal =
precision - scale
The "Fixed 255" Rule
TextArea: always include `<length>255</length>` exactly — this literal value is required by the Metadata API and omitting it fails deployment, even though the UI exposes no length control. Unlike every other type where length is a value you calculate, TextArea's is a fixed constant.
Visible Lines
Mandatory for Long/Rich text and Multi-select picklists to control UI height.
---
3. Field Data Types
3.1 Simple Attribute Types
| Type | <type> Value | Required Attributes |
|---|---|---|
| Auto Number | AutoNumber | displayFormat (must include {0}), startingNumber |
| Checkbox | Checkbox | Default defaultValue to false |
| Date | Date | No precision/length required |
| Date/Time | DateTime | No precision/length required |
Email | Built-in format validation | |
| Lookup Relationship | Lookup | referenceTo, relationshipName, deleteConstraint |
| Master-Detail Relationship | MasterDetail | referenceTo, relationshipName, relationshipOrder |
| Number | Number | precision, scale |
| Currency | Currency | Default precision: 18, scale: 2 |
| Percent | Percent | Default precision: 5, scale: 2 |
| Phone | Phone | Standardizes phone number formatting |
| Picklist | Picklist | valueSet containing EITHER valueSetDefinition (inline) OR valueSetName (reference); restricted (see "Picklist restricted default" below; advanced cases in §3.4) |
| Text | Text | length (Max 255) |
| Text Area | TextArea | <length>255</length> |
| Text (Long) | LongTextArea | length, visibleLines (default 3) |
| Text (Rich) | Html | length, visibleLines (default 25) |
| Time | Time | Stores time only (no date) |
| URL | Url | Validates for protocol and format |
3.2 Computed & Multi-Value Types
| Type | <type> Value | Required Attributes |
|---|---|---|
| Formula | Result type (e.g., Number) | formula, formulaTreatBlanksAs |
| Roll-Up Summary | Summary | See Section 5 for complete requirements |
| Multi-Select Picklist | MultiselectPicklist | valueSet, visibleLines (default 4) |
3.3 Specialized Types
| Type | <type> Value | Required Attributes |
|---|---|---|
| Geolocation | Location | scale, displayLocationInDecimal |
Picklist restricted default
Always set `<restricted>true</restricted>` inside <valueSet> unless the user explicitly says the picklist should accept custom values not in the admin-defined list (e.g. "unrestricted"/"open"). Restricted sets are capped at 1,000 total values (active + inactive). Minimal inline shape:
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value><fullName>Option_A</fullName><default>false</default><label>Option A</label></value>
</valueSetDefinition>
</valueSet>3.4 Advanced Picklists
The inline <valueSetDefinition> above is the simple case. Full rules and worked ✅/❌ examples for everything below are in `references/advanced-picklists.md` — load it for any non-trivial picklist. Section numbers in parentheses below (e.g. "ref §1") point to that reference file, not to this skill. The hard rules:
- Value-set reference (ref §1). A
<valueSet>holds EITHER<valueSetName>(reference) OR
<valueSetDefinition> (inline) — never both. Reference by the bare developer name — Standard set Industry, GlobalValueSet Priority_Levels with NO `__gvs` and no __c (the __gvs suffix is org-storage display only; the Metadata API uses the bare name). A value-set-backed field is <restricted>true</restricted>. Creating the value set is the platform-value-set-generate skill's job; this one only references it.
- Value-name fidelity (ref §3). A picklist value's
<fullName>/<label>keep the user's exact
text including spaces (Closed Won, never Closed_Won). The space→_ + __c rule is for the FIELD name only.
- Dependent picklists (ref §2). Use the modern API 38.0+ form:
<controllingField>+
one <valueSettings> (<controllingFieldValue>+<valueName>) per pair; never the legacy <picklist>/<picklistValues>/<controllingFieldValues> tags. Both controlling and dependent fields MUST be <restricted>true</restricted>, even if the request doesn't say so.
- Enhanced value attributes (ref §3).
<value>entries also accept<color>(hex, leading
#), <isActive> (false retires a value), and a value-level <description>.
- Scoping a picklist to a record type (ref §5). Per-record-type value visibility lives on the
RecordType (<picklistValues>), not the field. The RecordType file carries its own <fullName> (bare developer name). First decide if the object needs a BusinessProcess: only Opportunity / Lead / Case / Solution require one — they won't deploy without a <businessProcess> (Required field is missing: businessProcess), even when only a custom picklist is filtered. There you emit two coupled files: the businessProcesses/<Name>.businessProcess-meta.xml file AND a matching <businessProcess><Name></businessProcess> inside the <RecordType> (after <active>, before <picklistValues>; the <fullName> in the BP file is bare, never object-qualified). *Custom objects (`__c`) and all other standard objects (Account, Contact, …) need NO BusinessProcess — emit the RecordType alone; do not invent one. Scope limit:** picklist-value visibility per record type only — NOT general record-type authoring (compact layouts, page layouts, branding).
---
4. Master-Detail Relationship Rules CRITICAL
Master-Detail fields have strict attribute restrictions that differ from Lookup fields. Violating these rules causes deployment failures.
Forbidden Attributes on Master-Detail Fields
NEVER include these attributes on Master-Detail fields:
| Forbidden Attribute | Why | What Happens |
|---|---|---|
<required> | Master-Detail is ALWAYS required by design | Deployment error |
<deleteConstraint> | Master-Detail ALWAYS cascades deletes | Deployment error |
<lookupFilter> | Only supported on Lookup fields | Deployment error |
Master-Detail vs Lookup Comparison
| Attribute | Master-Detail | Lookup |
|---|---|---|
<required> | ❌ FORBIDDEN | ✅ Optional |
<deleteConstraint> | ❌ FORBIDDEN (always CASCADE) | ✅ Required (SetNull, Restrict, Cascade) |
<lookupFilter> | ❌ FORBIDDEN | ✅ Optional |
<relationshipOrder> | ✅ Required (0 or 1) | ❌ Not applicable |
<reparentableMasterDetail> | ✅ Optional | ❌ Not applicable |
<writeRequiresMasterRead> | ✅ Optional | ❌ Not applicable |
INCORRECT — Master-Detail with forbidden attributes:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Account__c</fullName>
<type>MasterDetail</type>
<referenceTo>Account</referenceTo>
<relationshipName>Contacts</relationshipName>
<relationshipOrder>0</relationshipOrder>
<required>true</required> <!-- WRONG: remove -->
<deleteConstraint>Cascade</deleteConstraint> <!-- WRONG: remove -->
<lookupFilter>...</lookupFilter> <!-- WRONG: remove entire block -->
</CustomField>Errors: Master-Detail Relationship Fields Cannot be Optional or Required · Can not specify 'deleteConstraint' for a CustomField of type MasterDetail · Lookup filters are only supported on Lookup Relationship Fields
CORRECT — Master-Detail field:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Account__c</fullName>
<label>Account</label>
<description>Links this record to its parent Account</description>
<type>MasterDetail</type>
<referenceTo>Account</referenceTo>
<relationshipLabel>Child Records</relationshipLabel>
<relationshipName>ChildRecords</relationshipName>
<relationshipOrder>0</relationshipOrder>
<reparentableMasterDetail>false</reparentableMasterDetail>
<writeRequiresMasterRead>false</writeRequiresMasterRead>
<!-- NO required, deleteConstraint, or lookupFilter -->
</CustomField>CORRECT — Lookup field (with optional attributes):
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Related_Account__c</fullName>
<label>Related Account</label>
<description>Optional link to a related Account</description>
<type>Lookup</type>
<referenceTo>Account</referenceTo>
<relationshipLabel>Related Records</relationshipLabel>
<relationshipName>RelatedRecords</relationshipName>
<required>false</required>
<deleteConstraint>SetNull</deleteConstraint>
<lookupFilter>
<active>true</active>
<filterItems>
<field>Account.Type</field>
<operation>equals</operation>
<value>Customer</value>
</filterItems>
<isOptional>false</isOptional>
</lookupFilter>
</CustomField>Additional Master-Detail Rules
- Relationship Order: First Master-Detail on object =
0, second =1 - Relationship Name: Must be a plural PascalCase string (e.g.,
Travel_Bookings) - Junction Objects: Use two Master-Detail fields for standard many-to-many (enables Roll-ups)
- Limit: Maximum 2 Master-Detail relationships per object. Use Lookup for additional relationships.
---
5. Roll-Up Summary Field Rules CRITICAL
Roll-up Summary fields have the highest deployment failure rate. Follow these rules exactly.
Required Elements for Roll-Up Summary
| Element | Requirement | Format |
|---|---|---|
<type> | Required | Always Summary |
<summaryOperation> | Required | count, sum, min, or max |
<summaryForeignKey> | Required | ChildObject__c.MasterDetailField__c |
<summarizedField> | Conditional | Required for sum, min, max. NOT for count |
Forbidden Elements on Roll-Up Summary
NEVER include these attributes on Roll-Up Summary fields:
| Forbidden Attribute | Why |
|---|---|
<precision> | Summary inherits from summarized field |
<scale> | Summary inherits from summarized field |
<required> | Not applicable to Summary fields |
<length> | Not applicable to Summary fields |
Format Rules for summaryForeignKey and summarizedField
CRITICAL: Both summaryForeignKey and summarizedField MUST use the fully qualified format:
ChildObjectAPIName__c.FieldAPIName__cDecision Logic:
summaryForeignKey=ChildObject__c.MasterDetailFieldOnChild__csummarizedField=ChildObject__c.FieldToSummarize__c
INCORRECT — Roll-Up Summary with common errors:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Total_Amount__c</fullName>
<label>Total Amount</label>
<type>Summary</type>
<precision>18</precision> <!-- WRONG: Remove - inherited from source -->
<scale>2</scale> <!-- WRONG: Remove - inherited from source -->
<summaryOperation>sum</summaryOperation>
<summaryForeignKey>Order__c</summaryForeignKey> <!-- WRONG: Missing field name -->
<summarizedField>Amount__c</summarizedField> <!-- WRONG: Missing object name -->
</CustomField>Errors:
Can not specify 'precision' for a CustomField of type SummaryMust specify the name in the CustomObject.CustomField format (e.g. Account.MyNewCustomField)
CORRECT — Roll-Up Summary (SUM operation):
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Total_Amount__c</fullName>
<label>Total Amount</label>
<description>Sum of all line item amounts</description>
<inlineHelpText>Automatically calculated from child line items</inlineHelpText>
<type>Summary</type>
<summaryOperation>sum</summaryOperation>
<summarizedField>Order_Line_Item__c.Amount__c</summarizedField>
<summaryForeignKey>Order_Line_Item__c.Order__c</summaryForeignKey>
<!-- NO precision, scale, required, or length -->
</CustomField>COUNT: identical structure to SUM but omit `<summarizedField>` entirely (and keep <summaryForeignKey>). MIN / MAX: identical to SUM — just <summaryOperation>min</summaryOperation> or max, with <summarizedField> pointing at the field to find the minimum/maximum of. The Quick Reference table below covers all four.
Roll-Up Summary Quick Reference
| Operation | summarizedField Required? | Use Case |
|---|---|---|
count | NO | Count number of child records |
sum | YES | Add up numeric values |
min | YES | Find smallest value |
max | YES | Find largest value |
Roll-Up Summary Prerequisites
- Roll-Up Summary fields can ONLY be created on the parent object in a Master-Detail relationship
- The child object MUST have a Master-Detail field pointing to this parent
- The summarized field must exist on the child object
---
6. Formula Field Rules
Formula Result Types
A Formula is not a type itself. The <formula> tag is added to a field whose <type> is set to the result data type:
Checkbox,Currency,Date,DateTime,Number,Percent,Text
Formula XML Generation Rules
- The contents of the
<formula>tag MUST be wrapped in a<![CDATA[ ... ]]>section. This prevents the XML parser from interpreting formula operators (like&,<,>) as XML markup. - If the formula text itself contains the literal sequence
]]>, escape it by breaking the CDATA block: e.g.,<![CDATA[Text_Field__c & "]]]]><![CDATA[>"]]> - NEVER use an attribute or tag named
returnType. This does not exist in the Metadata API. The<type>tag defines the return data type of the formula result.
formulaTreatBlanksAs Rule
Decision Logic:
- IF formula result type =
Number,Currency, orPercent→ set<formulaTreatBlanksAs>BlankAsZero</formulaTreatBlanksAs> - IF formula result type =
Text,Date, orDateTime→ set<formulaTreatBlanksAs>BlankAsBlank</formulaTreatBlanksAs>
INCORRECT — Using Formula as type:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Calculated_Value__c</fullName>
<type>Formula</type> <!-- WRONG: Formula is not a valid type -->
<returnType>Number</returnType> <!-- WRONG: returnType does not exist in Metadata API -->
<formula>Field1__c + Field2__c</formula> <!-- WRONG: Missing CDATA wrapper -->
</CustomField>CORRECT — Formula field:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Calculated_Value__c</fullName>
<label>Calculated Value</label>
<description>Sum of Field1 and Field2</description>
<type>Number</type> <!-- Result type, not "Formula" -->
<precision>18</precision>
<scale>2</scale>
<formula><![CDATA[Field1__c + Field2__c]]></formula>
<formulaTreatBlanksAs>BlankAsZero</formulaTreatBlanksAs>
</CustomField>Formula Field Dependencies & Functions
- Formula fields that reference other fields fail deployment if the referenced field doesn't exist or hasn't deployed yet — deploy referenced fields first.
- Use
ISPICKVAL()(not==) for picklist comparisons. - For the full formula-function reference (TEXT/VALUE/CASE/DAY/MONTH/DATEVALUE/ISCHANGED type rules), defer to the
platform-validation-rule-generateskill, which owns formula-function correctness.
---
7. Common Deployment Errors
| Error Message | Cause | Fix |
|---|---|---|
ConversionError: Invalid XML tags or unable to find matching parent xml file for CustomField | XML comments placed before the root <CustomField> element | Remove XML comments (<!-- ... -->) that appear before <CustomField> in the .field-meta.xml file |
Field [FieldName] does not exist. Check spelling. | Referenced field does not exist or has not been deployed yet | Verify the referenced field exists and is deployed before this field |
DUPLICATE_DEVELOPER_NAME | Field fullName already exists on the object | Use a unique business-driven name |
MAX_RELATIONSHIPS_EXCEEDED | More than 2 Master-Detail or 15 Lookup fields on the object | Use Lookup for 3rd+ Master-Detail; review Lookup count |
| Reserved keyword error | Using Order__c, Group__c, etc. | Rename to Status_Order__c, etc. |
Value set must reference a value set name or define a value set, but not both | <valueSet> has both <valueSetName> and <valueSetDefinition> | Keep exactly one (see Section 3.4) |
duplicate value found: [X] is defined multiple times | Two <value> entries share a <fullName> | Make every picklist value <fullName> unique |
Invalid fullName on a picklist value | Value <fullName> starts with a digit or contains hyphens | Start with a letter; no hyphens, no leading digit. Spaces ARE allowed — do NOT underscore them (see §3.4 value-name fidelity) |
Element ...picklist is not allowed | Deprecated ≤37.0 dependent-picklist syntax (<picklist>/<picklistValues>/<controllingFieldValues>) | Use the modern valueSettings/controllingFieldValue/valueName form (Section 3.4) |
---
8. Verification Checklist
Before generating CustomField XML, verify:
Universal Checks
- [ ] Does
<fullName>use valid format and end in__c? - [ ] Are
<description>and<inlineHelpText>both populated and meaningful? - [ ] Is
<label>in Title Case? - [ ] Are there no XML comments (
<!-- ... -->) before the root<CustomField>element? (Comments before the root element break SDR's parser)
Master-Detail Field Checks CRITICAL
- [ ] Is
<required>attribute ABSENT? (Master-Detail is always required) - [ ] Is
<deleteConstraint>attribute ABSENT? (Master-Detail always cascades) - [ ] Is
<lookupFilter>block ABSENT? (Only for Lookup fields) - [ ] Is
<relationshipOrder>set to0or1? - [ ] Is parent object's
<sharingModel>set toControlledByParent?
Lookup Field Checks
- [ ] Is
<deleteConstraint>set toSetNull,Restrict, orCascade? - [ ] Is
<relationshipName>in plural PascalCase?
Picklist Field Checks
- [ ] Does each
<valueSet>contain EITHER<valueSetName>OR<valueSetDefinition>— never both? - [ ] For a value-set reference: is
<restricted>true</restricted>set? - [ ] For a StandardValueSet reference: is the name the bare enum with NO
__c(e.g.Industry)? - [ ] For a GlobalValueSet reference: is the name the bare developer name with NO
__gvssuffix? - [ ] For dependent picklists: is
<controllingField>set, with one<valueSettings>(<controllingFieldValue>+<valueName>) per pair? - [ ] For dependent picklists: is the deprecated
<picklist>/<picklistValues>/<controllingFieldValues>form ABSENT? - [ ] Are all picklist value
<fullName>values unique, start with a letter, and free of hyphens? (spaces are allowed — do NOT replace them with underscores, per the §3.4 value-name fidelity rule)
Roll-Up Summary Field Checks CRITICAL
- [ ] Is
<precision>attribute ABSENT? - [ ] Is
<scale>attribute ABSENT? - [ ] Is
<summaryForeignKey>in formatChildObject__c.MasterDetailField__c? - [ ] For SUM/MIN/MAX: Is
<summarizedField>in formatChildObject__c.FieldName__c? - [ ] For COUNT: Is
<summarizedField>ABSENT? - [ ] Does the child object have a Master-Detail field to this parent?
Formula Field Checks
- [ ] Is
<type>set to result type (NOT "Formula")? - [ ] Is
<formula>content wrapped in<![CDATA[ ... ]]>? - [ ] Is
<returnType>attribute ABSENT? (does not exist in Metadata API) - [ ] Is
<formulaTreatBlanksAs>set toBlankAsZerofor numeric results orBlankAsBlankfor text/date results? - [ ] Do all referenced fields exist and deploy before this field?
Numeric Field Checks
- [ ] Is
scale ≤ precision? - [ ] Is
precision ≤ 18?
Text Area Checks
- [ ] For TextArea: Is
<length>255</length>explicitly included? - [ ] For LongTextArea/Html: Is
<visibleLines>set?
Relationship Limit Checks
- [ ] Are there 2 or fewer Master-Detail relationships on the object?
- [ ] Are there 15 or fewer Lookup relationships on the object?
Naming Checks
- [ ] Is the API name free of reserved words (
Order,Group,Select, etc.)? - [ ] Is the API name unique on this object?
Advanced Picklist Reference
Detailed rules and worked examples for picklist CustomFields that go beyond a simple inline value list. All XML uses the http://soap.sforce.com/2006/04/metadata namespace with a <CustomField> root, exactly like the simple-picklist examples in the main skill.
Covered here:
1. Value Set References (`<valueSetName>`) 2. Controlling / Dependent Picklists 3. Enhanced Value Attributes (`color`, `isActive`, value-level `<description>`) 4. Picklist Validation Rules 5. Scoping a Picklist to a Record Type
---
1. Value Set References (<valueSetName>)
A picklist field can either define its values inline or reference an existing value set (a Global Value Set or a Standard Value Set). The shared set is defined once and reused across many fields.
⭐ HARD RULE: <valueSet> is EITHER a reference OR inline — never both
A <valueSet> element must contain exactly one of:
<valueSetName>— references an existing value set (this section), OR<valueSetDefinition>— defines values inline (the simple-picklist case).
Including both in the same <valueSet> is a deployment error.
❌ INCORRECT — both reference and inline definition:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Priority__c</fullName>
<label>Priority</label>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetName>Priority_Levels</valueSetName> <!-- WRONG: reference … -->
<valueSetDefinition> <!-- WRONG: … AND inline -->
<sorted>false</sorted>
<value>
<fullName>High</fullName>
<default>false</default>
<label>High</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>Error: Value set must reference a value set name or define a value set, but not both.
✅ CORRECT — reference a Global Value Set:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Priority__c</fullName>
<label>Priority</label>
<description>Account priority drawn from the shared Priority Levels value set</description>
<inlineHelpText>Select the priority defined by the central Priority Levels list</inlineHelpText>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetName>Priority_Levels</valueSetName>
</valueSet>
</CustomField>Same <valueSetName> element — Global vs. Standard value sets
The SAME <valueSetName> element is used to reference both kinds of value set; only the name you put inside it differs.
| Referenced set | <valueSetName> value | Suffix |
|---|---|---|
| StandardValueSet (platform-defined, e.g. Industry, LeadSource) | Bare enum name | NO __c, NO __gvs → <valueSetName>Industry</valueSetName> |
| GlobalValueSet | Bare developer name | NO __c, NO `__gvs` → <valueSetName>Priority_Levels</valueSetName> |
Rule: always use the bare developer name — never add `__gvs`. In API 57.0+ orgs the
platform stores/displays a GlobalValueSet's name with a __gvs suffix internally, but theMetadata API (deploy AND retrieve) uses the bare name (the suffix came from a patched-out
Winter '23 change that broke deploys). So <valueSetName>Priority_Levels</valueSetName>, neverPriority_Levels__gvs. A retrieve showing__gvs(or a "returned from org but not found in
local project" warning) is expected org-storage display — keep local metadata on the bare name.
Never append __c to a value-set name either.✅ CORRECT — reference a Standard Value Set (bare name, no suffix):
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Industry__c</fullName>
<label>Industry</label>
<description>Industry classification from the standard Industry value set</description>
<inlineHelpText>Pick the industry that best describes this account</inlineHelpText>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetName>Industry</valueSetName>
</valueSet>
</CustomField>A value-set-backed field is <restricted> by design
When a field references a value set, its values can only change by editing the value set itself — the field cannot define ad-hoc values. Set <restricted>true</restricted> on these fields. Leaving it unrestricted is meaningless for a referenced set.
Cross-reference: creating the value set itself
This skill only references an existing value set. Creating the GlobalValueSet or editing a StandardValueSet (the .globalValueSet-meta.xml / .standardValueSet-meta.xml metadata) is the job of the platform-value-set-generate skill. If the value set does not yet exist, generate it there first, then reference it here.
---
2. Controlling / Dependent Picklists
A dependent picklist filters its available values based on the selected value of a controlling field (another picklist, or a checkbox). The dependency lives on the dependent field via a <controllingField> element plus one <valueSettings> block per (controlling value → dependent value) pair.
⭐ Use the MODERN API 38.0+ form ONLY
| Form | Elements | Status |
|---|---|---|
| Modern (API 38.0+) | <valueSet> → <controllingField> + <valueSetDefinition> + <valueSettings> (<controllingFieldValue> + <valueName>) | ✅ USE THIS |
| Legacy (API ≤ 37.0) | <picklist> / <picklistValues> / <controllingFieldValues> | ❌ DEPRECATED — do NOT generate |
Never emit the legacy <picklist>, <picklistValues>, or <controllingFieldValues> tags. They are not valid against the modern Metadata API and will fail deployment.
⭐ HARD RULE: both the controlling and dependent field must be <restricted>true</restricted>
A field dependency requires a fixed, admin-defined value set on both ends. Always emit `<restricted>true</restricted>` inside the `<valueSet>` of the controlling field AND the dependent field — even when the request does not mention "restricted". Omitting it produces an unrestricted picklist, which cannot reliably participate in a field dependency and diverges from the expected metadata. This is non-negotiable for dependent picklists: if you write a <controllingField> or <valueSettings>, the same <valueSet> must also carry <restricted>true</restricted>.
Structure of a dependent picklist
Inside the dependent field's <valueSet>, in this order:
1. <controllingField> — API name of the controlling field (e.g. Country__c). 2. <valueSetDefinition> — defines the dependent field's own values (as usual). The dependency mapping does NOT go in here. 3. one or more <valueSettings> blocks — siblings of <valueSetDefinition>, NOT nested inside it or inside any <value>. One block per (controlling value, dependent value) pair:
<controllingFieldValue>— a controlling-field value that enables this dependent value.<valueName>— the dependent value that becomes available.
### ⛔ THE #1 MISTAKE: do NOT put<controllingFieldValue>inside<value>
The mapping lives in separate `<valueSettings>` blocks, never as a child of a
<value>in<valueSetDefinition>. Putting<controllingFieldValue>inside a<value>
fails deployment with Element controllingFieldValue invalid at this location in type CustomValue.>
```xml
<!-- ❌ WRONG — controllingFieldValue nested in the value definition -->
<valueSetDefinition>
<value>
<fullName>USA</fullName><label>USA</label><default>false</default>
<controllingFieldValue>Americas</controllingFieldValue> <!-- INVALID HERE -->
</value>
</valueSetDefinition>
>
<!-- ✅ CORRECT — values defined plainly; mapping in SEPARATE valueSettings siblings -->
<valueSetDefinition>
<value><fullName>USA</fullName><label>USA</label><default>false</default></value>
</valueSetDefinition>
<valueSettings>
<controllingFieldValue>Americas</controllingFieldValue>
<valueName>USA</valueName>
</valueSettings>
```
>
One `<valueSettings>` per pair. With multiple controlling values (Americas→USA,Canada;
EMEA→UK,Germany) you emit one block for each (controllingValue, dependentValue) pair — four
pairs = four<valueSettings>blocks. And remember: both fields carry<restricted>true</restricted>.
✅ CORRECT — State dependent on Country (USA → California, Texas)
Controlling field — `Country__c` (a plain restricted picklist):
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Country__c</fullName>
<label>Country</label>
<description>Country used to filter the dependent State picklist</description>
<inlineHelpText>Select the country first; the State list filters to match</inlineHelpText>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>USA</fullName>
<default>false</default>
<label>USA</label>
</value>
<value>
<fullName>Canada</fullName>
<default>false</default>
<label>Canada</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>Dependent field — `State__c`, controlled by `Country__c`:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>State__c</fullName>
<label>State</label>
<description>State filtered by the selected Country</description>
<inlineHelpText>Available states depend on the Country you picked</inlineHelpText>
<type>Picklist</type>
<valueSet>
<controllingField>Country__c</controllingField>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>California</fullName>
<default>false</default>
<label>California</label>
</value>
<value>
<fullName>Texas</fullName>
<default>false</default>
<label>Texas</label>
</value>
</valueSetDefinition>
<valueSettings>
<controllingFieldValue>USA</controllingFieldValue>
<valueName>California</valueName>
</valueSettings>
<valueSettings>
<controllingFieldValue>USA</controllingFieldValue>
<valueName>Texas</valueName>
</valueSettings>
</valueSet>
</CustomField>Each dependent value gets its own <valueSettings> block per enabling controllingvalue. If California were also valid under a second country, you would add another
<valueSettings>block with that country's<controllingFieldValue>and
<valueName>California</valueName>.❌ INCORRECT — deprecated legacy form:
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>State__c</fullName>
<label>State</label>
<type>Picklist</type>
<picklist> <!-- WRONG: deprecated wrapper -->
<controllingField>Country__c</controllingField>
<picklistValues> <!-- WRONG: use valueSetDefinition/value -->
<fullName>California</fullName>
<controllingFieldValues>USA</controllingFieldValues> <!-- WRONG: use valueSettings -->
</picklistValues>
</picklist>
</CustomField>Error: Element {http://soap.sforce.com/2006/04/metadata}picklist is not allowed — the legacy dependency elements are not valid in the modern <valueSet> structure.
---
3. Enhanced Value Attributes
⭐ Value-name fidelity — do NOT underscore picklist value names
A picklist value's `<fullName>` is NOT a field API name and must NOT be transformed. Use the value text exactly as the user spelled it, spaces and all. A value the user calls Closed Won is <fullName>Closed Won</fullName> and <label>Closed Won</label> — never Closed_Won. Picklist value <fullName> permits spaces (and is not required to carry __c); the space-to-underscore + __c rule applies ONLY to the field <fullName> (e.g. field Total Contract Value → Total_Contract_Value__c), not to the values inside it. Underscoring a value name changes the value's identity, diverges from what the user asked for, and breaks any RecordType <picklistValues> / <valueSettings> that reference the value by its real name.
| Element | Spaces? | __c suffix? | Example for "Closed Won" |
|---|---|---|---|
Field <fullName> | ❌ replace with _ | ✅ required | (field named) Status__c |
Picklist value <fullName> | ✅ keep as written | ❌ never | Closed Won |
Picklist value <label> | ✅ keep as written | ❌ never | Closed Won |
❌<fullName>Closed_Won</fullName>✅<fullName>Closed Won</fullName>
When a RecordType filters this value it must also reference Closed Won (with the space)in <values><fullName> — the names must match exactly across field and record type.Inline <value> entries (CustomValue subfields) support more than <fullName>, <default>, and <label>. The common extras:
| Subfield | Type | Notes |
|---|---|---|
<color> | Hex string | UI chart/badge color, e.g. #FF0000. Include the leading #. |
<isActive> | Boolean | false retires a value without deleting it (preserves history). Inactive values still count toward the 1,000-value restricted limit. |
<description> | String | Value-level documentation, distinct from the field-level <description>. |
These are independent of <default> and <label> and may be combined freely.
✅ CORRECT — Status picklist with colors and an inactive value
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Status__c</fullName>
<label>Status</label>
<description>Lifecycle status of the record</description>
<inlineHelpText>Select the current lifecycle stage</inlineHelpText>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>Cancelled</fullName>
<default>false</default>
<label>Cancelled</label>
<color>#FF0000</color>
<isActive>true</isActive>
<description>Work was stopped before completion</description>
</value>
<value>
<fullName>Complete</fullName>
<default>false</default>
<label>Complete</label>
<color>#00FF00</color>
<isActive>true</isActive>
<description>All work finished and accepted</description>
</value>
<value>
<fullName>Draft</fullName>
<default>false</default>
<label>Draft</label>
<isActive>false</isActive>
<description>Legacy draft state, retired from new records</description>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>---
4. Picklist Validation Rules
The Metadata API rejects malformed picklist values at deploy time. Two common failures:
Duplicate value names
Two <value> entries with the same <fullName> inside one <valueSetDefinition> are rejected.
❌ INCORRECT — duplicate value:
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>Open</fullName>
<default>false</default>
<label>Open</label>
</value>
<value>
<fullName>Open</fullName> <!-- WRONG: duplicate fullName -->
<default>false</default>
<label>Open (again)</label>
</value>
</valueSetDefinition>Error: duplicate value found: Open is defined multiple times
Invalid API-name format
A picklist <value>'s <fullName> must start with a letter and must NOT contain hyphens or begin with a digit. Unlike field API names, spaces ARE permitted (e.g. Closed Won, United Kingdom) — see §3 value-name fidelity; do NOT underscore them. The __c suffix is not required on value <fullName> values.
The stricter "only alphanumerics and single underscores, no leading digit, no double or trailing underscore" rule applies to the field <fullName> (the __c-suffixed API name), not to picklist value fullNames.
❌ INCORRECT — invalid value API name:
<value>
<fullName>1st-Choice</fullName> <!-- WRONG: starts with digit, has hyphen -->
<default>false</default>
<label>1st Choice</label>
</value>Error: Invalid fullName: must begin with a letter and use only alphanumeric characters and underscores
✅ CORRECT:
<value>
<fullName>First Choice</fullName> <!-- fixed: letter-first, hyphen removed, space preserved -->
<default>false</default>
<label>1st Choice</label>
</value>---
5. Scoping a Picklist to a Record Type
Scope note. This section covers ONLY the picklist seam between a CustomField and a
RecordType — i.e. "expose a subset of this field's values for a given record type." It
is NOT a general record-type authoring guide. Record types also carry compact layouts,
page-layout assignments, branding, and more, which are out of scope here and owned by the
record-type / UI metadata area. When a request goes beyond picklist value visibility, say
so and defer the broader record-type work rather than guessing.
A common follow-on to creating a picklist field is "…and for the X record type, only show values A and B." That visibility is expressed on the RecordType, not the field — the field keeps its full value set; the record type filters which values appear.
Picklist value filtering
Add one <picklistValues> block per filtered field inside the <RecordType>:
| Element | Requirement | Notes |
|---|---|---|
<picklistValues> | One per filtered picklist | Repeat per field you filter |
<picklist> | Required | The field API name (e.g. Status__c, or StageName for a standard field) |
<values> | One per exposed value | List ONLY the values this record type should show; omitted values are hidden (NOT deleted from the field) |
<values><fullName> | Required | The picklist value's API name |
<values><default> | Required | true on exactly one value, false on the rest |
The RecordType file always carries its own <fullName>
Unlike a CustomObject (whose name is derived from the folder/filename), a <RecordType> must include a `<fullName>` element — the record type's developer name (e.g. <fullName>Internal</fullName>), matching the filename Internal.recordType-meta.xml. It's bare (no object prefix); the object comes from the objects/<Object>/ folder path.
⭐ STEP 1 — Decide if this object needs a BusinessProcess (do this BEFORE writing files)
A record type on a BusinessProcess-gated object — Opportunity, Lead, Case, or Solution — will NOT deploy without a `<businessProcess>` reference, even when it only filters a custom picklist and never touches the standard status field (Required field is missing: businessProcess). *Every other object — all custom objects (`__c`) and other standard objects like Account or Contact — needs NO BusinessProcess.** Decide first:
| Object | BusinessProcess? | Files to emit |
|---|---|---|
| Opportunity / Lead / Case / Solution | REQUIRED | BusinessProcess file + RecordType that references it (two files) |
Custom object (*__c), Account, Contact, everything else | None | RecordType alone (one file) |
For the gated four, a suitable BusinessProcess may already exist in the org — reference its developer name instead of generating a new one (confirm via the grounding MCP's search_metadata if available; otherwise generate a minimal one). For everything else, do not invent a BusinessProcess — adding one to a custom-object record type is wrong.
✅ CORRECT — Opportunity "Enterprise" record type, two deployable files
<!-- File 1: objects/Opportunity/businessProcesses/Enterprise_Sales_Process.businessProcess-meta.xml -->
<BusinessProcess xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Enterprise_Sales_Process</fullName> <!-- BARE — see the api-context translation note below -->
<isActive>true</isActive>
<values><fullName>Prospecting</fullName></values> <!-- these are Opportunity STAGE (StageName) values, NOT Status__c — the BP governs the standard stage picklist. Do NOT add <default> (fails: "Cannot specify a default on: Opportunity") -->
<values><fullName>Qualification</fullName></values>
<values><fullName>Closed Won</fullName></values>
<values><fullName>Closed Lost</fullName></values>
</BusinessProcess>⛔ `<fullName>` is BARE in the source file — strip the entity prefix that api-context gives you.
The metadata grounding / api-context for BusinessProcess reports the fullName in its **APIform**, entity-qualified: Opportunity.Enterprise_Sales_Process. That is correct for the API —but in the source/DX-format file you author, the entity is already conveyed by the
objects/Opportunity/folder path, so the<fullName>element is the bare process name:
Enterprise_Sales_Process, NOTOpportunity.Enterprise_Sales_Process. (Same API-vs-source split
as the GlobalValueSet __gvs suffix in §1.) Writing the entity-qualified form makes theRecordType's bare <businessProcess>Enterprise_Sales_Process</businessProcess> reference fail toresolve → no BusinessProcess named Opportunity.Enterprise_Sales_Process found. **When you pullthe BP name from api-context and it looks likeOpportunity.X, writeXin the file.** The BP
file's<fullName>and the RecordType's<businessProcess>must be the identical bare string.
<!-- File 2: objects/Opportunity/recordTypes/Enterprise.recordType-meta.xml -->
<RecordType xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Enterprise</fullName>
<label>Enterprise</label>
<active>true</active>
<businessProcess>Enterprise_Sales_Process</businessProcess> <!-- REQUIRED; must precede <picklistValues> -->
<picklistValues>
<picklist>Status__c</picklist>
<values>
<fullName>Qualified</fullName>
<default>true</default>
</values>
<values>
<fullName>Closed Won</fullName>
<default>false</default>
</values>
</picklistValues>
</RecordType>❌ INCORRECT — BusinessProcess file emitted but NOT referenced (most common failure)
<!-- File 1 (businessProcesses/Enterprise_Sales_Process...) was generated correctly, BUT -->
<!-- File 2, the RecordType, FORGOT the <businessProcess> element: -->
<RecordType xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Enterprise</fullName>
<active>true</active>
<label>Enterprise</label>
<!-- WRONG: no <businessProcess> here → the BP file is orphaned and the deploy fails -->
<picklistValues> ... </picklistValues>
</RecordType>Error: Required field is missing: businessProcess. Generating the BusinessProcess file is only half the job — the <RecordType> MUST also carry the <businessProcess>NameMatchingTheFile</businessProcess> element. Both, every time, for Opportunity/Lead/Case/Solution.
BusinessProcess gotchas (these block deployment):
- `<fullName>` is BARE — never object-qualified. Inside
objects/Opportunity/businessProcesses/Enterprise_Sales_Process.businessProcess-meta.xml, the <fullName> is just Enterprise_Sales_Process — NOT Opportunity.Enterprise_Sales_Process. The object comes from the folder path. Qualifying it (<fullName>Opportunity.Enterprise_Sales_Process</fullName>) makes the RecordType's bare <businessProcess>Enterprise_Sales_Process</businessProcess> reference unresolvable → no BusinessProcess named Opportunity.Enterprise_Sales_Process found. The BP file's <fullName> and the RecordType's <businessProcess> value must be the same bare string.
- Two coupled parts — (1) the
businessProcesses/<Name>.businessProcess-meta.xmlfile AND
(2) a <businessProcess><Name></businessProcess> element inside the <RecordType>. The developer name must match. Doing only one is the #1 deploy failure.
- Element order —
<businessProcess>must appear before<picklistValues>inside
<RecordType> (it follows <active>). Out-of-order elements fail schema validation.
- No `<default>` on Opportunity BP values — specifying
<default>on a<values>entry
fails with Cannot specify a default on: Opportunity. (Lead / Case / Solution DO allow a BP default; Opportunity is the exception.)
Deployment ordering
GlobalValueSet / StandardValueSet (if the field draws from a value set)
↓
CustomField (the picklist field, with its full value set)
BusinessProcess (REQUIRED for Opportunity/Lead/Case/Solution record types)
↓
RecordType (filters the field's values; references the BusinessProcess)- The CustomField (and ALL the values the record type references) MUST deploy before the
RecordType, or you get Cannot find the picklist value: <X>.
- For Opportunity/Lead/Case/Solution, the
<businessProcess>must exist (same package or
already in the org) before the RecordType.
⭐ UI-sync gotcha — values may not auto-display after API deploy
When <picklistValues> are loaded via the Metadata API, the values are correctly associated under the hood, but they may not automatically appear as "Selected Values" in the Record Type editing screen in Setup. The API deploy succeeds; this is a platform UI-sync limitation, not a deployment error. Tell the user they may need to: Setup → Object Manager → [Object] → Record Types → [Record Type] → Edit next to the picklist → move values into Selected Values → Save. Always call this out when delivering record-type picklist filtering.
Common failures
| Error / Symptom | Cause | Fix |
|---|---|---|
Required field is missing: businessProcess | Opportunity/Lead/Case/Solution record type without a <businessProcess> | Add a BusinessProcess (deploy it first or reference an existing one) |
Cannot specify a default on: Opportunity | <default> set on an Opportunity BusinessProcess value | Remove <default> from the Opportunity BP <values> |
Cannot find the picklist value: <X> | A value in <picklistValues> doesn't exist on the field | Deploy the field with that value first; check spelling/case |
| Filtered values still show full set in UI | API-loaded values not promoted to Selected Values | Perform the manual Setup step above |
Related skills
FAQ
Is Platform Custom Field Generate safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.