Skip to content

Fetching Security Metadata from Salesforce with sf CLI: Profile, PermissionSet, Role

How to retrieve Profile, PermissionSet, PermissionSetGroup, MutingPermissionSet, and Role metadata from Salesforce using sf CLI, including partial retrieval patterns for large Profile XML.

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 myorg to 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.xml for 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 --metadata flag accepts the format MetadataType: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-dir for MDAPI format: produces .profile extension (not .profile-meta.xml); source format is better for git workflows
  • MutingPermissionSet needs API v53+: update sourceApiVersion in sfdx-project.json if 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:

Action Items:

  1. Run sf org list metadata --metadata-type Profile --target-org myorg to see all Profile names in your org
  2. Retrieve your Admin profile: sf project retrieve start --metadata "Profile:Admin" --target-org myorg
  3. Open the file. Search for a field name you know is restricted and find the <fieldPermissions> entry for it in context

Resources & References


About This Guide: Part of the Salesforce Admin Git & sf CLI series.

Tags: #salesforce #sfcli #profile #permissionset #security #versioncontrol