Every org with more than three years of history has the same artifact: a profile list with entries like "Sales User", "Sales User - No Delete", "Sales User - No Delete (EMEA)", and "Sales User Copy FINAL". Nobody remembers who created the third one. Nobody can tell you what differs between the second and the fourth without exporting both to XML and running a diff.
That is not a discipline problem. It is a structural one. A user gets exactly one profile, so any access difference between two people has to become a whole new profile unless you have somewhere else to put it. Permission sets are that somewhere else, and permission set groups are what makes them manageable at scale.
This guide covers what each layer actually controls, why the community and Salesforce's own product direction favor permission set groups, and a staged migration plan you can run without a change-freeze weekend. If you want the deep reference material on how the layers resolve and what goes wrong, our Permission Set Spaghetti cheat sheet has the anti-pattern catalogue and the full SOQL query bank.
TLDR
- The overlap is bigger than the difference. Object permissions, field-level security, app and tab access, system permissions, Apex and Visualforce access, and custom permissions can all live in either place.
- A handful of settings are still profile-only: login hours, login IP range restrictions, default record type, default app, and page layout assignment. That list is what your minimum-access profile exists to carry.
- One profile per user is the whole reason profile sprawl happens. Every access variation becomes a cloned profile because there is nowhere else to put it.
- Permissions resolve as a union, not a waterfall. If any assigned source grants a permission, the user has it. Muting subtracts only inside its own permission set group, and another source granting the same permission bypasses the mute silently.
- Migrate in six stages: baseline audit, define the minimum viable profile, build permission set groups per job function, diff in a sandbox, pilot one cohort, roll out in waves.
- Write the rollback before wave one. Access changes fail loudly and during business hours. A documented reversal path is the difference between a bad hour and a bad week.
What each layer actually controls
Start here, because most migration plans go wrong at the first step: someone assumes a setting is portable to a permission set when it is not, moves everyone to Minimum Access, and discovers on Monday that nobody has a page layout.
The overlap
These can be granted from either a profile or a permission set:
| Permission type | On profile | On permission set |
|---|---|---|
| Object permissions (CRUD, View All, Modify All) | Yes | Yes |
| Field-level security | Yes | Yes |
| App visibility | Yes | Yes |
| Tab settings | Yes | Yes |
| System permissions (for example, Manage Users, Export Reports) | Yes | Yes |
| Apex class access | Yes | Yes |
| Visualforce page access | Yes | Yes |
| Custom permissions | Yes | Yes |
| Record type access | Yes | Yes |
| Assigned connected apps | Yes | Yes |
That is the large majority of what you tune day to day. Nearly all of it is portable.
The profile-only settings
These are the reason you cannot delete profiles entirely. Every user must have exactly one, and these settings ride on it:
- Login hours. The window during which a user can log in at all.
- Login IP ranges. Enforced at the profile level. Permission sets have an IP-related setting, but it behaves differently and is tied to session-based activation, so treat profile login IP ranges as the enforcement point.
- Default record type. Record type access is assignable via permission sets; which one is default for a user is a profile setting.
- Default app. The app a user lands in.
- Page layout assignment. Layouts are assigned by profile and record type. Dynamic Forms reduce how much a layout carries, but the assignment mechanism is still profile-based.
- The user license binding. A profile is tied to a user license, which constrains which permission sets can even be assigned to that user.
Salesforce's own documentation on permission sets and profiles is the source of truth here, and it moves between releases. Check it before you finalize a design rather than trusting a blog post, including this one.
How the layers actually resolve
This is where most orgs get their security posture wrong, so it is worth being blunt.
Permissions are a union, not a hierarchy. Salesforce evaluates the profile plus every assigned permission set plus every permission set inside every assigned permission set group, and grants the most permissive result. There is no precedence order. If one forgotten permission set from a 2022 project grants "Modify All Data", the user has Modify All Data, no matter how locked down their profile is.
Muting permission sets are the only subtractive mechanism, and their scope is narrow: a muting permission set removes a permission only within its parent permission set group. If the same permission is granted by the profile, by a standalone permission set, or by a different group, the mute is bypassed and nothing warns you.
Two practical consequences:
- You cannot secure an org by tightening profiles. You have to inventory every grant. A tight profile with eleven stacked permission sets on top is not a tight configuration.
- Muting is a design tool inside a group, not a security control. Do not use it to claw back permissions that other sources also grant.
Record-level access is a separate system entirely. Organization-wide defaults, the role hierarchy, sharing rules, and manual sharing decide which records a user sees. Profiles and permission sets decide what they can do with the records they can see. Migrating to permission set groups does not change your record visibility posture at all, and confusing the two is a reliable way to ship a migration that fails an audit.
Why the shift to minimum-access profiles
The argument is not fashion. It is four concrete operational problems that permission set groups solve and profiles structurally cannot.
One profile per user forces cloning. If your EMEA sales team needs everything the AMER team has minus delete on Opportunity, that is a new profile. Do that eight more times across regions, seniority levels, and product lines and you have 40 profiles that are 95% identical. Nobody can diff them, so nobody maintains them, so they drift.
Permission sets are additive and composable. The same "Opportunity Delete" permission set can be attached to any group that needs it, in one place. Change it once and everyone who should get the change gets it. That is the whole argument in one sentence.
Permission set groups map to how people actually think. A group named "Sales Manager - AMER" containing "Core CRM Read", "Opportunity Full", "Forecasting", and "Territory Reporting" is self-documenting. When someone asks why a sales manager can edit forecasts, the answer is one click deep instead of a profile XML export. Salesforce's Permission Set Groups Trailhead module walks the mechanics if your team needs the hands-on version.
Onboarding and offboarding become assignment operations. With a profile-heavy model, a role change means switching the user's profile, which is a bulk edit with side effects on layouts and defaults. With groups, a role change is removing one group and adding another. That is a difference you feel every week, not once a year.
Audit and compliance clarity. During a user access review, "this person has the Support Tier 2 group" is an answer an auditor accepts. "This person is on Custom Support Profile v3 and also has six permission sets we found in the assignment table" is a finding. If you are running a periodic access review for SOC 2 or SOX, the structural clarity is worth the migration on its own.
Salesforce has been pushing this direction in product for several releases: the standard Minimum Access - Salesforce profile ships out of the box, permission set groups with muting are GA, and User Access Policies exist to automate assignment at scale. Salesforce has also signaled at various points that it intends to reduce the role of permissions on profiles over time, though the specific timeline has moved. Do not plan around a deprecation date; plan around the fact that every new capability lands on the permission set side first.
What this migration does not fix
Being honest about scope prevents the most common disappointment.
It does not fix record-level access. Your org-wide defaults, role hierarchy, and sharing rules are untouched by this work.
It does not fix too many permissions existing. Moving a bad grant from a profile to a permission set relocates the problem with better labelling. If 26% of your users hold System Administrator today, they will still hold it after the migration unless you explicitly decide not to grant it. Do the reduction as a deliberate decision during Stage 1, not as a hoped-for side effect.
It does not fix integration users with admin rights. Those need their own least-privilege treatment, which is a separate project with a separate risk profile. Never bundle integration user cleanup into a human-facing access migration; if something breaks you will not know which change caused it.
The staged migration plan
Six stages. For a mid-market org with 20 to 40 profiles and a few hundred users, budget six to ten weeks of calendar time at part-time effort, most of which is waiting on stakeholder sign-off rather than clicking.
Stage 0: Baseline and freeze
You cannot migrate what you have not measured, and you cannot measure a moving target.
Freeze profile creation. No new profiles for the duration. Every request that would have been a cloned profile becomes a permission set instead. This alone stops the bleeding on day one.
Export the current state. You want four artifacts:
- Every profile with its full permission payload.
- Every permission set and permission set group, with assignment counts.
- Every user, their profile, and their assigned permission sets.
- The list of orphans: permission sets assigned to nobody.
A few queries to get you started. Run them in Workbench or the Developer Console:
-- Users per profile, active only
SELECT Profile.Name, COUNT(Id)
FROM User
WHERE IsActive = true
GROUP BY Profile.Name
ORDER BY COUNT(Id) DESC
-- Real permission set assignments (excludes the hidden
-- profile-owned permission set each profile carries)
SELECT Assignee.Name, PermissionSet.Name, PermissionSetGroupId
FROM PermissionSetAssignment
WHERE Assignee.IsActive = true
AND PermissionSet.IsOwnedByProfile = false
ORDER BY Assignee.Name
-- Dangerous object grants living on profiles
SELECT Parent.Profile.Name, SobjectType,
PermissionsModifyAllRecords, PermissionsViewAllRecords
FROM ObjectPermissions
WHERE Parent.IsOwnedByProfile = true
AND (PermissionsModifyAllRecords = true OR PermissionsViewAllRecords = true)
The permissions cheat sheet has the rest of the query bank, including orphan detection and Modify All Data spread. If you would rather not hand-roll this, the free permissions audit produces the same inventory automatically and flags the outliers.
Snapshot for rollback. Retrieve all Profile, PermissionSet, and PermissionSetGroup metadata into a version-controlled repository, tagged with the date. This snapshot is your rollback artifact. Do not skip it because "we can just re-create it." You cannot, not under pressure.
Stage 1: Define the minimum viable profile
The goal is the smallest number of profiles that can carry the profile-only settings for your population.
Group your users by the five profile-only dimensions: login hours, login IP ranges, default record type, default app, and page layout assignment. In most orgs, that collapses to somewhere between two and six profiles. A common landing spot:
| Profile | Carries | Typical population |
|---|---|---|
| Minimum Access - Salesforce (or a clone) | Nothing beyond license binding and defaults | The large majority of internal users |
| Restricted Hours / IP profile | Login hours and IP ranges for a contact center or offshore team | Support, offshore delivery |
| System Administrator | Full admin, deliberately small | Named admins only |
| Per-license profiles | One each for Platform, Community, or Chatter licenses | Users on non-standard licenses |
Two rules while you do this:
Rule one: do not reduce access in Stage 1. The migration and the least-privilege reduction are two different projects. Doing both at once means every breakage investigation has two candidate causes. Migrate to equivalent access first, then run the reduction as a follow-on with its own change log.
Rule two: the exception is Modify All Data and System Administrator. If your audit found admin sprawl, cutting it is worth doing, but do it as its own tracked change with named approvals, not silently inside the migration.
Stage 2: Build permission set groups per job function
Design top-down from job functions, not bottom-up from existing profiles. If you build one permission set per legacy profile, you have renamed your problem.
A structure that holds up:
- Baseline permission sets. What literally everyone gets. Read on core objects, standard tabs, the apps everyone needs. One set, assigned everywhere.
- Functional permission sets. One narrow capability each. "Opportunity Edit", "Case Close", "Forecast View", "Export Reports". Keep them single-purpose. A permission set that does four things cannot be reused for the one thing you need.
- Permission set groups. One per job function. "AE - AMER", "Sales Manager", "Support Tier 1", "Support Tier 2", "Finance Read Only". Each group composes baseline plus the functional sets that job needs.
- Muting, sparingly. Only where a group needs to be a strict subset of the sets it contains, and only after you have confirmed no other assigned source grants the same permission.
Naming matters more than it sounds. Pick a convention on day one and enforce it: PS_ prefix for permission sets, PSG_ for groups, function name, then scope. PS_Opportunity_Delete, PSG_SalesManager_AMER. Six months from now the naming is the only documentation that survives.
Aim for a manageable count. If you end up with 120 functional permission sets for 300 users, you have rebuilt profile sprawl with new nouns.
Stage 3: Build and diff in a sandbox
Do this in a sandbox that has representative user data. A Developer sandbox is fine for building; you want a Partial Copy or Full Copy for the diff step if you can get one.
Build the new structure alongside the old one. Do not delete anything yet.
Diff the effective permissions, user by user, for a representative sample. This is the step teams skip and then regret. For each sample user, compare the union of permissions under the old model against the union under the new model. You are looking for two things:
- Losses: permissions the user had and no longer has. These break work.
- Gains: permissions the user did not have and now does. These break audits.
Gains are more dangerous than losses because nobody reports them. A user who quietly acquires "Export Reports" or "View All Data" will not file a ticket. Grep your diff for gains specifically, and treat every one as a design bug until proven intentional.
Pay special attention to field-level security. FLS is the most common source of silent loss in these migrations, because a field hidden on the new baseline but visible on the old profile produces a blank page rather than an error message. Users describe it as "the record is broken."
Stage 4: Pilot one cohort
Pick a group of 5 to 15 users. Good pilot cohorts share three traits: they are homogeneous in job function, they are heavy daily users so problems surface fast, and they will actually tell you when something is off.
Run the pilot for a full business cycle. If your team closes deals at month end, that means running through a month end. A pilot that skips the busiest week of the month tests nothing.
Instrument it. Before switching, ask the cohort to list the ten things they do every day. After switching, walk that list. "Nothing seems broken" is not a test result.
Stage 5: Roll out in waves
Wave by job function, smallest and least critical first. Never migrate a whole org in one operation.
For each wave:
- Assign the new permission set groups first, while the old profile is still in place. Permissions are a union, so this is additive and safe. Nobody loses anything.
- Verify the cohort works normally for at least one full business day with both models active.
- Then switch the profile to the minimum-access profile. This is the subtractive step and the only genuinely risky moment.
- Watch for 48 hours before starting the next wave.
Schedule the profile switch for early in a business day, not at end of week. If something breaks, you want your whole team awake and your users available to describe the symptom.
Keep a change log with one row per wave: date, cohort, who approved, what was assigned, what was removed, and the exact rollback command. You will want it, and so will your auditor.
Stage 6: Decommission
Only after every wave is stable for a full cycle.
Delete legacy profiles last, and only after confirming zero assigned users and no references from page layout assignments or record type defaults. Salesforce will block deletion of a profile with assigned users, but it will happily let you orphan a page layout assignment.
Then run the audit again. Compare the before and after on the metrics that matter: number of profiles, number of users with Modify All Data, number of orphaned permission sets, number of users holding System Administrator. If those numbers did not improve, you did a rename, not a migration.
The rollback checklist
Write this before wave one. Print it if you have to.
Prerequisites, in place before any wave:
- Full metadata snapshot of all Profile, PermissionSet, and PermissionSetGroup components, in version control, tagged with a date.
- Export of every affected user's current ProfileId and current permission set assignments, stored as a CSV outside Salesforce.
- A named rollback owner per wave who has the access to execute it, and a backup for that person.
- A stated rollback trigger. For example: any P1 access failure, or more than three P2 reports within four hours.
- A tested reversal in the sandbox. Not planned. Tested.
- A communication template written in advance, so you are not composing a status update during an incident.
Execution order when you pull the trigger:
- Restore the profile assignment first. This is the fastest path back to working access, because the old profile carried the grants. Use your CSV and Data Loader on the User object.
- Leave the new permission set groups assigned. They are additive. Removing them during an incident adds risk without adding recovery.
- Verify with two named users from the affected cohort before declaring recovery. Not by checking setup, by having them do their actual job.
- Freeze the wave sequence until the root cause is documented.
- Write down the specific permission that broke, and add it to the design. Every rollback should improve the target state.
Partial rollback beats full rollback. If one job function broke and four are fine, revert the one. A full-org reversal turns a contained problem into an org-wide event and destroys team confidence in the project.
Common mistakes
Building one permission set per legacy profile. You will get the migration done and none of the benefit. The point is decomposition into reusable capabilities.
Using muting to fix grants that come from elsewhere. Muting only works inside its own group. If the profile or a standalone permission set grants the same permission, the mute is silently ineffective and you have a false sense of security.
Migrating access reduction and access restructuring at the same time. Two changes, one deployment, ambiguous blast radius. Sequence them.
Skipping the field-level security diff. FLS failures present as blank fields rather than error messages, so users report them as data problems and you spend two days looking in the wrong place.
Forgetting integration and API users. They have profiles too. If your minimum-access profile becomes the default for everyone and an integration user gets caught in a wave, you will find out when a nightly sync fails at 2am.
No naming convention. Six months later, PS_New_Sales_2 and PS_Sales_Updated will both exist and nobody will know which is live.
Effort and timeline
Rough planning numbers for a mid-market org, 200 to 600 users, 20 to 40 profiles.
| Stage | Calendar time | Effort | Main risk |
|---|---|---|---|
| 0. Baseline and freeze | 1 week | 8-12 hrs | Incomplete inventory |
| 1. Minimum viable profile | 1 week | 6-10 hrs | Stakeholder disagreement on admin access |
| 2. Build permission set groups | 2-3 weeks | 20-40 hrs | Over-decomposition into too many sets |
| 3. Sandbox build and diff | 1-2 weeks | 15-25 hrs | Missed FLS and permission gains |
| 4. Pilot cohort | 2-4 weeks | 5-10 hrs | Pilot that skips a real business cycle |
| 5. Waves | 3-6 weeks | 3-6 hrs per wave | Wave too large, no rollback rehearsal |
| 6. Decommission and re-audit | 1 week | 4-8 hrs | Orphaned layout and record type references |
Total is typically 60 to 110 hours of hands-on work spread over two to three months. The calendar time is dominated by pilot and wave observation windows, which you should not compress. Those windows are the control.
Frequently asked questions
Can I delete profiles entirely in Salesforce?
No. Every user must be assigned exactly one profile, and profiles still carry login hours, login IP ranges, default record type, default app, and page layout assignment. The realistic target is a small number of thin profiles, not zero.
What is the Minimum Access - Salesforce profile?
It is a standard profile Salesforce ships that grants almost nothing beyond the ability to log in. It is the intended base layer for a permission-set-driven access model: assign it, then grant every capability through permission set groups. Many orgs clone it rather than using it directly, so they can set a default app and record types per population.
Do permission sets override profiles?
No, and this is the most consequential misconception in Salesforce access control. Permissions combine as a union. If either the profile or any assigned permission set grants something, the user has it. Nothing overrides anything. The only subtractive mechanism is a muting permission set, and it only subtracts within its own permission set group.
How many permission sets is too many?
There is no hard limit that matters in practice, but there is a maintainability ceiling. If your permission set count is approaching your user count, you have rebuilt profile sprawl. A healthy mid-market structure is usually 20 to 50 functional permission sets composed into 8 to 20 permission set groups. If a set is assigned to exactly one person, question whether it should exist.
Does migrating to permission sets change what records users can see?
No. Record visibility is controlled by org-wide defaults, the role hierarchy, sharing rules, and manual sharing. Profiles and permission sets control object and field permissions plus system permissions. The two systems interact (View All and Modify All on an object bypass sharing), but a like-for-like permission migration leaves record-level access unchanged.
How do I test a permission migration safely?
Build in a sandbox, diff effective permissions per user in both directions (losses and gains), pilot with one homogeneous cohort through a full business cycle, then roll out in waves. In each wave, assign the new groups first while the old profile is still active, verify, and only then switch the profile. That sequencing means the risky subtractive step happens after you have already confirmed the additive step worked.
Should we do this ourselves or bring help?
If you have an admin with sandbox time and a clean inventory, this is a very doable internal project and the knowledge is worth owning. Bring help when one of three things is true: your inventory is unknown, your org has audit exposure that makes a mistake expensive, or your admin does not have 60 to 100 hours to spare over a quarter. If it is the last one, the work is a good fit for managed services precisely because it is high-effort, well-defined, and reviewable at every step.
Related Tools
Published August 2026. Written by Neil Sarkar. Product behavior described here reflects Salesforce as of Summer 2026 and changes between releases; verify against Salesforce Help on permission sets and the Permission Set Groups Trailhead module before you finalize a design. For the underlying access-model concepts, Salesforce's Data Security module and the Protect Your Salesforce Data trail are the best free references. Clientell builds AI-assisted Salesforce admin tooling, including a permissions audit, so we have a commercial interest in this topic. The migration plan above works the same whether you run it by hand or with a tool.
Related reading:
- Salesforce Permissions Audit: The Complete Guide for 2026
- Salesforce Health Check: The Complete 2026 Guide
- Salesforce Technical Debt: The Complete 2026 Guide
- Permission Set Spaghetti cheat sheet
- Salesforce Permissions Audit
