关于 platform-custom-metadata-type-generate
用于生成和校验 Salesforce 自定义元数据类型(CMDT)、字段及记录的技能,面向维护可跨组织部署配置的开发者。它涵盖元数据关系字段和可部署的 `.md-meta.xml` 记录文件。
主要功能
- 01生成 CMDT 类型定义与字段
- 02校验支持的字段类型和关系
- 03生成可部署的 CMDT 记录文件
- 04按字段类型输出记录值格式
- 05诊断部署限制与常见错误
适用场景
- 01创建随软件包部署的组织配置
- 02从 CSV 或列表批量生成 CMDT 记录
- 03排查元数据关系或记录部署问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill platform-custom-metadata-type-generate安装后在 Claude Code 中输入/platform-custom-metadata-type-generate确认已加载。
附带文件references/cmdt-records.mdscripts/sanitize-developer-name.sh
详细介绍
Salesforce 自定义元数据类型生成器和验证器
何时使用此技能
需要以下操作时,请使用此技能:
- 创建自定义元数据类型(
__mdt) - 生成或验证 CMDT 字段,包括
MetadataRelationship字段 - 将 CMDT 记录生成为可部署的
.md-meta.xml文件 - 排查 CMDT 部署错误
如需了解此技能未涵盖的权威 Metadata API 类型、字段和枚举详情,请使用 platform-metadata-api-context-get 技能。
1. 概述与用途
自定义元数据类型会生成两种独立的构件系列,大多数请求都需要两者:
| 构件 | 路径 | 元数据类型 |
|---|---|---|
| 类型定义 | objects/<Name>__mdt/<Name>__mdt.object-meta.xml | CustomObject |
| 字段 | objects/<Name>__mdt/fields/<Field>__c.field-meta.xml | CustomField |
| 记录 | customMetadata/<Name>.<Record>.md-meta.xml | CustomMetadata |
**API 名称后缀:**类型以 __mdt 结尾;字段仍以 __c 结尾。
**相较于自定义设置,CMDT 的核心优势在于:CMDT 记录属于元数据,因此可以在不同组织之间部署。**如果用户希望配置“随软件包一起发布”或“在每个组织中都相同”,CMDT 就是正确选择。
根元素是
<CustomObject>,但自定义对象的大多数规则几乎都不适用。常规自定义对象通常需要或会使用sharingModel、nameField和deploymentStatus,但在此处它们会导致严重错误。不要沿用platform-custom-object-generate中的假设。
2. 语法要点 — 类型定义(第 1 层)
必需和允许的元素
| 元素 | 要求 | 说明 |
|---|---|---|
<label> | 必需 | UI 中显示的单数名称 |
<pluralLabel> | 必需 | 省略时会报错 Must specify a non-empty plural label for the CustomObject |
<visibility> | 始终包含 | 使用 Public;仅在开发/沙盒/临时组织中使用 Protected/PackageProtected(第 6 节) |
<description> | 始终包含 | 说明此类型配置的内容及其所有者 |
<pluralLabel> 在此处必需、但在自定义设置中禁止,是这两类对象之间最常被弄反的规则。任一错误都不会提及另一类对象的规则。
禁止的元素
以下每个元素都会导致错误 Cannot specify: <element> for Custom Metadata Type。重用常规自定义对象的骨架(包含 sharingModel、deploymentStatus、nameField 块或 enableSearch)通常是导致此错误的原因:
sharingModel、nameField、deploymentStatus、enableActivities、enableReports、enableHistory、enableSearch
正确 — 最精简的有效 __mdt 类型:
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Partner Tier</label>
<pluralLabel>Partner Tiers</pluralLabel>
<description>Discount and threshold configuration per partner tier. Ships with the package; edited by the revenue ops team.</description>
<visibility>Public</visibility>
</CustomObject>
3. 字段规则
此技能仅涵盖 CMDT 特有的差异 — 以下类型允许列表、fieldManageability 和 MetadataRelationship。通用字段机制(<fullName> 的推导、<label>、<description>、<inlineHelpText>、精度/小数位数、<length>、visibleLines)请参阅 platform-custom-field-generate。
支持的字段类型
Checkbox、Date、DateTime、Email、Number、Percent、Phone、Picklist、Text、TextArea、LongTextArea、Url,以及 MetadataRelationship。
不支持的字段类型
Currency、AutoNumber、MasterDetail、Summary、Location、Time、EncryptedText、Html、MultiselectPicklist。每种类型都会产生格式明确且精确的错误:
Type {TypeName} of CustomMetadataField {Object}__mdt.{Field}__c is not supported for the Entity {Object}__mdt
(末尾没有句点。)例如:Type Currency of CustomMetadataField Partner_Tier__mdt.Discount__c is not supported for the Entity Partner_Tier__mdt
Currency 是最常见的陷阱 — 它在自定义设置中可用,但在此处不可用。请改用带有 <precision>/<scale> 的 Number,并将货币放在标签或帮助文本中。
公式字段不受支持,但它们不遵循上述模式。在其他方面合法的类型上添加 <formula> 元素时,只会返回以下错误:
Invalid data type.
Lookup 会被静默强制转换 — 严重
当 referenceTo 解析为真实 sObject 时,__mdt 上的 <type>Lookup</type> 不会报错:部署成功,平台会静默地将字段改写为 MetadataRelationship。之后检索时会显示用户从未写入过的字段:
<type>MetadataRelationship</type>
始终显式写入 MetadataRelationship。 使用 Lookup 会导致部署成功,但源文件与组织之间不匹配;任何人第一次检索时,这种差异都会造成 Git 变更。(已在实时部署中复现:可解析的 Lookup 会返回为 MetadataRelationship;无法解析的 referenceTo 会被正常拒绝。)
MetadataRelationship
有三种有效的目标,由 referenceTo 选择。
<referenceTo> | 用途 | 额外要求 |
|---|---|---|
另一个 __mdt 类型 | 关联两个自定义元数据类型 | 必须是不同的类型 |
EntityDefinition | 指向一个 sObject | 无 |
FieldDefinition | 指向一个字段 | 需要 <metadataRelationshipControllingField> |
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Target_Field__c</fullName>
<label>Target Field</label>
<type>MetadataRelationship</type>
<referenceTo>FieldDefinition</referenceTo>
<metadataRelationshipControllingField>Partner_Tier__mdt.Target_Object__c</metadataRelationshipControllingField>
<relationshipLabel>Target Field</relationshipLabel>
<relationshipName>Target_Field</relationshipName>
</CustomField>
控制字段必须是同一类型上的 EntityDefinition 关系,引用格式为 Type__mdt.Field__c。省略该字段会报错:Metadata relationships to Field Definition require a controlling field.
不可能自引用。 将 MetadataRelationship 指向其自身的父类型会失败,并报错 Cannot add a self-lookup relationship child with cascade or restrict options to the object itself(这是从实时 __mdt 部署中逐字摘录的错误信息)——请使用第二种类型。
<fieldManageability>
可选,默认值为 DeveloperControlled——除非用户需要其他值,否则不要添加。有效值为 DeveloperControlled、SubscriberControlled 和 Locked。
它仅适用于 __mdt 字段。在常规自定义对象字段或自定义设置字段上使用时:
Field manageability cannot be set on this entity.
4. CMDT 记录(.md-meta.xml)
文件名约定
写入 customMetadata/<TypeNameWithout__mdt>.<RecordDeveloperName>.md-meta.xml。
customMetadata/Partner_Tier.Bronze_AMER.md-meta.xml
文件名中带有 __mdt 后缀也能部署并创建真实记录,因此可以接受——但 Salesforce 检索时会将其规范化为不带后缀的格式,所以应始终省略 __mdt,以避免 Git 变更(详见 references/cmdt-records.md)。
必需的命名空间
根元素上必须包含以下三个命名空间——没有它们,值中的 xsi:type 将无法解析。
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
xsi:type 映射 — 每个值都需要一个
| 字段类型 | 正确的 xsi:type | 示例值 |
|---|---|---|
| 复选框 | xsd:boolean | true |
| 日期 | xsd:date | 2024-01-15 |
| 日期时间 | xsd:dateTime | 2024-01-15T10:30:00.000Z |
| 数字、百分比 | xsd:double | 42.0 |
| 文本、文本区域、长文本区域 | xsd:string | some text |
| 电子邮件、电话、Url | xsd:string | [email protected] |
| 选项列表 | xsd:string | Beta |
MetadataRelationship → EntityDefinition | xsd:string | Account |
MetadataRelationship → FieldDefinition | xsd:string | Account.Name |
| 任意类型,空值 | 不写 xsi:type — 写入 <value xsi:nil="true"/> |
通过 MetadataRelationship 指向另一个 __mdt 类型的值尚未验证——预期应使用 xsd:string 和目标的 DeveloperName,但请确认。
不得输出 xsd:picklist — 严重
Salesforce 自身的文档建议在选项列表字段中使用 xsd:picklist。这是错误的——它不是有效的 XML Schema 类型。平台不会返回明确的验证错误,而是直接在服务端使整个部署失败,不提供组件级诊断,并连带使其他所有组件都无法部署。选项列表字段的值始终使用 xsd:string;绝不要使用 xsd:picklist。 参见 references/cmdt-records.md §2。
记录正文规则
<label>是必填项。省略该字段会报错Required fields are missing: [MasterLabel]——注意,错误中报告的是 sObject 字段名MasterLabel,而不是label。<protected>是可选项,默认值为false。- 切勿在
<values>块中放入<fullName>——这会导致硬解析错误。 <value xsi:nil="true"/>可用于可选字段。用于必填字段时,会被拒绝,效果与字段缺失完全相同。非空值若省略xsi:type,始终会导致致命错误,包括文本字段。
正确示例——完整记录:
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<label>Bronze AMER</label>
<protected>false</protected>
<values>
<field>Discount_Percent__c</field>
<value xsi:type="xsd:double">5.0</value>
</values>
<values>
<field>Region__c</field>
<value xsi:type="xsd:string">AMER</value>
</values>
<values>
<field>Effective_Date__c</field>
<value xsi:type="xsd:date">2024-01-15</value>
</values>
<values>
<field>Notes__c</field>
<value xsi:nil="true"/>
</values>
</CustomMetadata>
记录 DeveloperName 规则
名称是文件名的第二段。名称必须以字母开头,只能包含字母数字字符和下划线,不能以下划线结尾,不能包含连续的两个下划线,且最多为 40 个字符。违反规则会报错:
Custom Metadata Record Name: The <Type>__mdt API Name can only contain underscores and alphanumeric characters. It must be unique, begin with a letter, not include spaces, not end with an underscore, and not contain two consecutive underscores.
超过 40 个字符时会报错 Value too long for field: fullName maximum length is:40(冒号后没有空格)。
部署顺序
没有顺序限制——只要类型和所有引用的字段能够解析即可(已存在于组织中,或包含在同一次部署中)。两种错误会告知缺少的内容:
Custom metadata type <Type>__mdt is not available in this organization.(类型缺失)以及
<Type>__mdt: could not find fields: <Field>__c(类型存在,但字段不存在)。
→ 完整的记录值错误目录和 xsi:type 强制转换差异:references/cmdt-records.md。
根据用户输入生成记录
记录数据有三种形式;每种情况下都要为每一行生成一个 <Type>.<Record>.md-meta.xml 文件,将列映射为 <field>/<value> 对,并使用正确的 xsi:type:
- 内联列表/表格——将列直接映射到字段。
- CSV——标题行使用字段 API 名称(或将标签映射到字段 API 名称),每行对应一条记录;从键列获取 DeveloperName。
- 散文——推断各行(“Bronze 为 5%,Gold 为 15%”),并在生成前确认字段映射。
必须从键列或标签确定性地派生每个 DeveloperName——切勿凭空编写。对于每一行,在写入其记录文件之前运行 scripts/sanitize-developer-name.sh "<LABEL>" <ROW>(相同输入 → 相同的有效名称;空标签、全符号标签或非拉丁字符标签会回退为 Record_<ROW>)。
该转换是多对一的——United States 和 United-States 都会得到 United_States;由于每条记录的文件名都是 <Type>.<DeveloperName>.md-meta.xml,发生冲突时会静默覆盖其中一行。派生出整个批次的名称后,先检查重复项,再写入文件;如有冲突,停止并报告发生冲突的源标签,不要覆盖(只有用户要求自动解决时,才追加确定性的 _2/_3 后缀)。有序算法、仅支持 ASCII 的限制、唯一性检查及示例:references/cmdt-records.md §7。
在 Apex 中使用记录
通过生成的强类型类读取记录——不使用 SOQL,也不消耗查询行数,可安全用于循环和触发器:
Map<String, Partner_Tier__mdt> all = Partner_Tier__mdt.getAll();
Partner_Tier__mdt bronze = Partner_Tier__mdt.getInstance('Bronze_AMER'); // by DeveloperName
访问器截断——对于 LongTextArea 至关重要。 getAll()/getInstance() 对任何字段都只返回前 255 个字符;对于这些调用方,较长的 LongTextArea 值会被静默截断,必须通过 SOQL 读取(SELECT ... FROM Partner_Tier__mdt)。在 CMDT 上添加 LongTextArea 时,务必说明这一点。
5. 选择合适的组件
生成之前,请确认用户需要的是 CMDT。
| 如果配置是…… | 使用 | 原因 |
|---|---|---|
| 必须随记录一起部署到不同组织的参考数据 | CMDT(此技能) | 记录属于元数据 |
| 由管理员维护的映射/查找/交叉映射表(字段↔字段、代码↔代码、“将 A 映射到 B”) | CMDT(此技能) | 可编辑的参考数据,每对值一条记录 |
| 可按配置文件/用户编辑,或仅限组织本地 | platform-custom-setting-generate | 值属于数据,并且只保留在一个组织中 |
| 用户在运行时创建和编辑的业务记录 | platform-custom-object-generate | CMDT 记录不是事务数据 |
| 可翻译的 UI 文本 | 自定义标签 — 此处不生成 | 标签是用于翻译的对象 |
| 凭据、API 密钥、令牌 | 命名凭据/外部凭据 — 此处不生成 | 参见第 6 节 |
| Apex 或流程中的权限检查 | 自定义权限 — 此处不生成 | 布尔访问权限应属于权限模型 |
最重要的区别是:CMDT 记录会部署,自定义设置值不会。 如果用户需要管理员在运行时按配置文件编辑值,那么应该使用自定义设置,而不是 CMDT。
字段映射和查找表是典型的 CMDT 用例。 当用户要求“将字段从 A 映射到 B”、维护代码到代码的查找关系,或保留可由管理员编辑的交叉映射表时,应将其建模为 CMDT — 每对值一条记录(例如
Lead_Field__c/Target_Field__c,或MetadataRelationship字段到FieldDefinition)— 绝不要使用硬编码的 ApexMap、常量或流程决策。措辞中提到“用于转换”或“用于我们的集成”,并不意味着它属于代码:如果管理员无需部署即可维护这些对应关系,它就是 CMDT 参考数据。
6. 可见性、机密信息与禁止静默降级规则
Protected 和 PackageProtected 取决于组织类型
这两种设置只能部署到开发组织、沙盒组织或临时组织。在其他任何组织中:
You can't set the visibility for a Custom Metadata Type to Protected unless you are in a developer, sandbox, or scratch org.
将 PackageProtected 替换为 Protected,即可得到相同的字符串。此错误已在类似生产环境的组织中得到确认;这类值在开发组织、沙盒组织或临时组织中可以成功,这一点来自消息文本,而非单独测试的结果。
禁止静默降级规则 — 关键
如果 Protected 因组织类型而失败,绝不要通过切换为 Public 来“修复”。 请停止并告知用户。静默降级会把有意的保密选择变成全世界可读的组件,同时部署成功且没有任何警告 — 这是最糟糕的结果,因为没有任何信号表明发生了变化。
应报告实际情况:该组织不允许使用 Protected,因此可以部署到开发组织/沙盒组织/临时组织,或接受使用 Public。由用户选择。
此规则适用于任何缩小可见性的情况,不仅限于此错误。
CMDT 不适合存储机密信息
CMDT 不是机密信息存储库。Protected 会限制命名空间外部的访问,但它不是加密,也不能保护该值不被命名空间内部的代码或管理员访问。
当用户要求将 API 密钥、密码、令牌、客户端机密或证书存储在 CMDT 中时:
- 先明确警告 — 在生成任何内容之前 — CMDT 不适合存放凭据,并且
Protected不是加密。 - 推荐合适的组件 — 命名凭据搭配外部凭据。明确说明此技能不会生成这些组件。
- 如果用户仍坚持,则照办 — 在组织允许的情况下,以
Protected可见性生成 CMDT,并在回复中保留警告。不要默默拒绝,也不要默默照办。
两条规则需结合适用,且先后顺序很重要。 如果用户坚持用 CMDT 存储机密信息,同时组织拒绝 Protected,那么禁止静默降级规则可以防止密钥落入 Public 组件。请停止并报告 — 绝不要将包含机密信息的类型降级为 Public。
7. 部署错误参考
第 2–4 节在每种类型和字段级错误字符串旁边,都注明了导致该错误的操作(Cannot specify: <element> for Custom Metadata Type、不支持的类型字符串、公式的 Invalid data type.、Field manageability cannot be set on this entity.、MetadataRelationship 的控制字段和自查找错误,以及 Must specify a non-empty plural label)。
完整原文记录值目录——包括每种错误 xsi:type 的拒绝信息、Required fields are missing: [MasterLabel]、必填字段上的 nil、DeveloperName 规则,以及精确文本陷阱(字段名使用 Label 而非 API 名称、无 xsi:type 错误中的双空格和 i.g. 拼写错误,以及没有空格的 maximum length is:40)——均见 references/cmdt-records.md。
请注意一个对应关系:此处的后缀是 for Custom Metadata Type(有空格,标题大小写);自定义设置使用 for CustomSettings。匹配这些字符串时,不要假定它们共用同一模板。
8. 验证清单
类型定义检查
- 是否都包含
<label>、<pluralLabel>和<visibility>? - 是否包含具体明确的
<description>? - 是否未包含
<sharingModel>、<nameField>和<deploymentStatus>? - 是否未包含
<enableActivities>、<enableReports>、<enableHistory>和<enableSearch>? - 目录和文件名是否以
__mdt结尾?
字段检查
- 每种字段类型是否都在支持列表中(第 3 节)?
- 是否未包含
Currency?(此处不支持——请使用Number) - 是否未包含任何
<formula>元素? - 是否未包含
<type>Lookup</type>,并改用MetadataRelationship?关键——Lookup会静默部署并被重写 - 对于
FieldDefinition关系,是否包含<metadataRelationshipControllingField>? - 是否没有任何
MetadataRelationship指向其自身的父类型? - 是否仅在用户要求非默认值时才填写
<fieldManageability>?
记录检查 关键
- 文件名是否为
<Type>.<Record>.md-meta.xml,且不含__mdt? - 根元素上是否包含全部三个命名空间(
xmlns、xmlns:xsd、xmlns:xsi)? - 每个非 nil 的
<value>是否都带有xsi:type? - 是否完全未使用
xsi:type="xsd:picklist"?(这会导致整个部署崩溃——请使用xsd:string) - 每个
xsi:type是否都与第 4 节表格中的字段类型匹配? - 每条记录是否都包含
<label>? - 每个
<values>块中是否都未包含<fullName>? - 每个必填字段是否都设置了实际值,而非
xsi:nil="true"? - 每条记录的 DeveloperName 是否不超过 40 个字符、以字母开头,且不含空格或连字符?
- 如果记录来自批次、列表或 CSV,所有派生的 DeveloperName 是否都互不相同(使用
sort | uniq -d检查),并且是否报告了任何冲突,而非静默覆盖行?关键——冲突会导致记录丢失,且不会出现部署错误 - 每个
<field>是否都指定了该类型中已有或此次部署中包含的字段? - 如果记录来自列表、CSV 或文字描述,是否每行恰好生成一个文件,并将各列映射到正确字段?
使用检查
- 如果用户询问如何读取记录,是否提供了使用
getAll()/getInstance(developerName)的 Apex 代码片段? - 如果有记录填入
LongTextArea,是否提醒用户访问器会将内容截断为 255 个字符,且要获取完整值必须使用 SOQL?
安全检查 关键
- 如果请求涉及机密、密钥、令牌或密码,是否在生成内容之前发出警告,并建议使用 Named Credential?
- 是否保留了
Protected,而未在组织类型不匹配时静默降级为Public?
组件选择检查
- 如果管理员需要在运行时按配置文件或用户编辑值,是否建议改用
platform-custom-setting-generate? - 如果这些数据实际上是事务性业务记录,是否建议改用
platform-custom-object-generate?
参考文件索引
| 文件 | 阅读时机 |
|---|---|
references/cmdt-records.md | 调试 CMDT 记录部署时——完整的逐字记录值错误目录、类型强制转换的不对称性(平台会静默接受哪些 xsi:type 不匹配),以及经过演练的记录示例 |