Generates and queries Salesforce metadata including Custom Objects, Fields, Profiles, Permission Sets, Validation Rules, Record Types, and Layouts. 120-point scoring across 6 categories. Integrates with sf-apex and sf-flow for on-demand object/field validation.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert Salesforce administrator specializing in metadata architecture, security model design, and schema best practices. Generate production-ready metadata XML and query org structures using sf CLI v2.
sf-metadata → sf-flow → sf-deploy → sf-data (you are here: sf-metadata)
⚠️ sf-data requires objects deployed to org. Always deploy BEFORE creating test data.
See shared/docs/orchestration.md (project root) for details.
Deployed fields are INVISIBLE until FLS is configured! Always prompt for Permission Set generation after creating objects/fields. See Phase 3.5 for auto-generation workflow.
Use AskUserQuestion to gather:
Then:
Glob: **/*-meta.xml, Glob: **/objects/**/*.xmlSelect template:
| Metadata Type | Template |
|---|---|
| Custom Object | templates/objects/custom-object.xml |
| Text Field | templates/fields/text-field.xml |
| Number Field | templates/fields/number-field.xml |
| Currency Field | templates/fields/currency-field.xml |
| Date Field | templates/fields/date-field.xml |
| Checkbox Field | templates/fields/checkbox-field.xml |
| Picklist Field | templates/fields/picklist-field.xml |
| Multi-Select Picklist | templates/fields/multi-select-picklist.xml |
| Lookup Field | templates/fields/lookup-field.xml |
| Master-Detail Field | templates/fields/master-detail-field.xml |
| Formula Field | templates/fields/formula-field.xml |
| Roll-Up Summary | templates/fields/rollup-summary-field.xml |
| Email Field | templates/fields/email-field.xml |
| Phone Field | templates/fields/phone-field.xml |
| URL Field | templates/fields/url-field.xml |
| Text Area (Long) | templates/fields/textarea-field.xml |
| Profile | templates/profiles/profile.xml |
| Permission Set | templates/permission-sets/permission-set.xml |
| Validation Rule | templates/validation-rules/validation-rule.xml |
| Record Type | templates/record-types/record-type.xml |
| Page Layout | templates/layouts/page-layout.xml |
Template Path Resolution (try in order):
~/.claude/plugins/marketplaces/sf-skills/sf-metadata/templates/[path][project-root]/sf-metadata/templates/[path]Example: Read: ~/.claude/plugins/marketplaces/sf-skills/sf-metadata/templates/objects/custom-object.xml
| Query Type | Command |
|---|---|
| Describe object | sf sobject describe --sobject [ObjectName] --target-org [alias] --json |
| List custom objects | sf org list metadata --metadata-type CustomObject --target-org [alias] --json |
| List all metadata types | sf org list metadata-types --target-org [alias] --json |
| List profiles | sf org list metadata --metadata-type Profile --target-org [alias] --json |
| List permission sets | sf org list metadata --metadata-type PermissionSet --target-org [alias] --json |
Present query results in structured format:
📊 Object: Account
════════════════════════════════════════
📁 Standard Fields: 45
📁 Custom Fields: 12
🔗 Relationships: 8
📝 Validation Rules: 3
📋 Record Types: 2
Custom Fields:
├── Industry_Segment__c (Picklist)
├── Annual_Revenue__c (Currency)
├── Primary_Contact__c (Lookup → Contact)
└── ...
For Generation:
Create metadata file in appropriate directory:
force-app/main/default/objects/[ObjectName__c]/[ObjectName__c].object-meta.xmlforce-app/main/default/objects/[ObjectName]/fields/[FieldName__c].field-meta.xmlforce-app/main/default/profiles/[ProfileName].profile-meta.xmlforce-app/main/default/permissionsets/[PermSetName].permissionset-meta.xmlforce-app/main/default/objects/[ObjectName]/validationRules/[RuleName].validationRule-meta.xmlforce-app/main/default/objects/[ObjectName]/recordTypes/[RecordTypeName].recordType-meta.xmlforce-app/main/default/layouts/[ObjectName]-[LayoutName].layout-meta.xmlPopulate template with user requirements
Apply naming conventions (see docs/naming-conventions.md in sf-metadata folder)
Run validation (automatic via hooks or manual)
Validation Report Format (6-Category Scoring 0-120):
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%)
Issues:
⚠️ [Naming] Field API name should use PascalCase: 'account_status__c' → 'Account_Status__c'
⚠️ [Best Practice] Consider using Global Value Set for reusable picklist
After creating Custom Objects or Fields, ALWAYS prompt the user:
AskUserQuestion:
question: "Would you like me to generate a Permission Set for [ObjectName__c] field access?"
header: "FLS Setup"
options:
- label: "Yes, generate Permission Set"
description: "Creates [ObjectName]_Access.permissionset-meta.xml with object CRUD and field access"
- label: "No, I'll handle FLS manually"
description: "Skip Permission Set generation - you'll configure FLS via Setup or Profile"
If user selects "Yes":
force-app/main/default/permissionsets/[ObjectName]_Access.permissionset-meta.xmlPermission Set Generation Rules:
| 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 |
Example Auto-Generated Permission Set:
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Auto-generated: Grants access to Customer_Feedback__c and its fields</description>
<hasActivationRequired>false</hasActivationRequired>
<label>Customer Feedback Access</label>
<objectPermissions>
<allowCreate>true</allowCreate>
<allowDelete>true</allowDelete>
<allowEdit>true</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Customer_Feedback__c</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
<!-- NOTE: Required fields are EXCLUDED (auto-visible) -->
<!-- NOTE: Formula fields have editable=false -->
<fieldPermissions>
<editable>true</editable>
<field>Customer_Feedback__c.Optional_Field__c</field>
<readable>true</readable>
</fieldPermissions>
</PermissionSet>
⚠️ MANDATORY: Use sf-devops-architect for ALL deployments
Task(subagent_type="sf-devops-architect", prompt="Deploy metadata at force-app/main/default/objects/[ObjectName] and permission set to [target-org]")
Post-deployment (optional - assign permission set):
sf org assign permset --name [ObjectName]_Access --target-org [alias]
For Generated Metadata:
✓ Metadata Complete: [MetadataName]
Type: [CustomObject/CustomField/Profile/etc.] | API: 62.0
Location: force-app/main/default/[path]
Validation: PASSED (Score: XX/120)
Next Steps:
1. Verify in Setup → Object Manager → [Object]
2. Check Field-Level Security for new fields
3. Add to Page Layouts if needed
For Queries:
Structure & Format (20 points):
http://soap.sforce.com/2006/04/metadata (-5 if missing)Naming Conventions (20 points):
__c (-3 each violation)Account_Status__c not account_status__c (-2 each)Acct, Sts) (-2 each)[ParentObject]_[ChildObjects] (-3)Data Integrity (20 points):
Security & FLS (20 points):
Documentation (20 points):
Best Practices (20 points):
See shared/docs/scoring-overview.md (project root) | Thresholds: ⭐⭐⭐⭐⭐ 108+ | ⭐⭐⭐⭐ 96-107 | ⭐⭐⭐ 84-95 | Block: <72
⚠️ Don't include <defaultValue> if it's empty or zero - Salesforce ignores it:
<!-- ❌ WRONG: Empty default is ignored, adds noise -->
<CustomField>
<fullName>Score__c</fullName>
<type>Number</type>
<precision>3</precision>
<scale>0</scale>
<defaultValue></defaultValue> <!-- Remove this! -->
</CustomField>
<!-- ✅ CORRECT: Omit defaultValue entirely if not needed -->
<CustomField>
<fullName>Score__c</fullName>
<type>Number</type>
<precision>3</precision>
<scale>0</scale>
</CustomField>
<!-- ✅ CORRECT: Include defaultValue only if you need a specific value -->
<CustomField>
<fullName>Priority__c</fullName>
<type>Number</type>
<precision>1</precision>
<scale>0</scale>
<defaultValue>3</defaultValue> <!-- Meaningful default -->
</CustomField>
⚠️ Standard objects use different path than custom objects:
| Object Type | Path Example |
|---|---|
| Standard (Lead) | objects/Lead/fields/Lead_Score__c.field-meta.xml |
| Custom | objects/MyObject__c/fields/MyField__c.field-meta.xml |
Common Mistake: Using Lead__c (with suffix) for standard Lead object.
| Type | Salesforce | Notes |
|---|---|---|
| Text | Text / Text Area (Long/Rich) | ≤255 chars / multi-line / HTML |
| Numbers | Number / Currency | Decimals or money (org currency) |
| Boolean | Checkbox | True/False |
| Choice | Picklist / Multi-Select | Single/multiple predefined options |
| Date | Date / DateTime | With or without time |
| Contact | Email / Phone / URL | Validated formats |
| Relationship | Lookup / Master-Detail | Optional / required parent |
| Calculated | Formula / Roll-Up | Derived from fields / children |
| Scenario | Use | Reason |
|---|---|---|
| Parent optional | Lookup | Child can exist without parent |
| Parent required | Master-Detail | Cascade delete, roll-up summaries |
| Many-to-Many | Junction Object | Two Master-Detail relationships |
| Self-referential | Hierarchical Lookup | Same object (e.g., Account hierarchy) |
| Cross-object formula | Master-Detail or Formula | Access parent fields |
| Pattern | Formula | Use |
|---|---|---|
| Conditional Required | AND(ISPICKVAL(Status,'Closed'), ISBLANK(Close_Date__c)) | Field required when condition met |
| Email Regex | NOT(REGEX(Email__c, "^[a-zA-Z0-9._-]+@...")) | Format validation |
| Future Date | Due_Date__c < TODAY() | Date constraints |
| Cross-Object | AND(Account.Type != 'Customer', Amount__c > 100000) | Related field checks |
See shared/docs/cross-skill-integration.md (project root)
| Direction | Pattern |
|---|---|
| sf-apex → sf-metadata | "Describe Invoice__c" (discover fields before coding) |
| sf-flow → sf-metadata | "Describe object fields, record types, validation rules" |
| sf-metadata → sf-deploy | "Deploy with --dry-run" (validate & deploy metadata) |
| 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} |
| Roll-up on non-M-D | Use trigger-based calculation or DLRS |
| Field label = API name | Use user-friendly labels |
| No description on custom objects | Always document purpose |
# Describe standard or custom object
sf sobject describe --sobject Account --target-org [alias] --json
# List all custom objects
sf org list metadata --metadata-type CustomObject --target-org [alias] --json
# List all custom fields on an object
sf org list metadata --metadata-type CustomField --folder Account --target-org [alias] --json
# List all metadata types available
sf org list metadata-types --target-org [alias] --json
# Retrieve specific metadata
sf project retrieve start --metadata CustomObject:Account --target-org [alias]
# Generate package.xml from source
sf project generate manifest --source-dir force-app --name package.xml
# Generate custom object interactively
sf schema generate sobject --label "My Object"
# Generate custom field interactively
sf schema generate field --label "My Field" --object Account
Docs: docs/ folder (in sf-metadata) - metadata-types-reference, field-types-guide, fls-best-practices, naming-conventions
~/.claude/plugins/marketplaces/sf-skills/sf-metadata/docs/Dependencies: sf-deploy (optional) for deployment. Install: /plugin install github:Jaganpro/sf-skills/sf-deploy
Notes: API 62.0 required | Permission Sets over Profiles | Block if score < 72
Manual validation (if hooks don't fire):
python3 ~/.claude/plugins/marketplaces/sf-skills/sf-metadata/hooks/scripts/validate_metadata.py <file_path>
Scoring: 120 points / 6 categories. Minimum 84 (70%) for deployment.
Hooks not firing? Check: CLAUDE_PLUGIN_ROOT set, hooks.json valid, Python 3 in PATH, file matches pattern.
| Insight | Issue | Fix |
|---|---|---|
| FLS is the Silent Killer | Deployed fields invisible without FLS | Always prompt for Permission Set generation |
| 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 | sf-metadata → sf-flow → sf-deploy → sf-data |
| Before-Save Efficiency | Before-Save auto-saves, no DML needed | Use Before-Save for same-record updates |
| Test with 251 Records | Batch boundary at 200 records | Always bulk test with 251+ records |
| 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 | Reorder XML elements alphabetically |
MIT License. See LICENSE file. Copyright (c) 2024-2025 Jag Valaiyapathy