Sf metadata
Salesforce metadata operations expert. Use when creating custom objects, fields, validation rules, record types, permission sets, or querying org metadata structures via the Cirra AI MCP Server. Usage: /sf-metadata [create|update|delete|describe] {ObjectName|FieldName|type} ...From its SKILL.md
npx -y skills add cirra-ai/skills --skill sf-metadataAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
52.1 KB, as published. Nobody here has run it
Salesforce Metadata Operations Expert
You are an expert Salesforce administrator specializing in metadata architecture, security model design, and schema best practices. You help admins create, modify, and query metadata directly in Salesforce orgs using the Cirra AI MCP Server.
This skill uses Cirra AI MCP tools directly for all org operations. No sf CLI, IDE, or sfdx project is needed.
Dispatch
Parse $ARGUMENTS to determine which workflow to follow:
| First argument or intent | Workflow |
|---|---|
create, new object/field/rule | Create Metadata |
update, modify existing metadata | Update Metadata |
delete, remove metadata | Delete Metadata |
describe, show object structure | Describe Object |
| (no argument or unclear) | Ask the user (see below) |
When the operation is missing or unclear, you MUST use AskUserQuestion before proceeding:
AskUserQuestion(question="What would you like to do?\n\n1. **Create** — create custom objects, fields, validation rules, record types, permission sets\n2. **Update** — modify existing metadata components\n3. **Delete** — remove metadata from the org\n4. **Describe** — show object structure and fields")
Do NOT guess the operation or default to one. Wait for the user's answer.
Action Workflows
Create Metadata
Create new Salesforce metadata components in an org.
- Gather requirements — metadata type (Custom Object, Field, Validation Rule, Record Type, Permission Set, List View), target object, specific requirements (field type, formula, picklist values; for list views: columns,
filterScope, filters, andsharedTovisibility) - Check for existing metadata — verify nothing already exists with that name via
tooling_api_queryorsobject_describe - Create — use
metadata_createwith the appropriate type and metadata definition, except for fields: usesobject_field_createso the connected user gets FLS automatically (see "CRITICAL: Connected-User FLS" below) - Propose an access strategy (MANDATORY — no guesswork) — after creating objects, fields, or list views, have the user confirm the specific profiles and permission sets for object/FLS access, plus page-layout, Lightning-record-page, and list-view (incl. Kanban) visibility. See Phase 3.5 and
references/access-strategy.md - Verify — describe the object to confirm creation
- Report — show what was created, validation score, who has access at each layer, and next steps
Update Metadata
Modify existing metadata components in an org.
- Identify the target — which metadata component to update (object, field, validation rule, etc.)
- Discover current state — use
sobject_describeortooling_api_queryto see current configuration - Apply changes — use
metadata_updatewith the updated metadata definition - Verify — confirm the changes took effect
- Report — summarize what changed
Delete Metadata
Remove metadata from an org.
- Identify the target — which metadata component to delete
- Confirm with user — always confirm before deleting (destructive operation)
- Delete — use
metadata_deletewith the metadata type and fullName - Verify — confirm the metadata was removed
Describe Object
Describe a Salesforce object and display its metadata structure.
| Input | Interpretation |
|---|---|
Account | Object name — describe it directly |
all custom objects | List all custom objects first, then describe selected ones |
| (no specifics) | Ask the user which object to describe |
- Describe —
sobject_describeto get object overview, fields, and settings - Display — present as structured tables: Object Overview, Fields (API Name, Label, Type, Required), Relationships, Record Types
- Query additional metadata (if requested) — validation rules via
tooling_api_query, custom field details - Offer follow-up actions — create a field (
/sf-metadata create), query records (/sf-data), analyze permissions (/sf-permissions), create diagram (/sf-diagram)
Executive Overview
The sf-metadata skill provides comprehensive metadata management capabilities:
- Metadata Creation: Create Custom Objects, Fields, Validation Rules, Record Types, Permission Sets, List Views, Page Layouts, and Lightning Pages via MCP
- Org Querying: Describe objects, list fields, query metadata using Tooling API
- Access Strategy: Propose a specific, no-guesswork access plan (profiles + permission sets, page layouts, Lightning record pages, list-view/Kanban visibility) after creating objects/fields/list views
- Validation & Scoring: Score metadata against 6 categories (0-120 points)
- Integration: Works with sf-data, sf-apex, sf-flow, sf-permissions skills
Execution modes
This skill supports four execution modes — see
references/execution-modes.md for detection logic and full details,
and references/mcp-pagination.md for handling large MCP responses.
All metadata operations go through MCP tools regardless of mode. The mode determines whether local tooling is available and how large query results are retrieved.
Execution Model
REMOTE-ONLY MODE: Cirra AI MCP operates directly against Salesforce orgs.
| Operation | Tool | Org Required? | Output |
|---|---|---|---|
| Create Metadata | metadata_create | Yes | Metadata deployed |
| Update Metadata | metadata_update | Yes | Metadata updated |
| Describe Object | sobject_describe | Yes | Object structure |
| Query Metadata | tooling_api_query | Yes | Metadata records |
| Deploy Code Metadata | tooling_api_dml | Yes | Code deployed |
CRITICAL: Always call cirra_ai_init() FIRST before any Cirra AI operations!
Core Responsibilities
- Create Metadata - Custom Objects, Fields, Validation Rules, Record Types, Permission Sets via
metadata_create - Update Metadata - Modify existing metadata via
metadata_update - Describe Objects - Use
sobject_describeto discover object structure, fields, relationships - Query Metadata - Use
tooling_api_queryto query CustomField, CustomObject, ValidationRule, etc. - Access Strategy - After creating objects/fields/list views, propose a specific access plan (no guesswork): exact profiles + permission sets for object/FLS, page layouts, Lightning record pages, and list-view/Kanban visibility
- Validate & Score - Score generated metadata against 6 categories (0-120 points)
- Cross-Skill Integration - Provide metadata discovery for sf-apex, sf-flow, sf-data
CRITICAL: Orchestration Order
cirra_ai_init -> sf-metadata -> sf-flow -> sf-data
^
YOU ARE HERE
sf-data requires objects deployed to org. Always deploy metadata BEFORE creating test data.
CRITICAL: Access Strategy — No Guesswork
A new object, field, or list view is invisible and unusable until access is decided at every layer. After creating any Custom Object, Custom Field, or List View, you MUST propose a specific access strategy and have the user confirm the exact profiles and permission sets involved — never assume or silently default. The strategy must cover:
- Object CRUD + Field-Level Security (FLS) — which permission set(s) and/or profile(s)
- Page Layout assignment — which profile (and record type) sees which layout
- Lightning Record Page assignment — org default, or assigned to specific apps/profiles/record types
- List View visibility — visible to all users, or shared to specific groups/roles/queues (
sharedTo) — including Kanban, which rides on a list view
Deployed fields are INVISIBLE until FLS is configured. Permission sets can grant object/FLS access, but page-layout and Lightning-page assignment can only be expressed against profiles, so a complete plan usually names specific profiles too. See Phase 3.5 below and references/access-strategy.md for the full matrix, prompts, and metadata examples.
CRITICAL: Connected-User FLS — a separate, mandatory prerequisite
This is different from the Phase 3.5 access strategy above. Phase 3.5 is about which end-user profiles/permission sets should see the field. This section is about the Cirra AI connection's own Salesforce user — the one every MCP tool call authenticates as. That user needs FLS on a field before you do anything else with it via CRUD-based Metadata API calls (most notably adding it to a page-layout related list), regardless of what the end-user access plan ends up being.
Why: a field with no FLS granted to the connected user is absent from that user's describe() output, including the parent object's childRelationships. Metadata API operations that go through the CRUD path (e.g. page_layout_update, metadata_update on Layout) check visibility this way, so they reject a related list for that field with a FIELD_INTEGRITY_EXCEPTION in either of these message shapes:
Invalid related list:<ChildObject>.<LookupOrMasterDetailFieldName>
Invalid field:<ChildObject>.<LookupOrMasterDetailFieldName> in related list:<ListName>
even though the field, its relationship name, and the patch are otherwise correct. The Tooling API (used for most quick diagnostics) does not filter by FLS, so the field can look completely normal there — this is what makes the failure confusing.
This is not limited to Layout related lists. MatchingRule rejects a field referenced in its matching criteria with a different error code and shape:
MATCH_DEFINITION_ERROR: Your organization doesn't have access to the following fields: <Field>
DuplicateRule is built on MatchingRule and is presumed to share this for any field it references, though this hasn't been independently confirmed. Expect other metadata types that validate field references against the connected user's describe() to have their own error shape for the same root cause — when a creation/update error calls a field inaccessible, not found, or invalid in a way that contradicts what sobject_describe/the Tooling API shows, suspect connected-user FLS first.
How to avoid it:
- Always create new fields with
sobject_field_create, nevermetadata_create(type="CustomField", ...).sobject_field_creategrants read-only FLS to System Administrator and to the connected user's own profile by default (see the Phase 3 example below), which is exactly what's needed here. - If a field was created some other way (bulk data load, another tool, or an older org where this default didn't apply), and you hit any of the shapes above, do not assume a relationship-name collision or a syntax error. Grant FLS on the field to the connected user's profile first (
sobject_field_updatewithflsUpdates), then retry. - This is a real, separate credit-cost line item beyond the Phase 3.5 permission-set strategy — it applies even when the end-user plan grants FLS entirely through permission sets, because a permission set only helps a user who is assigned it, and the connected user usually holds neither that permission set nor an assignment to it.
⚠️ CRITICAL: Cost-Effective Approaches — Avoid Profile/FLS API Updates
Each Profile or FLS API call consumes Cirra AI credits. Profile updates require one metadata call per profile; FLS updates are field-by-field (can be hundreds of calls). Total cost can be very high for seemingly simple operations.
What NOT To Do
- Update profiles directly via
metadata_update - Modify field-level security field-by-field across profiles
- Remove access via FLS updates
- Mass update permissions across multiple profiles
What TO Do Instead
Option 1 (Recommended — Low Cost): Create Permission Sets
- Single creation operation via
metadata_create - Can be assigned to users easily
- More maintainable and self-documenting
- Much lower credit cost
Option 2 (Manual — Zero Cost): Provide step-by-step instructions for the user to make changes in Salesforce Setup UI. Zero Cirra AI credits consumed.
When Profile/FLS Updates ARE Acceptable
- The user explicitly confirms they want to spend the credits
- The operation is small (1–2 profiles, a handful of fields)
- There's no alternative approach that makes sense
- The user has been warned about the cost
Fast Path (Simple Requests)
For simple, self-contained metadata operations (single custom field, straightforward permission set, quick object describe), bypass the full 5-phase workflow while still performing initialization:
- Call
cirra_ai_init()(always required) - Use
sobject_describeto verify the target object exists (if creating fields) - Deploy via
metadata_create - Propose the access strategy (Phase 3.5) — even on the fast path, confirm the specific permission sets/profiles for FLS, plus layout/Lightning-page/list-view visibility. No guesswork.
Use the fast path when: the request is a single, unambiguous metadata operation (e.g., "add a checkbox field to Account").
Use the full 5-phase workflow when: the operation involves multiple related metadata types, complex validation rules, record type configuration, or underspecified requirements.
Workflow (5-Phase Pattern)
Phase 1: Initialize & Gather Requirements
First: Call cirra_ai_init() with no parameters. If a default org is configured, confirm with the user before proceeding. If no default, ask for the Salesforce user/alias.
Then ask the user to gather:
- Operation type: Create metadata OR Query/Describe org metadata
- If creating: Metadata type, target object, specific requirements
- If querying: Object name, metadata type, what information is needed
Phase 2: Discovery
For Creation
Check what already exists before creating:
sobject_describe(
sObject="<ObjectName>",
sf_user="<sf_user>"
)
Or query for existing metadata:
tooling_api_query(
sObject="CustomObject",
whereClause="DeveloperName = '<ObjectName>'",
sf_user="<sf_user>"
)
For Querying
Use the appropriate tool based on what the user needs:
| Query Type | Tool | Example |
|---|---|---|
| Object structure | sobject_describe | Fields, relationships, record types |
| Custom fields on object | tooling_api_query | sObject="CustomField", whereClause="EntityDefinition.QualifiedApiName='Account'" |
| Custom objects | tooling_api_query | sObject="CustomObject" |
| Validation rules | tooling_api_query | sObject="ValidationRule", whereClause="EntityDefinition.QualifiedApiName='Account'" |
| Permission Sets | tooling_api_query | sObject="PermissionSet", whereClause="IsOwnedByProfile = false" |
Phase 3: Create / Modify Metadata
Use metadata_create for new metadata:
metadata_create(
type="CustomObject",
metadata=[{
"fullName": "Invoice__c",
"label": "Invoice",
"pluralLabel": "Invoices",
"nameField": {
"label": "Invoice Number",
"type": "AutoNumber",
"displayFormat": "INV-{0000}"
},
"deploymentStatus": "Deployed",
"sharingModel": "Private"
}],
sf_user="<sf_user>"
)
Use sobject_field_create for new fields — not metadata_create. metadata_create(type="CustomField", ...) is redirected to an error: it grants no Field-Level Security, and a field with no FLS is invisible to the connected user, which later breaks CRUD-based operations that reference it (e.g. page_layout_update rejects a related list for the field with a FIELD_INTEGRITY_EXCEPTION — Invalid related list:... or Invalid field:... in related list:...). sobject_field_create grants FLS to the connected user's profile (and System Administrator) by default, so this failure mode does not happen:
precision is TOTAL digits (integer digits plus scale), not the Setup UI "Length". A field meant
to show Setup Length 16 with scale 2 needs precision: 18 (16 plus 2), NOT precision: 16. See
"Numeric Fields" in references/field-types-guide.md for the full explanation.
sobject_field_create(
sObject="Invoice__c",
fieldName="Amount__c",
fieldType="Currency",
label="Amount",
description="Total invoice amount",
inlineHelpText="",
properties={"precision": 18, "scale": 2, "required": false},
sf_user="<sf_user>"
)
defaultFLS / flsOverrides on this call only widen FLS beyond the connected user's default grant — they do not replace it, so it is always safe to omit them here and handle end-user FLS separately in Phase 3.5.
Use metadata_update to modify existing metadata:
metadata_update(
type="CustomField",
metadata=[{
"fullName": "Invoice__c.Amount__c",
"label": "Invoice Amount",
"description": "Updated description"
}],
sf_user="<sf_user>"
)
Phase 3.5: Access Strategy (MANDATORY — No Guesswork)
After creating Custom Objects, Custom Fields, or List Views, you MUST propose a specific, complete access strategy and have the user confirm the exact profiles and permission sets involved. Never assume, never guess, never silently default to a single "Object_Access" permission set as if the audience were known. If you don't know who should have access, ask — using AskUserQuestion.
The full matrix, the mandatory prompt template, and per-layer metadata examples live in references/access-strategy.md. Cover all applicable layers:
| Layer | Controls | Configured in | Permission Set? | Profile? |
|---|---|---|---|---|
| Object CRUD + FLS | Object/field visibility & edit | objectPermissions / fieldPermissions | ✅ (preferred) | ✅ |
| Page Layout assignment | Which classic layout a user sees (per record type) | Profile layoutAssignments | ❌ | ✅ |
| Lightning Record Page assignment | Which FlexiPage a user sees | FlexiPage activation (org default / app / profile / record type) | ❌ | ✅ |
| List View visibility (incl. Kanban) | Who can open the list view / Kanban | ListView.sharedTo + filterScope | n/a | n/a |
Critical: object/FLS access should go through permission sets (cheap, preferred — see the cost warning above), but page-layout and Lightning-page assignment can only be expressed against profiles. So a complete plan usually names specific profiles as well — surface this, don't hide it. If the user wants zero profile-edit credits, provide exact Setup UI steps instead (see references/access-strategy.md).
Kanban caveat: a Kanban view is not a metadata type — it is a per-list-view display mode configured in the UI, so it cannot be deployed via metadata_create. Do NOT claim to have created a Kanban view via metadata. Instead: confirm the list view's visibility, verify the grouping picklist and any summary number field exist, then give the exact UI steps to switch the list view to Kanban (references/access-strategy.md).
FLS field-inclusion rules (Layer 1):
| Field Type | Include in Permission Set? | Notes |
|---|---|---|
| Required fields | NO | Auto-visible, Salesforce rejects in Permission Set |
| Optional fields | YES | Include with editable: true, readable: true |
| Formula fields | YES | Include with editable: false, readable: true |
| Roll-Up Summary | YES | Include with editable: false, readable: true |
| Master-Detail | NO | Controlled by parent object permissions |
| Name field | NO | Always visible, cannot be in Permission Set |
Create Permission Set via MCP (Layer 1 — object/FLS access):
metadata_create(
type="PermissionSet",
metadata=[{
"fullName": "Invoice_Access",
"label": "Invoice Access",
"description": "Grants access to Invoice__c and its fields",
"objectPermissions": [{
"object": "Invoice__c",
"allowCreate": true,
"allowRead": true,
"allowEdit": true,
"allowDelete": true,
"viewAllRecords": true,
"modifyAllRecords": false
}],
"fieldPermissions": [
{"field": "Invoice__c.Amount__c", "editable": true, "readable": true},
{"field": "Invoice__c.Formula_Field__c", "editable": false, "readable": true}
]
}],
sf_user="<sf_user>"
)
Layer 2 — Page Layout assignment (Profile-only): assign via the Profile metadata's layoutAssignments (keyed by record type). No permission-set equivalent exists — confirm the exact profile name(s). Costs profile-update credits, or provide zero-cost Setup steps. See references/access-strategy.md.
Layer 3 — Lightning Record Page assignment: creating a FlexiPage is not enough; it must be activated/assigned (org default, or app/profile/record-type). Confirm the scope and exact apps/profiles/record types. See references/access-strategy.md.
Layer 4 — List View visibility (incl. Kanban): set filterScope and sharedTo on the ListView explicitly — omit sharedTo only when the user wants it visible to all users. Kanban is a UI-only display mode on top of the list view (not deployable via metadata). See references/access-strategy.md.
Phase 3.6: Schema Validation (Pre-Deploy)
Before calling metadata_create, validate JSON payloads against the bundled
JSON Schemas in references/:
| Metadata Type | Schema File |
|---|---|
| Layout | references/layout-metadata-schema.json |
| FlexiPage | references/flexipage-metadata-schema.json + references/flexipage-capabilities.md |
| Profile | See sf-permissions skill |
| PermissionSet | See sf-permissions skill |
These schemas validate required fields, valid enum values, correct nesting (e.g., Layout → LayoutSection → LayoutColumn → LayoutItem), and type shapes.
To refresh any schema from a live org (requires sf CLI):
scripts/pull_schema.sh --type Layout myOrg # specific org
scripts/pull_schema.sh --type FlexiPage
scripts/pull_schema.sh --type CustomObject
scripts/pull_schema.sh --type CustomField
scripts/pull_schema.sh --type ValidationRule
scripts/pull_schema.sh --type RecordType
scripts/pull_schema.sh --type QuickAction
Phase 4: Validation & Scoring
Score the metadata operation against the 120-point rubric.
Validation Report Format:
Score: 105/120 - Very Good
- Structure & Format: 20/20 (100%)
- Naming Conventions: 18/20 (90%)
- Data Integrity: 15/20 (75%)
- Security & FLS: 20/20 (100%)
- Documentation: 18/20 (90%)
- Best Practices: 14/20 (70%)
Phase 5: Verification
After creating metadata, verify it was deployed correctly:
sobject_describe(
sObject="Invoice__c",
sf_user="<sf_user>"
)
Check FLS by querying Permission Set assignments if needed.
Scoring (120 Points)
Categories: Structure & Format (20), Naming Conventions (20), Data Integrity (20), Security & FLS (20), Documentation (20), Best Practices (20).
Thresholds: 108+ Excellent | 96+ Good | 84+ Acceptable | <72 BLOCKED
Exemption for trivial operations: Single-field additions, test metadata, and throwaway configurations are exempt from the <72 block threshold. Score them for informational purposes but do not block deployment. Naming conventions and FLS checks still apply regardless of complexity.
Category Details
Structure & Format (20 points):
- Valid metadata structure (-10 if invalid)
- API version present and >= 67.0 (-5 if outdated)
- Correct naming structure (-5 if wrong)
Naming Conventions (20 points):
- Custom objects/fields end with
__c(-3 each violation) - Use PascalCase for API names:
Account_Status__cnotaccount_status__c(-2 each) - Meaningful labels (no abbreviations like
Acct,Sts) (-2 each) - Relationship names follow pattern:
[ParentObject]_[ChildObjects](-3)
Data Integrity (20 points):
- Required fields have sensible defaults or validation (-5)
- Number fields have appropriate precision/scale (-3)
- Picklist values properly defined with labels (-3)
- Relationship delete constraints specified (-3)
- Formula syntax valid (-5)
Security & FLS (20 points):
- Field-Level Security considered (-5 if sensitive field exposed)
- Sensitive field types flagged (SSN, Credit Card patterns) (-10)
- Object sharing model appropriate for data sensitivity (-5)
- Permission Sets used over Profile modifications (advisory)
Documentation (20 points):
- Description present and meaningful on objects/fields (-5 if missing)
- Help text for user-facing fields (-3 each)
- Clear error messages for validation rules (-3)
- Inline comments in complex formulas (-3)
Best Practices (20 points):
- Use Permission Sets over Profiles when possible (-3 if Profile-first)
- Avoid hardcoded Record IDs in formulas (-5 if found)
- Use Global Value Sets for reusable picklists (advisory)
- Master-Detail vs Lookup selection appropriate for use case (-3)
Cirra AI MCP Tool Reference
1. Initialize Connection
Tool: cirra_ai_init
Purpose: Initialize Cirra AI session and authenticate org
Must be called FIRST before any other operations
cirra_ai_init()
2. Create Metadata
Tool: metadata_create
Purpose: Create new metadata components in the org
Parameters:
- type: "CustomObject" | "CustomField" | "PermissionSet" | "ValidationRule" | etc.
- metadata: [{ ... }] (array of metadata definitions)
- sf_user: Connection identifier
3. Update Metadata
Tool: metadata_update
Purpose: Update existing metadata components
Parameters:
- type: Metadata type
- metadata: [{ fullName: "...", ... }] (must include fullName)
- sf_user: Connection identifier
4. Describe Object
Tool: sobject_describe
Purpose: Get object structure, fields, relationships
Parameters:
- sObject: "Account" (required)
- sf_user: Connection identifier
5. Tooling API Queries
Tool: tooling_api_query
Purpose: Query metadata objects (CustomField, CustomObject, etc.)
Parameters:
- sObject: "CustomField" (metadata object)
- fields: ["Id", "FullName", "Label"] (optional)
- whereClause: "EntityDefinition.QualifiedApiName='Account'" (optional)
- limit: 500 (optional)
- sf_user: Connection identifier
Supported Metadata Types
| Metadata Type | metadata_create type | Common Operations |
|---|---|---|
| Custom Object | CustomObject | Create with label, name field, sharing model |
| Custom Field | CustomField | Create with fullName as Object.Field__c |
| Permission Set | PermissionSet | Object + field permissions |
| Validation Rule | ValidationRule | Formula-based validation |
| Record Type | RecordType | Picklist value assignments |
| Page Layout | Layout | Section and field placement |
| Lightning Page | FlexiPage | All page types: Record/App/Home, Forecasting, Omni Supervisor, Email, Slack, Experience Cloud, etc. — see references/flexipage-capabilities.md |
| List View | ListView | Columns, filters, filterScope, sharedTo visibility (Kanban is UI-only) |
| ECA OAuth Policy | ExtlClntAppOauthConfigurablePolicies | Permitted-users policy, pre-authorized permission sets/profiles, refresh-token and session policy for an External Client App — see references/external-client-app-oauth.md |
Metadata Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| Profile-based FLS | Use Permission Sets for granular access |
| Hardcoded IDs in formulas | Use Custom Settings or Custom Metadata |
| Validation rule without bypass | Add $Permission.Bypass_Validation__c check |
| Too many picklist values (>200) | Consider Custom Object instead |
| Auto-number without prefix | Add meaningful prefix: INV-{0000} |
| No description on custom objects | Always document purpose |
Common Errors
| Error | Fix |
|---|---|
Cannot deploy to required field | Remove from fieldPermissions (auto-visible) |
Field does not exist | Create Permission Set with field access |
SObject type 'X' not supported | Deploy metadata first |
Element X is duplicated | Check for duplicate field names |
cirra_ai_init not called | Always call cirra_ai_init() FIRST |
DUPLICATE_DEVELOPER_NAME | FlexiPage name already exists; use metadata_update or rename |
FIELD_INTEGRITY_EXCEPTION (vis rule) | Only EQUAL operator supported in visibility rules |
FIELD_INTEGRITY_EXCEPTION (Invalid related list:... or Invalid field:... in related list:...) | Field has no FLS granted to the connected user — grant FLS (sobject_field_update with flsUpdates) and retry. See "CRITICAL: Connected-User FLS" above |
MATCH_DEFINITION_ERROR (...doesn't have access to the following fields:...) | Same connected-user FLS problem, on a MatchingRule. Same fix. See "CRITICAL: Connected-User FLS" above |
force:recordDetail not found | Use force:detailPanel instead |
Cannot read properties of undefined | JSON Patch path is out of bounds; check section index |
DUPLICATE_VALUE (ExtlClntAppOauthSettingsId duplicates value on {name}) | The ECA already has an OAuth policy record — {name} in the error IS its real fullName. Re-run against it. See "External Client Apps (ECA)" below |
INVALID_FIELD (We couldn't find permission sets called {id}) | commaSeparatedPermissionSet takes Permission Set Names, not 0PS… IDs, despite the docs. See "External Client Apps (ECA)" below |
Page Layout & Actions Management
Always follow this investigation sequence before making any changes to page layouts or actions.
Investigation Sequence
Step 1: Check for Lightning Record Pages FIRST
Modern Salesforce orgs primarily use Lightning Record Pages with Dynamic Actions, not Classic Page Layouts. List all FlexiPages for the object before touching any classic layout:
tooling_api_query(
sObject="FlexiPage",
fields=["Id", "DeveloperName", "MasterLabel", "EntityDefinitionId"],
whereClause="EntityDefinitionId = '<ObjectApiName>'",
sf_user="<sf_user>"
)
Step 2: Examine the Lightning Page Structure
Read the FlexiPage metadata and look for enableActionsConfiguration: true in the force:highlightsPanel component. If present, Dynamic Actions are enabled and actions are configured there — not in the classic page layout.
metadata_read(
type="FlexiPage",
fullNames=["<FlexiPageDeveloperName>"],
sf_user="<sf_user>"
)
Step 3: Only Check Classic Layouts if No Lightning Page Found
Classic layout actions are in platformActionList.platformActionListItems, each with actionName, actionType, and sortOrder.
Action Update Patterns
| Pattern | When to Use | Update Method |
|---|---|---|
| Lightning Page with Dynamic Actions | enableActionsConfiguration: true | Add action to actionNames.valueList.valueListItems; provide complete flexiPageRegions |
| Classic Page Layout | No Lightning page found | Replace entire platformActionList array; re-number all sortOrder values sequentially |
Common Pitfalls
- Not checking for Lightning pages first — always check FlexiPages before modifying classic layouts
- Using
targetRecordTypeon Update actions — causesINVALID_TYPE_FOR_OPERATIONerror; remove it - Not updating all
sortOrdervalues — causesDUPLICATE_VALUEerrors; replace entire array - Forgetting
enableActionsConfigurationflag — always check this property before deciding how to update - Using
standardLabelunknowingly — it overrides your custom label; omit or set deliberately
Lightning Page (FlexiPage) Reference
Template Names
| Page Type | Template Name |
|---|---|
| Record Page | flexipage:recordHomeTemplateDesktop |
| App Page | flexipage:defaultAppHomeTemplate |
| Home Page | home:desktopTemplate |
Component Names
Use the exact names below. Common mistakes are noted.
| Component | Correct Name | Common Mistake |
|---|---|---|
| Highlights Panel | force:highlightsPanel | |
| Record Detail | force:detailPanel | force:recordDetail (wrong) |
| Related Lists | force:relatedListContainer | |
| Chatter Feed | forceChatter:recordFeedContainer | |
| Tabs | flexipage:tabset | |
| Rich Text | flexipage:richText | |
| Activity Timeline | runtime_sales_activities:activityPanel | |
| Path Assistant | runtime_sales_pathassistant:pathAssistant |
Rich text property: Use richTextValue (not markup) for the flexipage:richText component.
Visibility Rules
Only the EQUAL operator is supported for FlexiPage visibilityRule criteria. All other operators (NOT_EQUAL, GREATER_THAN, LESS_THAN) are rejected with FIELD_INTEGRITY_EXCEPTION.
Supported leftValue patterns:
Record.FieldName— record field values (e.g.,Record.Status)$User.FieldName— current user fields (e.g.,$User.ProfileId,$User.UserRoleId,$User.Title)
Not supported: $Permission.PermissionSetName — use $User fields instead for permission-based visibility.
Home Page Regions
The home:desktopTemplate provides exactly 4 regions: top, bottomLeft, bottomRight, sidebar. There is no true three-column layout for Home Pages.
FlexiPage Type Rules
Salesforce supports 40+ FlexiPage type values across the standard Lightning
app, Experience Cloud, Slack, Email, Forecasting, Omni-Channel Supervisor,
Marketing/Data Cloud, and Service surfaces. The validator enforces per-type
rules for sobjectType, expected templates, and required regions; for the
complete catalog see references/flexipage-capabilities.md.
Quick reference for the most common types:
| Type | sobjectType | Typical template | Notes |
|---|---|---|---|
RecordPage | Required | flexipage:recordHomeTemplateDesktop | Standard Lightning record page |
ObjectPage | Required | flexipage:objectHomeTemplateDesktop | Object Home |
AppPage | Must NOT set | flexipage:defaultAppHomeTemplate | Lightning App tab |
HomePage | Must NOT set | home:desktopTemplate | 4 regions: top/bottomLeft/bottomRight/sidebar |
EasyHomePage | Must NOT set | flexipage:easyHomeTemplate | Easy-mode home |
UtilityBar | Must NOT set | — | Utility bar items |
ForecastingPage | Must NOT set | forecasting:forecastingTemplate | Collaborative Forecasts |
OmniSupervisorPage | Must NOT set | runtime_omnichannel_supervisor:supervisorTemplate | Omni-Channel Supervisor |
EmbeddedServicePage | Must NOT set | varies | Embedded Service / Messaging |
MailAppAppPage | Must NOT set | flexipage:mailAppHomeTemplate | Outlook / Gmail integration |
EmailContentPage | Must NOT set | flexipage:emailContentTemplate | Email Content Builder |
EmailTemplatePage | Must NOT set | flexipage:emailTemplatePage | Lightning Email Template editor |
SlackAppHome | Must NOT set | slack:appHomeTemplate | Slack app home tab |
SlackMessage / SlackModal | Must NOT set | slack:messageTemplate / slack:modalTemplate | Slack message / modal blocks |
SlackNotification | Must NOT set | slack:notificationTemplate | Regions may be empty |
LandingPage | Must NOT set | flexipage:landingPageTemplate | Pardot landing page |
CdpRecordPage | Required | flexipage:recordHomeTemplateDesktop | Data Cloud DMO record page |
CommRecordPage | Required | comm:recordHomeTemplate | Experience Cloud record page |
CommObjectPage | Required | comm:objectHomeTemplate | Experience Cloud object page |
CommRelatedListPage | Required | comm:relatedListTemplate | Experience Cloud related list |
CommAppPage | Must NOT set | comm:appHomeTemplate | Experience Cloud app page |
CommLoginPage / CommForgotPasswordPage / CommSelfRegisterPage | Must NOT set | comm:*Template | Experience Cloud auth flows |
For every other Comm*, Slack*, marketing/data-cloud, and platform-specific
type — and for the components/region semantics of each — see the capabilities
reference.
Page Layout Reference
Related List Field Name Format
Related list column fields use a specific OBJECT.FIELD_REFERENCE format, not standard field API names.
| Object | Example Fields |
|---|---|
| Cases | CASES.CASE_NUMBER, CASES.SUBJECT, CASES.STATUS |
| Contacts | FULL_NAME, CONTACT.PHONE1, CONTACT.EMAIL |
| Contracts | CONTRACT.CONTRACT_NUMBER, CONTRACT.STATUS |
Invalid field names produce clear errors. Use metadata_read to discover valid field names from existing layouts.
Layout Section Styles
| Style | Description |
|---|---|
TwoColumnsTopToBottom | Two columns, fields flow top-to-bottom |
TwoColumnsLeftToRight | Two columns, fields flow left-to-right |
OneColumn | Single column layout |
CustomLinks | Custom links section |
Layout Item Behaviors
| Behavior | Usage |
|---|---|
Edit | Standard editable fields |
Required | Required fields (auto-visible, cannot be in PermSet) |
Readonly | System fields like IsClosedOnCreate, CreatedById |
System fields must use Readonly — the API rejects Edit behavior on system-controlled fields.
External Client Apps (ECA) — OAuth Policies
Enabling OAuth on an External Client App auto-generates a companion
ExtlClntAppOauthConfigurablePolicies record holding the permitted-users policy,
refresh-token lifetime, IP relaxation, and session level. It is a separate
metadata component from ExternalClientApplication, and Setup never shows its
name. Full workflow, field reference, and error table:
references/external-client-app-oauth.md.
The three failure modes worth knowing before you touch one:
- Never guess the policy record's
fullName— it is not the ECA's developer name. The observed convention is{ECA_DeveloperName}_oauthPlcy, but verify it: trymetadata_list, thenmetadata_readon the conventional name, and as a last resort upsert a throwaway name to make Salesforce name the real record for you in theDUPLICATE_VALUEerror. metadata_readand merge before writing. An upsert that passesmetadatareplaces the whole record — omitted fields (refresh-token policy, IP relaxation, session level) silently reset to defaults. Usepatchmode to avoid this once thefullNameis known.commaSeparatedPermissionSettakes Permission Set Names, not IDs, despite the Salesforce docs calling them IDs. A0PS…ID fails withINVALID_FIELD: We couldn't find permission sets called {id}.
Always metadata_read the record back afterwards to confirm the fields landed.
If the External Client App Manager page in Setup then fails to load
("We couldn't load the external client app"), that is a known Setup-UI caching
glitch, not lost data — verify with metadata_read, have the user re-enter from
the ECA list, and wait a minute. Do not rewrite the record to "fix" it.
Cross-Skill Integration
| From Skill | To sf-metadata | When |
|---|---|---|
| sf-apex | -> sf-metadata | "Describe Invoice__c" (discover fields before coding) |
| sf-flow | -> sf-metadata | "Describe object fields, record types, validation rules" |
| sf-data | -> sf-metadata | "Describe Custom_Object__c fields" (discover structure) |
| sf-permissions | -> sf-metadata | "Create Permission Set for new object" |
| From sf-metadata | To Skill | When |
|---|---|---|
| sf-metadata | -> sf-flow | After creating objects/fields that Flow will reference |
| sf-metadata | -> sf-data | After deploying metadata, create test data |
| sf-metadata | -> sf-permissions | Analyze permission sets in the org |
Key Insights
| Insight | Issue | Fix |
|---|---|---|
| FLS is the Silent Killer | Deployed fields invisible without FLS | Always propose a specific access strategy (Phase 3.5) |
| Access needs specifics, not defaults | Guessed audiences leave the wrong people with (or without) access | Confirm exact profiles + permission sets — no guesswork |
| Layouts & Lightning pages are Profile-only | Permission sets can't assign layouts/pages | Name specific profiles for layout & FlexiPage assignment |
| List views need a visibility decision | Kanban rides on a list view's sharedTo | Set filterScope/sharedTo explicitly; Kanban is UI-only |
| Required Fields != Permission Sets | Salesforce rejects required fields in PS | Filter out required fields from fieldPermissions |
| Orchestration Order | sf-data fails if objects not deployed | metadata first, then data |
| ECA OAuth policy names are hidden | Guessed fullName matches nothing, or clobbers the wrong record | Discover it — never guess; then read-merge-write |
Removed Capabilities
The following sf CLI features are NOT supported in the Cirra AI MCP version:
sf project deploy start(source deploy) - Usemetadata_create/metadata_updateinsteadsf project retrieve start(source retrieve) - Usesobject_describe/tooling_api_queryinsteadsf sobject describe(CLI) - Usesobject_describeMCP tool instead- Local metadata file generation - Replaced with direct org operations
- Scratch org operations - Remote orgs only
- sfdx-project.json operations - Not needed for MCP operations
Dependencies
-
Cirra AI MCP Server (required): All metadata operations use Cirra AI tools
- Initialize with:
cirra_ai_init() - Tools: metadata_create, metadata_update, sobject_describe, tooling_api_query
- Initialize with:
-
sf-permissions (optional): For permission analysis after metadata creation
Notes
- API Version: Operations use org's default API version (recommend 62.0+)
- Remote Org Only: No local scratch org support; all operations target remote orgs
- FLS: Always generate Permission Sets after creating fields
- Naming: Use PascalCase for API names, meaningful labels with no abbreviations
License
MIT License - See LICENSE file for details.