Custom Metadata And Settings Designer
Design and audit custom metadata types and settings for Salesforce applications.
Custom Metadata & Custom Settings Designer Agent
What This Agent Does
Two modes:
designmode — given a configuration scenario (feature flag, environment-specific config, business rule table, API endpoint registry, tax rate table, etc.), produces the correct artifact design: Custom Metadata Type vs List Custom Setting vs Hierarchy Custom Setting, with fields, protection, usage pattern in Apex / Flow / Formula, default record set, and deploy-vs-data-load boundary.auditmode — given an org (or a named CMT / Custom Setting), audits usage: unused types, types mutated at runtime via Apex Metadata API (anti-pattern for most scenarios), Custom Settings that should migrate to CMT, Hierarchy Custom Settings with no org-default, types referenced in code but not in any deployed record set.
Scope: One design or one audit per invocation. Produces design doc + XML stubs + deploy plan. Does not create types, does not insert/upsert records.
Invocation
- Direct read — "Follow
agents/custom-metadata-and-settings-designer/AGENT.mdin design mode for a feature-flag registry used by 30 Apex classes" - Slash command —
/design-cmt-or-settings - MCP —
get_agent("custom-metadata-and-settings-designer")
Mandatory Reads Before Starting
agents/_shared/AGENT_CONTRACT.mdskills/admin/custom-metadata-typesskills/admin/custom-metadata-types-and-settingsskills/apex/custom-metadata-in-apexskills/devops/environment-specific-value-injectionskills/apex/feature-flags-and-kill-switchesskills/admin/picklist-and-value-sets— CMT entity references to pickupstemplates/apex/cmdt/— if CMT query patterns are emittedtemplates/admin/naming-conventions.mdagents/_shared/DELIVERABLE_CONTRACT.md— Wave 10 output contract (persistence + scope guardrails)
Inputs
| Input | Required | Example |
|---|---|---|
mode | yes | design | audit |
target_org_alias | yes | |
scenario_summary | design | "feature flag registry for 30 Apex classes; per-environment values; production reads must be fast" |
expected_usage | design | apex | flow | formula | apex+flow | apex+flow+formula |
expected_record_count | design | integer — approximate live record count |
environment_scoped | design | true if values vary per sandbox/prod (tilts to CMT) |
audit_scope | audit | org | type:<DeveloperName> |
Plan
Design mode
Step 1 — Choose the right artifact
| Need | Right artifact |
|---|---|
| Config values that vary per environment and ship with the package; queried from Apex/Flow/Formula | Custom Metadata Type |
| Per-user / per-profile overrides with minimal record count | Hierarchy Custom Setting |
| Small reference list, rarely changed, cached in memory | List Custom Setting (legacy preference) — but in most new designs, CMT is now the default |
| Frequently updated runtime state (counters, cursors) | Neither — use a custom sObject or Platform Cache; CMT/Settings are not for runtime state |
| Large reference data (> 10,000 rows) | Neither — use a custom object + selective SOQL |
CMT rules of thumb (cite custom-metadata-types):
- CMT records are metadata; they deploy with Change Sets / SFDX.
- CMT is queryable from Apex, Flow, and Formulas (Formulas via entity reference fields).
- CMT is cached in-memory and does not consume SOQL queries when accessed via
getAll()/getInstance(). - CMT records can be protected (not queryable from outside the package namespace).
Custom Settings rules of thumb:
- List Custom Settings are legacy — prefer CMT for new work.
- Hierarchy Custom Settings are still useful for per-user / per-profile / org-default overrides where the CMT per-user model is awkward.
If environment_scoped=true → CMT is almost always correct.
If expected_record_count > 10000 → neither; route to custom object.
Step 2 — Design the type (CMT) or the setting
For CMT:
- Developer name per
naming-conventions.md:<Feature>_Config__mdtfor config,<Feature>_Rule__mdtfor rules. - Fields — enumerate, with type, required, and whether the field participates in the label-lookup pattern.
- Protection — Protected Component Yes/No. Protected CMT records are invisible to queries from outside the defining package.
- Deployment scope — which records ship with the type (bootstrap) vs which get loaded per environment.
- Usage in Apex —
MyConfig__mdt.getInstance(developerName)for singleton access;MyConfig__mdt.getAll()for the full set. Document the pattern. - Usage in Flow — "Get Records" on the CMT object; document the pattern including null-safety.
- Usage in Formula — entity reference field returning a CMT record, then dot-walk to fields. Document.
For Custom Setting:
- Type: List vs Hierarchy.
- Visibility: Public vs Protected.
- Fields — same enumeration.
- Usage:
MySetting__c.getInstance(userId)for Hierarchy;MySetting__c.getValues(name)for List. - Caveat: Custom Settings are still record storage; records consume storage and queries depending on access pattern.
Step 3 — Default record set
CMT records that ship with the type and represent "out of the box" defaults live in the repo. Environment-specific records live in per-environment record sets (typically force-app/main/default/customMetadata/ + env-specific subfolders or per-SFDX-project config).
Emit:
- The type XML stub (
<Feature>_Config__mdt/__mdt.xmlskeleton). - 1–3 default record XML stubs (
<RecordName>.md).
For Custom Settings, records are DATA (not metadata); they must be loaded via a Data Loader / SFDX data:tree:import step, NOT a metadata deployment.
Step 4 — Runtime-mutation stance
Apex Metadata API supports inserting / updating CMT records at runtime. This is:
- Appropriate for an admin UI that lets a non-developer update feature-flag values without a deployment.
- Inappropriate for "I want to track counters" or "I want to log events" (that's runtime state, not metadata).
If the scenario mentions runtime updates, confirm the use case fits, cite skills/apex/custom-metadata-in-apex, and note the governor implications (Metadata API ops are async and not transactional with DML).
Audit mode
Step 1 — Enumerate types and settings
tooling_query("SELECT QualifiedApiName, DeveloperName, NamespacePrefix FROM CustomMetadata")— all CMT records in the org.- Type-level:
tooling_query("SELECT QualifiedApiName, DeveloperName, NamespacePrefix FROM EntityDefinition WHERE QualifiedApiName LIKE '%__mdt'"). - Custom Settings:
tooling_query("SELECT DeveloperName, NamespacePrefix FROM CustomObject WHERE CustomSetting__c != null")— falls back to inspectingCustomObject.settingsTypevia Metadata API if Tooling is limited.
Step 2 — Find dead types
For each CMT type with 0 records → dead.
For each type with records but no Apex / Flow / Formula references (probe via tooling_query on ApexClass.Body LIKE '%<TypeName>%' and flow metadata scan) → dead.
Step 3 — Find mis-classified artifacts
- List Custom Settings with fewer than 50 records and environment-scoped values → migrate to CMT.
- List Custom Settings with > 50 records → fine as-is.
- Custom Objects used as config (descriptive fields, a handful of records) → migrate to CMT.
- CMT records mutated at runtime via Metadata API → confirm this is a deliberate admin-UI pattern or flag as runtime-state drift.
Step 4 — Find hierarchy gaps
For Hierarchy Custom Settings:
- No org-default value → P1.
- Profile-level values set on profiles that no active user holds → dead config.
Step 5 — Find namespace collisions
If the org has managed packages that ship their own CMT types, and a local type shares a base name (before namespace), flag as REFUSAL_MANAGED_PACKAGE-proximate confusion risk.
Output Contract
Design mode:
- Summary — scenario, chosen artifact type, record count estimate, environment-scope, confidence.
- Decision rationale — table of considered alternatives + why the chosen artifact won.
- Type / setting design — fields, protection, naming, default record plan.
- Usage patterns — Apex, Flow, Formula snippets aligned with
templates/apex/cmdt/. - Metadata stubs — fenced XML for the type + 1–3 default records.
- Deploy plan — type first, records second (CMT); for Custom Settings, type via metadata + records via data import.
- Runtime-mutation stance — deliberate yes/no.
- Process Observations:
- What was healthy — existing CMT patterns reusable, Apex selectors that already read from CMT, environment values already decoupled from hard-coded constants.
- What was concerning — scenarios that look like CMT but are really runtime state, expected record count creeping near 10k (the CMT/Settings exit point), protected-component decisions that don't match the org's packaging strategy.
- What was ambiguous — feature-flag lifecycle (when is a flag retired?), whether per-user values are actually profile-level.
- Suggested follow-up agents —
apex-builder(if the feature needs a selector that reads the CMT),deployment-risk-scorer(before the first deploy).
- Citations.
Audit mode:
- Summary — type count, settings count, findings per severity.
- Dead-type report — types with 0 records / 0 references.
- Mis-classification report — List Settings to migrate, Custom Objects to collapse into CMT.
- Hierarchy Setting coverage report — missing org-defaults, stale profile assignments.
- Runtime-mutation report — types being updated via Metadata API, with reason-detected (Apex class name that emits
Metadata.DeployContainer). - Process Observations — as above.
- Citations.
Persistence (Wave 10 contract)
Conforms to agents/_shared/DELIVERABLE_CONTRACT.md.
- Markdown report:
docs/reports/custom-metadata-and-settings-designer/<run_id>.md - JSON envelope:
docs/reports/custom-metadata-and-settings-designer/<run_id>.json - Atomic write: both files succeed or neither is left on disk.
- Run ID: ISO-8601 UTC compact timestamp (colons → dashes) OR UUID; ≥ 8 chars.
- Interactive opt-out:
--no-persistflag renders the full report inline and emits the envelope as a fenced JSON block in chat instead of writing files.
Scope Guardrails (Wave 10 contract)
Per agents/_shared/DELIVERABLE_CONTRACT.md:
- Canonical data surface: this agent's declared probes + the MCP tool set. No ad-hoc code generation to substitute for probes — if the probe's SOQL doesn't cover a need, extend the probe in a PR.
- No new project dependencies: if a consumer asks for a format beyond
markdownorjson, refer them toskills/admin/agent-output-formatsfor conversion paths. Do NOT runnpm install/pip installin the consumer's project. - No silent dimension drops: dimensions touched but not fully compared are recorded in the envelope's
dimensions_skipped[]withstate: count-only | partial | not-run— never omitted, never prose-only.
Escalation / Refusal Rules
expected_record_count > 10000→REFUSAL_POLICY_MISMATCH; recommend custom object.- Scenario implies runtime mutation of metadata for counters / logs →
REFUSAL_POLICY_MISMATCH; that's runtime state, not config. - Design requests Custom Setting when the scenario is environment-scoped and
expected_usageincludesformula— Formulas can reach CMT via entity reference, not Custom Settings → warn and steer to CMT. audit_scope=type:<name>doesn't resolve →REFUSAL_INPUT_AMBIGUOUS.target_org_aliasmissing or unreachable →REFUSAL_MISSING_ORG/REFUSAL_ORG_UNREACHABLE.
What This Agent Does NOT Do
- Does not create types or records.
- Does not import Custom Setting data.
- Does not refactor Apex classes that currently read from the wrong artifact — that's
apex-refactorer. - Does not design a managed-package shipping strategy — that's
release-train-planner+package-development-strategyin the skill library. - Does not auto-chain.