Permission sets vs profiles is no longer a style debate — it is a migration with a deadline of practicality. Salesforce’s access model has been permission-set-led for years now, new features increasingly ship with permission-set-only switches, and orgs still running thirty cloned profiles are paying for it in audit time, deployment conflicts and onboarding friction. This guide covers what actually remains on the profile in 2026, how to structure permission sets and groups, and the migration sequence that doesn’t break user access mid-flight.
This sits inside the wider Salesforce security model — permissions answer what a user can do; record-level sharing answers which records they can do it to. Don’t conflate the two during the migration.
Where the EOL story actually landed
Salesforce originally announced the end of permissions on profiles, then retired the hard deadline after customer feedback. The destination did not change — only the enforcement. The supported model in 2026:
- Profile — one per user, reduced to a minimal container
- Permission sets — all functional access, stackable, additive
- Permission set groups — persona-level bundles with optional muting
New platform capabilities increasingly expose their access switches only as permission set toggles. Every release makes the profile-heavy org slightly more stuck. That, not a deadline, is the forcing function.
What stays on the profile — the complete list
After full migration, the profile legitimately owns four things:
| Setting | Why it can’t move |
|---|---|
| Login hours | Profile-only setting, no permission set equivalent |
| Login IP ranges | Profile-only setting |
| Default record type | Defaults are a profile concept; record type access moves to permission sets |
| Page layout assignment | Assigned per profile + record type |
Everything else — object CRUD, field-level security, app visibility, tab settings, Apex class access, Visualforce page access, custom permissions, system permissions, connected app access — belongs in permission sets. A correctly migrated org has profiles you can count on one hand: typically a Minimum Access baseline for humans, plus the special-purpose standard profiles you cannot delete.
Design rules for permission sets that scale
The migration fails when teams convert thirty profiles into thirty mega permission sets — same mess, new container. The structure that works:
1. Permission sets are capabilities, not personas. Name them for what they grant: Opportunity_Edit, Invoice_Admin, API_Access, Reports_Builder. A permission set with a job title for a name is a profile in disguise.
2. Permission set groups are personas. PSG_Sales_User = Opportunity_Edit + Quote_Create + Reports_Builder. One persona, one group, one assignment. When the Sales persona gains a capability, you add one permission set to one group and every assigned user gets it.
3. Muting is the exception tool, not the design tool. A muting permission set inside a group subtracts specific permissions from the group’s total — built for “Sales User, but this subset can’t delete.” If you find yourself muting heavily, your capability sets are cut too coarse.
4. Session-based permission sets cover elevated access that should exist only during an authenticated session context — worth knowing, rarely your first problem.
The additive rule underpins everything: a user’s effective access is the union of profile + all permission sets + all groups, minus mutings. Nothing in a permission set ever takes access away. If something must be removable per-user, it must be granted granularly enough to be unassignable.
The audit — SOQL before strategy
You cannot migrate access you haven’t inventoried. Three queries do most of the work.
What grants access to a given object:
SELECT Parent.Name, Parent.IsOwnedByProfile, Parent.Profile.Name,
PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete
FROM ObjectPermissions
WHERE SobjectType = 'Invoice__c'
ORDER BY Parent.IsOwnedByProfile DESC
Parent.IsOwnedByProfile = true rows are your migration backlog — each one is a grant living on a profile that needs a permission-set home.
Who holds a given permission set:
SELECT Assignee.Name, Assignee.Profile.Name, PermissionSet.Name
FROM PermissionSetAssignment
WHERE PermissionSet.Name = 'Opportunity_Edit'
Which profiles still carry dangerous system permissions:
SELECT Name, PermissionsModifyAllData, PermissionsViewAllData,
PermissionsAuthorApex
FROM Profile
WHERE PermissionsModifyAllData = true OR PermissionsViewAllData = true
Run these into a spreadsheet and you have the real scope of the migration — usually smaller than feared, because most cloned profiles differ from each other in a handful of rows.
Migration sequence — grant first, strip last
The order is the whole risk model. Per persona:
- Build capability permission sets from the audit of that persona’s profile
- Compose the permission set group for the persona
- Assign the group alongside the existing profile — access is additive, so nothing breaks
- Verify with User Access Summary on representative users: every permission should now show a permission-set source in addition to the profile source
- Strip the profile of the migrated permissions
- Re-verify the same users — effective access must be identical, now sourced purely from the group
- Re-point the user to the minimal baseline profile
Never invert steps 3 and 5. Stripping before granting is the migration pattern that generates the “nobody can see Opportunities” incident, and it always happens at 9am on a Monday.
Two populations need special handling: integration users belong on the Salesforce Integration license with narrow API permission sets rather than inheriting a human persona, and admins should keep their own thin group so that admin access is auditable like everything else.
Pitfalls that surface mid-migration
- Field-level security drift. FLS lives in both profiles and permission sets; during coexistence (steps 3–4) a field visible via the profile but missed in the permission set passes verification, then vanishes at step 5. Diff FLS explicitly, don’t eyeball it.
- Record type access vs default. Access to record types moves to permission sets; the default record type stays on the profile. Migrate access, keep the default, or users get a record type picker with no default selected.
- Deployment coupling. Profiles in change sets and metadata deployments are notoriously partial; permission sets deploy cleanly and completely. This alone repays the migration — see how this interacts with your deployment strategy if profiles have been corrupting your pipelines.
- License mismatches. A permission set carrying a permission unsupported by a user’s license fails assignment at runtime. Audit license types before composing groups, not after.
The end state worth aiming for
Five or fewer profiles. Capability-named permission sets in the dozens, not hundreds. One group per persona. Every access question answerable by one SOQL query, every access change deployable without touching a profile, every new hire onboarded with a single group assignment. Orgs that reach this state stop talking about permissions — which is the entire point.
Test your knowledge — Security & Sharing
10 questions · Basic to Advanced