platform-custom-metadata-type-generate
Use this skill when users need to create, generate, or validate Salesforce Custom Metadata Type metadata — the __mdt object, its fields, and its deployable records. Trigger when users mention custom metadata types, CMDT, __mdt objects, custom metadata records, .md-meta.xml files, or reference/config
By forcedotcom · 693 installs
npx skills add forcedotcom/sf-skills --skill platform-custom-metadata-type-generate
Source repository · Upstream listing
Salesforce Custom Metadata Type Generator and Validator
When to Use This Skill
Use this skill when you need to:
Create a Custom Metadata Type ( mdt )
Generate or validate CMDT fields, including MetadataRelationship fields
Generate CMDT records as deployable .md meta.xml files
Troubleshoot CMDT deployment errors
1. Overview and Purpose
A Custom Metadata Type produces two separate artifact families , and most requests need both:
Artifact Path Metadata type
Type definition objects/<Name mdt/<Name mdt.object meta.xml CustomObject
Fields objects/<Name mdt/fields/<Field c.field meta.xml CustomField
Records customMetadata/<Name .<Record .md meta.xml CustomMetadata
API name suffix: mdt on the type; fields still end in c .
The defining advantage over a custom setting: CMDT records are metadata and therefore deploy between
orgs. When a user says configuration should "ship with the package" or "be the same in every org," CMDT
is the right answer.
The root element is <CustomObject , but almost none of a custom object's rules apply. sharingModel ,
nameField , and deploymentStatus are required or normal on a regular custom object and are hard
errors here. Do not carry assumptions across from platform custom object generate .
2. Syntactic Essentials — Type Definition (Tier 1)
Required and Allowed Elements
Element Requirement Notes
<label Required Singular UI name
<pluralLabel Required Omitting it gives Must specify a non empty plural label for the CustomObject
<visibility Always include Public , or Protected / PackageProtected only in dev/sandbox/scratch (Section 6)
<description Always include What this type configures and who owns it
<pluralLabel being required here but forbidden on a custom setting is the most commonly inverted
rule between the two families. Neither failure mentions the other family's rule.
Forbidden Elements
Every element below produces Cannot specify: <element for Custom Metadata Type — reusing a regular custom
object's skeleton (with sharingModel , deploymentStatus , a nameField block, or enableSearch ) is the
usual cause:
sharingModel , nameField , deploymentStatus , enableActivities , enableReports , enableHistory ,
enableSearch
CORRECT — minimum valid mdt type:
3. Field Rules
This skill owns the CMDT specific deltas only — the type allowlist, fieldManageability , and
MetadataRelationship below. For generic field mechanics ( <fullName derivation, <label ,
<description , <inlineHelpText , precision/scale, <length , visibleLines ), follow
platform custom field generate .
Supported Field Types
Checkbox , Date , DateTime , Email , Number , Percent , Phone , Picklist , Text , TextArea ,
LongTextArea , Url , plus MetadataRelationship .
Unsupported Field Types
Currency , AutoNumber , MasterDetail , Summary , Location , Time , EncryptedText , Html ,
MultiselectPicklist . Each fails with a precise, well formed error:
(There is no trailing period.) Example: Type Currency of CustomMetadataField Partner Tier mdt.Discount c is not supported for the Entity Partner Tier mdt
Currency is the common trap — it works on a custom setting but not here. Use Number with
<precision / <scale and put the currency in the label or help text.
Formula fields are unsupported , but they break the pattern above. A <formula element on an otherwise
legal type gives only:
Lookup is silently coerced — CRITICAL
<type Lookup</type on a mdt does not fail when its referenceTo resolves to a real sObject: the
deploy is green and the platform silently rewrites the field to MetadataRelationship . A later retrieve
shows a field the user never wrote:
Always write MetadataRelationship explicitly. Emitting Lookup produces a green deploy and a
source vs org mismatch that churns in git the first time anyone retrieves. (Reproduced on a live deploy: a
resolvable Lookup returns as MetadataRelationship ; a non resolvable referenceTo is cleanly rejected.)
MetadataRelationship
Three targets, all valid. referenceTo selects which.
<referenceTo Purpose Extra requirement
Another mdt type Link two custom metadata types Must be a different type
EntityDefinition Point at an sObject None
FieldDefinition Point at a field Requires <metadataRelationshipControllingField
The controlling field must be an EntityDefinition relationship on the same type , referenced as
Type mdt.Field c . Omitting it gives Metadata relationships to Field Definition require a controlling field.
Self references are impossible. Pointing a MetadataRelationship at its own parent type fails with
Cannot add a self lookup relationship child with cascade or restrict options to the object itself (verbatim
from a live mdt deploy) — use a second type.
<fieldManageability
Optional. It defaults to DeveloperControlled — do not add it unless the user wants a different value.
Valid values are DeveloperControlled , SubscriberControlled , and Locked .
It is valid only on mdt fields. On a regular custom object field or a custom setting field:
4. CMDT Records ( .md meta.xml )
Filename convention
Write customMetadata/<TypeNameWithout mdt .<RecordDeveloperName .md meta.xml .
The mdt suffix in the filename also deploys and creates a real record, so it is tolerated — but
Salesforce's retrieve normalizes to the no suffix form, so always write it without mdt to avoid git
churn (detail in references/cmdt records.md ).
Required namespaces
All three are mandatory on the root element — xsi:type on values will not resolve without them.
xsi:type mapping — every value needs one
Field type Correct xsi:type Example value
Checkbox xsd:boolean true
Date xsd:date 2024 01 15
DateTime xsd:dateTime 2024 01 15T10:30:00.000Z
Number, Percent xsd:double 42.0
Text, TextArea, LongTextArea xsd:string some text
Email, Phone, Url xsd:string a@b.com
Picklist xsd:string Beta
MetadataRelationship → EntityDefinition xsd:string Account
MetadataRelationship → FieldDefinition xsd:string Account.Name
any type, null no xsi:type — write <value xsi:nil="true"/
A value pointing at another mdt type via MetadataRelationship was not verified — expect
xsd:string with the target's DeveloperName, but confirm.
xsd:picklist must not be emitted — CRITICAL
Salesforce's own documentation tells you to use xsd:picklist for Picklist fields. It is wrong — it is
not a valid XML Schema type. Instead of a clean validation error, the platform fails the entire deploy
server side with no component level diagnostics, taking every other component down with it. Always emit
xsd:string for a Picklist field's value; never xsd:picklist . See references/cmdt records.md §2.
Record body rules
<label is required . Omitting it gives Required fields are missing: [MasterLabel] — note it reports
the sObject field name MasterLabel , not label .
<protected is optional and defaults to false .
Never put <fullName inside a <values block — it is a hard parse error.
<value xsi:nil="true"/ works on optional fields. On a required field it is rejected exactly as if
the field were absent. Omitting xsi:type on a non nil value is always fatal, including for text fields.
CORRECT — a complete record:
Record DeveloperName rules
The name is the second segment of the filename. It must begin with a letter, contain only alphanumerics and
underscores, not end with an underscore, not contain two consecutive underscores, and be at most 40
characters . Violations give:
Over 40 characters gives Value too long for field: fullName maximum length is:40 (no space after the colon).
Deploy ordering
No ordering constraint — the type and every referenced field only need to resolve (already in the org,
or present in the same deploy). Two failures tell you what is missing:
Custom metadata type <Type mdt is not available in this organization. (the type is missing) and
<Type mdt: could not find fields: <Field c (the type exists but the field does not).
→ Full record value error catalog and the xsi:type coercion asymmetry: references/cmdt records.md .
Generating records from user input
Record data arrives three ways; in each case emit one <Type .<Record .md meta.xml per row , mapping
columns to <field / <value pairs with the right xsi:type :
Inline list/table — map columns straight to fields.
CSV — header = field API names (or map labels to them), each line = one record; take the DeveloperName
from a key column.
Prose — infer the rows ("Bronze at 5%, Gold at 15%") and confirm the field mapping before generating.
Derive every DeveloperName deterministically from the key column or label — never free hand one. For each
row, run scripts/sanitize developer name.sh "<LABEL " <ROW before writing its record file (same input →
same valid name; empty/all symbol/non Latin labels fall back to Record <ROW ).
The transform is many to one — United States and United States both yield United States , and since
each record is <Type .<DeveloperName .md meta.xml , a collision silently overwrites a row. After deriving the
whole batch, check duplicates before writing ; on any collision stop and report the colliding source
labels rather than overwrite (append a deterministic 2 / 3 suffix only if the user asks to auto resolve).
Ordered algorithm, ASCII only limitation, uniqueness check, and worked examples: references/cmdt records.md §7 .
Consuming records in Apex
Records are read through the generated typed class — no SOQL, no query rows consumed , safe in loops and
triggers:
Accessor truncation — CRITICAL for LongTextArea . getAll() / getInstance() return only the first
255 characters of any field; longer LongTextArea values are silently truncated for those callers and
must be read via SOQL ( SELECT ... FROM Partner Tier mdt ). Say so whenever you put a LongTextArea on a
CMDT.
5. Choosing the Right Component
Before generating, confirm a CMDT is what the user needs.
If the config is… Use Why
Reference data that must deploy between orgs with its records CMDT (this skill) Records are metadata
An admin maintained mapping / lookup / crosswalk table (field↔field, code↔code, "map A to B") CMDT (this skill) Editable reference data, one record per pair
Admin editable per profile/user, or org local platform custom setting generate Values are data and stay in one org
Business records users create and edit at runtime platform custom object generate CMDT records are not transactional data
Translatable UI text Custom Label — not generated here Labels are the translation surface
Credentials, API keys, tokens Named Credential / External Credential — not generated here See Section 6
A permission check in Apex or a flow Custom Permission — not generated here Boolean access belongs in the permission model
The distinction that matters most: CMDT records deploy, custom setting values do not. If the user needs
admins to edit values per profile at runtime, that is a custom setting, not a CMDT.
Field mapping and lookup tables are a canonical CMDT use case. When a user asks to "map fields from A
to B," maintain a code to code lookup, or keep a crosswalk admins can edit, model it as a CMDT — one record
per pair (e.g. Lead Field c / Target Field c , or MetadataRelationship fields to FieldDefinition ) —
never a hardcoded Apex Map , constant, or Flow decision. Phrasing like "for conversion" or "for our
i