This article is part of the Salesforce Admin Git & sf CLI series, a complete guide to version control and metadata management for Salesforce admins. Articles are standalone; no need to read in order.
TL;DR
- Retrieve all Profiles with
sf project retrieve start --metadata Profile --target-org myorg, but expect it to be slow and produce very large files - Profile XML files are huge: a single Profile can be 5,000–50,000 lines long, covering field-level security for every object and field in your org
- Retrieve one specific Profile:
sf project retrieve start --metadata "Profile:Admin" --target-org myorg(the recommended approach for day-to-day work) - Always list first:
sf org list metadata --metadata-type Profile --target-org myorgto get the exact API names before retrieving
What You'll Learn
- How to retrieve Profile, PermissionSet, PermissionSetGroup, MutingPermissionSet, and Role metadata
- Why Profiles are the most complex metadata type to version-control
- How to retrieve a single Profile instead of all at once (partial retrieval)
- The difference between source format and MDAPI format for security metadata
- How to use
package.xmlfor repeatable multi-type security metadata retrieves - What these files look like on disk and why they're large
The Problem
Security metadata is the most risky to get wrong and the hardest to track manually. When a Profile changes (someone adds a field permission, tweaks object access, adds a tab) there's no audit trail unless you're version-controlling it. This article shows you how.
Common questions this article answers:
- How do I retrieve a Salesforce Profile using sf CLI?
- Why is my Profile XML file so large?
- How do I retrieve just one PermissionSet?
- Can I version-control Roles and Permission Set Groups?
- What is a MutingPermissionSet?
Quick Answer
If you just need the commands now:
# See all Profile names in your org
sf org list metadata --metadata-type Profile --target-org myorg
# Retrieve one specific Profile (recommended)
sf project retrieve start --metadata "Profile:Admin" --target-org myorg
# Retrieve one specific PermissionSet
sf project retrieve start --metadata "PermissionSet:My_Permission_Set" --target-org myorg
Always list before you retrieve. The API name sf CLI needs is not always what you see in Setup. A Profile labelled "Sales User" might have an API name of Sales_User or something entirely different set at creation time; only the list command shows you the exact value to use in your retrieve flags. For Profiles especially, retrieve one at a time rather than all at once: a single Profile can be tens of thousands of lines, and pulling every Profile in one command can take several minutes, produce files too large to review, and make it harder to isolate what actually changed. Fetching one Profile per command keeps retrieves fast and diffs readable.
The rest of this article explains the details: listing components, handling large files, MDAPI vs source format, and using package.xml for repeatable retrieves.
Section 1: List Security Components First
Before retrieving, list what's in your org so you know the exact API names to use. sf CLI requires API names, not display labels.
# List all Profiles
sf org list metadata --metadata-type Profile --target-org myorg
# List all PermissionSets
sf org list metadata --metadata-type PermissionSet --target-org myorg
# List PermissionSetGroups
sf org list metadata --metadata-type PermissionSetGroup --target-org myorg
# List MutingPermissionSets
sf org list metadata --metadata-type MutingPermissionSet --target-org myorg
# List Roles
sf org list metadata --metadata-type Role --target-org myorg
The fullName value in the output is what you use in your retrieve commands. For Profiles, fullName is the Profile name exactly as it appears: Admin, Standard, or for custom profiles, the name as set in Setup. For PermissionSets, it's the API name of the permission set (not the label). For Roles, it's the developer name (API name), not the display label you see in Salesforce Setup.
Managed package components: Security components from installed managed packages carry a namespace prefix (e.g.,
mypkg__Custom_PS). These can sometimes be retrieved for inspection, but managed package Profiles and PermissionSets are locked by the package provider. You cannot modify or redeploy them. Do not include namespace-prefixed security metadata in your version-controlled source as if it were your own customisation.
Section 2: Retrieve Profile Metadata
Profiles are the most complex metadata type in Salesforce. A single Profile stores field-level security for every field, object permissions for every object, app permissions, tab settings, page layout assignments, record type assignments, and more.
# Retrieve ALL Profiles (warning: this can be very slow and produce large files)
sf project retrieve start --metadata Profile --target-org myorg
# Retrieve a specific Profile (much faster; strongly recommended)
sf project retrieve start --metadata "Profile:Admin" --target-org myorg
# Retrieve multiple specific Profiles
sf project retrieve start \
--metadata "Profile:Admin" \
--metadata "Profile:Standard" \
--target-org myorg
Important note about Profile size: Profile XML files can be 5,000–50,000 lines long. Each Profile contains field-level security for every field in your org, object permissions, app permissions, tab settings, page layout assignments, record type assignments, and more. Retrieving all Profiles at once can take several minutes and produce files too large for a code review tool. For day-to-day work, retrieve specific Profiles by name.
The
--metadataflag accepts the formatMetadataType:ApiName. This works the same on Windows and macOS. sf CLI commands are cross-platform.
Section 3: Retrieve PermissionSet, PermissionSetGroup, and MutingPermissionSet
PermissionSets are typically more manageable than Profiles. They only store what they grant, so files are smaller and more readable.
# Retrieve ALL PermissionSets
sf project retrieve start --metadata PermissionSet --target-org myorg
# Retrieve a specific PermissionSet
sf project retrieve start --metadata "PermissionSet:My_Permission_Set" --target-org myorg
# Retrieve PermissionSetGroups
sf project retrieve start --metadata PermissionSetGroup --target-org myorg
# Retrieve a specific PermissionSetGroup
sf project retrieve start --metadata "PermissionSetGroup:My_Group" --target-org myorg
# Retrieve MutingPermissionSets
sf project retrieve start --metadata MutingPermissionSet --target-org myorg
# Retrieve multiple types at once
sf project retrieve start \
--metadata PermissionSet \
--metadata PermissionSetGroup \
--metadata MutingPermissionSet \
--target-org myorg
What is a MutingPermissionSet? A MutingPermissionSet is a special permission set that removes permissions rather than granting them. It is assigned inside a PermissionSetGroup to suppress specific permissions that other permission sets in the group would otherwise grant. It cannot be assigned directly to users. It only works within a PermissionSetGroup.
Section 4: Retrieve Role Metadata
Roles control record visibility through the role hierarchy. They're simpler than Profiles: smaller files, fewer edge cases.
# Retrieve ALL Roles
sf project retrieve start --metadata Role --target-org myorg
# Retrieve a specific Role
sf project retrieve start --metadata "Role:CEO" --target-org myorg
Role names in the Metadata API use the developer name (API name), not the label. If you see a Role called "Chief Executive Officer" in Setup, its API name might be CEO or Chief_Executive_Officer. Check Setup → Roles → click the role to see its API name before retrieving.
Section 5: Retrieve Into a Custom Directory (Source Format)
By default, sf project retrieve start places files inside the paths defined in your sfdx-project.json. To retrieve into a separate directory (useful for backups or comparisons), use --output-dir.
# Retrieve security metadata into a custom directory (source format)
sf project retrieve start \
--metadata Profile \
--target-org myorg \
--output-dir ./security-backup
Files are placed in source format inside the specified directory. The directory structure mirrors what you'd get in your project: profiles/, permissionsets/, etc.
Section 6: Retrieve in MDAPI Format
Source format (.profile-meta.xml) is better for version control. MDAPI format produces the older file extensions (.profile, .permissionset) that the Metadata API has always used. You might need MDAPI format for legacy tooling or deployments that expect it.
# Retrieve Profile in MDAPI format
sf project retrieve start \
--metadata Profile \
--target-org myorg \
--target-metadata-dir mdapi-output \
--unzip
# Retrieve PermissionSet in MDAPI format
sf project retrieve start \
--metadata PermissionSet \
--target-org myorg \
--target-metadata-dir mdapi-output \
--unzip
--target-metadata-dir tells sf CLI to produce a ZIP file in MDAPI format. --unzip extracts it automatically. The key difference:
| Format | Profile extension | PermissionSet extension |
|---|---|---|
| Source format | .profile-meta.xml |
.permissionset-meta.xml |
| MDAPI format | .profile |
.permissionset |
For new projects and git-based workflows, source format is the right choice.
Section 7: Retrieve Using package.xml
package.xml gives you a repeatable, version-controlled retrieve specification. Instead of typing long --metadata flags every time, commit the package.xml and run one command.
sf project retrieve start --manifest package.xml --target-org myorg
Full package.xml for security metadata:
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Admin</members>
<members>Standard</members>
<name>Profile</name>
</types>
<types>
<members>*</members>
<name>PermissionSet</name>
</types>
<types>
<members>*</members>
<name>PermissionSetGroup</name>
</types>
<types>
<members>*</members>
<name>MutingPermissionSet</name>
</types>
<types>
<members>*</members>
<name>Role</name>
</types>
<version>62.0</version>
</Package>
Note the Profile section: it lists specific Profile names (Admin, Standard) rather than *. Using * for Profile retrieves every profile in your org, which is usually not what you want and will be slow. Name the specific profiles you care about tracking. For PermissionSet, PermissionSetGroup, and Role, * is safe and practical.
Section 8: What Files Look Like on Disk
After a retrieve, your project directory will contain:
force-app/
└── main/
└── default/
├── profiles/
│ ├── Admin.profile-meta.xml ← can be 10,000+ lines
│ └── Standard.profile-meta.xml
├── permissionsets/
│ └── My_Permission_Set.permissionset-meta.xml
├── permissionsetgroups/
│ └── My_Group.permissionsetgroup-meta.xml
└── roles/
└── CEO.role-meta.xml
Profile files are large because they store the entire security configuration for every object and field the Profile has access to: one XML entry per field permission, per object permission, per tab visibility, per record type assignment. A Profile file for an org with 100 custom objects and 1,000 custom fields can easily exceed 50,000 lines.
PermissionSet files are typically much smaller. They only store what they grant. If a permission set grants access to 20 fields, the file has 20 fieldPermissions entries. It doesn't carry the full deny-everything-else context that Profiles do.
Troubleshooting
Profile retrieve times out
Large orgs with many objects and fields produce enormous Profile XML. Retrieve specific profiles one at a time (--metadata "Profile:Admin") rather than all at once. If timeouts persist, check whether your org has hundreds of custom objects, as each one adds entries to every Profile file.
"Insufficient privileges" error Retrieving security metadata requires the "View Setup and Configuration" permission. Verify your running user has this enabled on their Profile before retrieving.
Profile retrieved but field-level security is missing sf CLI only retrieves field-level security for fields that exist as CustomField metadata. Standard field FLS is not stored as retrievable metadata. It won't appear in the XML. This is a Metadata API limitation, not an sf CLI issue.
MutingPermissionSet not found
MutingPermissionSet was introduced in API version 53.0. If your sfdx-project.json uses an older sourceApiVersion, MutingPermissionSets won't appear in list or retrieve results. Open sfdx-project.json and update sourceApiVersion to 53.0 or higher, then retry.
Role API name vs label confusion
Role metadata uses the developer name (API name), not the display label shown in Salesforce. The safest way to find the exact API name is to run sf org list metadata --metadata-type Role --target-org myorg. The fullName column shows the developer API name used by sf CLI.
Frequently Asked Questions
Why are Profile files so large?
Profiles contain field-level security for every custom field in the org, object permissions for every custom and standard object, and tab visibility settings for every app. A large org with 100 custom objects and 1,000 custom fields will produce Profile files with tens of thousands of XML lines: one entry per field per object per Profile. This is by design in the Metadata API; there's no way to retrieve a subset of a Profile's contents.
Should I version-control Profiles or use PermissionSets instead?
Both, if you use both. Salesforce best practice is to assign access via PermissionSets rather than Profiles. Profiles should grant the minimum, and PermissionSets add what's needed. If you're migrating from Profile-centric security to PermissionSets, version-controlling both during the transition is essential for tracking what changed and when.
What's the difference between PermissionSet and PermissionSetGroup?
A PermissionSet grants specific permissions to users. A PermissionSetGroup bundles multiple PermissionSets into a single assignable unit. Instead of assigning five permission sets to every sales user, you assign one PermissionSetGroup. PermissionSetGroups can also include a MutingPermissionSet to suppress specific permissions that other permission sets in the group would otherwise grant.
Can I retrieve only the field-level security portion of a Profile?
No. The Metadata API returns the complete Profile document. There is no partial-field retrieval for Profile. The workaround is to retrieve one Profile at a time and use git diff to see only what changed since your last commit. This makes large Profile files manageable. You only need to review the diff, not the whole file.
What is a MutingPermissionSet?
A MutingPermissionSet is a special type of permission set that removes permissions rather than granting them. It can only be used inside a PermissionSetGroup. You cannot assign it directly to users. Inside a PermissionSetGroup, it suppresses specific permissions that other permission sets in the group would otherwise grant, giving you fine-grained control over the net access a PermissionSetGroup provides.
Key Takeaways
- Retrieve one Profile at a time:
--metadata "Profile:Admin"is much faster and more practical than retrieving all profiles in one command - Profiles are massive XML: expect 5,000–50,000 line files containing every permission for every object and field for that Profile
- PermissionSets are smaller: they only store what they grant, not the full deny context, making them easier to review in git
--target-metadata-dirfor MDAPI format: produces.profileextension (not.profile-meta.xml); source format is better for git workflows- MutingPermissionSet needs API v53+: update
sourceApiVersioninsfdx-project.jsonif MutingPermissionSets are missing from your results - package.xml: list specific profiles: use named
<members>elements rather than*for Profile to avoid retrieving every profile in the org
What's Next?
Recommended Reading:
- Article 7: Fetching Code Metadata: ApexClass, ApexTrigger, LWC
- Article 5: Fetching Automation Metadata: Flow, WorkflowRule, ApprovalProcess
- Article 10: Git Basics for Salesforce Admins: commit security metadata to version control
- Article 11: Deploying Metadata Back to Salesforce: push permission changes between orgs
Action Items:
- Run
sf org list metadata --metadata-type Profile --target-org myorgto see all Profile names in your org - Retrieve your Admin profile:
sf project retrieve start --metadata "Profile:Admin" --target-org myorg - Open the file. Search for a field name you know is restricted and find the
<fieldPermissions>entry for it in context
Resources & References
- Salesforce Metadata API: Profile
- Salesforce Metadata API: PermissionSet
- Salesforce Permission Set Groups
- Salesforce Metadata Coverage Report
About This Guide: Part of the Salesforce Admin Git & sf CLI series.
Tags: #salesforce #sfcli #profile #permissionset #security #versioncontrol