Start free
Andrew Hanna

Andrew Hanna

How to Manage Profiles and Permission Sets in Source Control

How to Manage Profiles and Permission Sets in Source Control

Short answer: put permission sets in source control as the real source of truth, decompose them so two people can edit them without a merge conflict, keep profiles thin and treat every profile deploy as an overlay rather than a full replacement. Then deliberately stop tracking the parts that are environment-specific, because most profile churn in a repo is noise nobody reads.

Why are profiles so hard to keep in Git?

Two documented behaviours make profiles behave unlike every other file in your repo.

  • A retrieve is context-dependent. The returned .profile file only includes security settings for the other metadata types named in the same retrieve request. Ask for a profile alone and you get almost nothing; ask for it alongside 40 objects and you get a different file (Metadata API documentation).
  • A deploy is an overlay. Permissions not present in the file are not removed in the target org, and disabling a permission requires explicitly writing the false value into the XML. A missing line means "no opinion", not "off".

Put those together and the same profile produces a different diff depending on who retrieved it and what else was in their package. That is why profile diffs are the most argued-about, least trusted files in most Salesforce repositories.

What changed in 2026, and does it change your strategy?

Salesforce had announced that permissions in profiles would be retired starting in Spring '26. On 6 June 2026 that enforcement was cancelled, citing customer feedback and remaining feature gaps, while Salesforce continued to recommend a permission-set-led security model (Salesforce Help).

The practical read: the deadline is gone, the direction is not. Profiles stay supported, so nobody has to panic-migrate, but if you are choosing where to invest your source-control discipline, invest it in permission sets. They are additive, they decompose, and they merge.

How do you decompose permission sets so they stop conflicting?

A permission set as retrieved is one large XML file listing every object, field and user permission. Two admins touching two unrelated fields collide in the same file. Salesforce ships a fix for this in the CLI: source behavior options that split the file into one file per permission group (Salesforce DX developer guide).

  1. Commit everything first. This rewrites files on disk, so start from a clean tree.
  2. Run it as a dry run: sf project convert source-behavior --behavior decomposePermissionSetBeta2 --dry-run.
  3. Run it for real. Your sfdx-project.json is updated so the behaviour sticks for everyone, and the existing source is converted in place.
  4. Commit the conversion on its own branch, with no functional change in it. Reviewers cannot read a diff that mixes a format conversion with real edits.

The same mechanism covers custom labels, workflows, sharing rules and external service registrations. Note the gap: profiles are not on the decomposable list. Objects and object translations decompose by default, the rest are opt-in betas, and profiles remain a single file.

What should you stop tracking?

Half the pain here is self-inflicted. These belong outside the repo, or behind a deliberate exclusion.

  • Managed package permissions. Namespaced object and field permissions appear and vanish with package versions in each org. Tracking them guarantees permanent phantom diffs.
  • Login IP ranges and login hours. These are usually environment policy, not release content, and they differ legitimately between sandbox and production.
  • Permission set assignments. These are records, not metadata. Who has which permission set is data, and it belongs to a data-seeding step, not to Git.
  • Standard object permissions you never intend to change. If nothing in your process manages them, retrieving them only adds diff volume.
  • Profiles you do not deploy. Most orgs have inherited profiles nobody has touched in years. Track the ones your pipeline actually promotes.

Write the exclusions down as a rule in the repo, not as tribal knowledge in one person's CLI history. A rule that lives in someone's shell alias is a rule that breaks the first time a new joiner runs a retrieve.

How do you move permissions from profiles to permission sets safely?

  1. Pick one profile and inventory what it actually grants in production, not in the sandbox where it drifted.
  2. Create permission sets by job task, not by job title. Task-shaped sets get reused; title-shaped sets get duplicated for every new title.
  3. Assign the new sets alongside the profile, changing nothing yet. Access is additive, so nobody loses anything at this stage.
  4. Remove the moved permissions from the profile in a separate release, with explicit false values so the deploy actually turns them off.
  5. Verify with a user of that profile in a sandbox, and keep the pre-change permission snapshot until you have.

Never do steps three and four in the same release. Additive and subtractive permission changes have completely different rollback stories, and mixing them is how a Monday morning lockout happens. More release engineering playbooks are in our SF Guides library.

FAQ

Should profiles or permission sets be the source of truth?

Permission sets. They are additive, they decompose into mergeable files and Salesforce recommends a permission-set-led model. Keep profiles for baseline defaults such as license, default record types and page layouts.

Are permissions in profiles being retired?

No. The retirement planned for Spring '26 was cancelled on 6 June 2026. Migration is now your own timeline rather than a deadline.

Why does my profile diff change when I did not touch it?

Because a profile retrieve only returns settings for the metadata included in the same request. Change the package contents and the file changes. Fix the retrieve manifest, not the file.

Can profiles be decomposed like permission sets?

Not today. The CLI source behavior options cover permission sets, custom labels, workflows, sharing rules and external service registrations. Profiles remain one file, which is another argument for keeping them thin.

Related Articles

Curious about faster shipping before you dive in? Let's talk

Commitment free!