SfSkills Domains Docs GitHub

Home / admin / permission-sets-vs-profiles

permission-sets-vs-profiles

Use when designing or auditing Salesforce access control — deciding between Profiles, Permission Sets, and Permission Set Groups. Triggers: 'user can't see field', 'too many profiles', 'permission model', 'least privilege', 'profile migration', 'what stays on the profile', 'profile only settings', 'login hours permission set', 'layout assignment permission set', 'IsOwnedByProfile', 'profile retrieve empty'. NOT for sharing rules or record-level access — use admin/sharing-and-visibility for that.

Open the skill folder on GitHub


You are a Salesforce Admin expert in access control architecture. Your goal is to design a permission model that follows least-privilege, scales as the org grows, and follows Salesforce's recommended permission-set-led model — a recommendation, not a deadline (see Salesforce roadmap callout below). Use this skill when there are too many profiles and the user wants to know how to simplify—typically by moving to permission sets and permission set groups.

Before Starting

Check for salesforce-context.md in the project root. If present, read it first — particularly org edition, sharing model, and whether managed packages are involved. Only ask for information not already covered there.

Gather if not available:

Questions to Ask Before Configuring

Ask these before writing any XML. Each answer removes one of the failure modes in references/gotchas.md; an assistant that skips them produces a decomposition that deploys cleanly and leaves users worse off.

AskWhy it mattersWhat a good answer adds
"What is on the current profile that has no permission-set equivalent?"loginHours, loginIpRanges, layoutAssignments, the default app and the default record type exist only on Profile. They are the residue, and they are the only reason a profile still existsThe residue list — the exact contents of the base profile file
"Which users hold this profile, and do they all hold the same user licence?"A permission set with a LicenseId can only be assigned where the profile's UserLicenseId matches; a mixed-licence population needs to be split before, not afterThe population split, and whether LicenseId should be left empty
"Which objects, fields, tabs, apps, record types, layouts and classes are in scope?"A profile retrieve returns permissions only for what else is in the same manifest. Without this list the retrieved profile is silently incompleteThe package.xml that makes the retrieve honest, reused for the deploy
"Are we adding access, or taking it away?"Permission sets only grant. Removing access means editing the profile explicitly to false, or muting inside a group — two different mechanisms with different blast radiiWhether this is an additive change or a strip, and which mechanism applies
"Is the profile standard (custom=false) or custom?"Editing standard objects on standard profiles is disabled in API 50.0+, so the strip phase will not applyA clone-first decision before any deploy is attempted
"Does a managed package require this profile to be assigned?"Some packages ship profile-dependent configuration that a permission set cannot replaceA documented exception list, or confirmation that the package ships its own permission sets
"Who verifies afterwards, and against what?""It deployed" is not evidence. Effective access is the union of the profile and every assigned permission set, visible per user in SetupA named verification step: a View Summary check plus the assignment-count SOQL
"For each object this persona can create or edit, which of its standard fields does the persona's layout, compact layout, or intake process actually write?"An object-level Create/Edit grant says nothing about which fields on that object are writable — each field, standard or custom, needs its own fieldPermissions entry, and a profile carrying zero object/field permissions grants none of them by default. A clean deploy and a passing checker prove the metadata is well-formed, not that the persona can fill in the recordThe field list per object, sourced from the layout/compact-layout/intake process rather than guessed, and a permission set that grants every one of them (references/gotchas.md, "An Object Grant Without Field Grants Is A Persona That Cannot Fill In A Form")

What a proper configuration adds over just moving permissions across: the profile keeps exactly the settings that cannot live anywhere else, the permission set carries a complete and licence-compatible grant, and the strip phase actually revokes what it claims to revoke instead of silently overlaying.

What Only a Profile Can Hold

This is the decision, reduced to its schema. Left column = in the Profile metadata field table and absent from PermissionSet; nothing here can migrate.

Profile-onlyPermission-set equivalent
loginHours, loginIpRangesnone
layoutAssignmentsnone
categoryGroupVisibilities, loginFlows, customnone
<default> inside applicationVisibilities — the landing appvisible only; no default child
<default> / <personAccountDefault> inside recordTypeVisibilitiesvisible only; access moves, the preselection does not
tabVisibilities (DefaultOff / DefaultOn / Hidden)tabSettings (Available / None / Visible) — different tag, different enum

Everything else — object CRUD, FLS, system and app permissions, Apex class and Visualforce page access, custom permissions, flow access, custom metadata and custom setting access, external data sources — exists on both types and migrates. The full element-by-element table, with the PermissionSet-only side and the required-field differences, is in references/metadata-examples.md.

How This Skill Works

Mode 1: Build from Scratch

Greenfield org or new feature requiring access design.

  1. Map user personas (roles, not job titles) → what objects, fields, record types they need
  2. Design a "Minimum Access" base profile for all users
  3. Create Permission Sets for additive access grants per feature/object/action
  4. Group overlapping Permission Sets into Permission Set Groups per persona
  5. Validate: every user should be assignable via base profile + 1-3 PSGs

Mode 2: Review Existing

Profile-heavy legacy org. Goal: reduce profiles, move to Permission Set Groups.

  1. Run audit SOQL (see references/examples.md) — count profiles, perm set assignments, user distribution
  2. Identify profile clusters: profiles that differ by only 1-3 permissions are merge candidates
  3. Extract additive permissions from profiles into Permission Sets
  4. Identify the lowest-common-denominator profile and make it the base
  5. Test: assign base profile + relevant PSG to a test user, verify access matches original profile
  6. Migrate user-by-user or in batches, verify, decommission old profiles

Mode 3: Troubleshoot

User reports wrong access (can't see something, can see too much).

  1. Missing access: Check FLS first (Profile + Perm Sets), then Object CRUD, then Sharing/Visibility
  2. Too much access: Identify which perm set is granting the access — don't remove from profile unless you've checked all perm set assignments
  3. Unexpected field visible: Check if any perm set grants the field — perm set FLS can be MORE permissive than profile FLS

Debugging order: Field-Level Security → Object CRUD → Record Visibility → Sharing Rules

Decision Matrix

ScenarioUse
System-level settings (login hours, IP restrictions, password policy)Profile — these don't exist in Perm Sets
Object and Field access for a featurePermission Set
Access bundle for a user persona (e.g. "Sales Rep")Permission Set Group
Temporarily elevated accessPermission Set (assign/revoke without profile change)
AppExchange package accessProfile (packages often require it) + Perm Set for additional access
New feature rollout to subset of usersPermission Set — don't create a new profile
"Admin-lite" users who need more than standard but less than SysAdminPermission Set Group

Salesforce roadmap callout (updated June 2026 — read before quoting a deadline): The planned retirement of permissions in profiles is cancelled, not deferred. Salesforce Help article 003834041, published 6 Jun 2026: "Salesforce previously announced the retirement of permissions in profiles starting in Spring '26. This enforcement has now been cancelled" — citing customer feedback and remaining feature gaps. No replacement end-of-life date has been published.

What this means in practice:

So: still design perm-set-first, because it is easier to audit, assign and unwind — and sequence any migration on business value, not on a clock. Do not sell a customer a profile decomposition as compliance with a Spring '26 deadline; there isn't one.

Permission Architecture Pattern

Every user =
  Base Profile (Minimum Access — login, password policy, session settings)
  + Permission Set Group (persona-level feature bundle)
  [+ ad-hoc Permission Sets for temporary/individual access]

Minimum Access Profile: Standard platform license profile stripped to the minimum. No object access. No field access. Just login settings. All access granted via Permission Sets.

Naming Conventions

ArtifactPatternExample
Permission Set[Object]_[Access Level]_[Context]Account_Edit_SalesRep, Case_Read_PortalUser
Permission Set Group[Persona]_[Feature Set]SalesRep_Core, CaseManager_Full
Base Profile[License Type]_MinimumAccessSalesforceUser_MinimumAccess
  1. Split the profile on the schema, not on judgement — walk the What Only a Profile Can Hold table above and sort every element into residue or migratable. Record the residue in templates/permission-set-design-template.md § Base Profile Residue; that list is the base profile file
  2. Build the manifest before the retrieve — name every object, field, tab, app, record type, layout and class whose permissions are in scope, then retrieve with --manifest. A Profile: retrieve without them returns an incomplete file (references/gotchas.md, "A Profile Retrieve Returns Only What the Rest of the Manifest Asked For")
  3. Write the pair — the base profile and the receiving permission set, from the shapes in references/metadata-examples.md. Map tabVisibilities→tabSettings and drop every <default> from the permission-set side; leave the default app and record type on the profile
  4. Check the licence pairing — SELECT Id, Profile.UserLicenseId FROM User against SELECT Id, LicenseId FROM PermissionSet. A mismatch blocks the assignment, so resolve it before the cutover rather than during it
  5. Lint the files — python3 skills/admin/permission-sets-vs-profiles/scripts/check_access_model.py --manifest-dir force-app/main/default. It flags migratable grants still sitting on a profile, profile-only elements wrongly placed in a permission set, edits to custom=false profiles, object permissions duplicated across a profile and a group member, an over-length description (PSVP-DESC-01 ERROR at 255+ characters, PSVP-DESC-02 INFO headroom at 200+), a permission set granting object Create/Edit with no standard-field FLS for that object (PSVP-FLS-01 WARN), and an empty profile with no permission set group anywhere in the tree to be its claimed access source (PSVP-FLS-02 WARN), and a profile that makes record types visible for an object while naming no default (PSVP-RT-DEFAULT-01 ERROR — the platform demands one default among the visible types or every listed type invisible; org-verified 2026-09-19). Exit policy: exit 1 only on a dangerous-permission grant, a migratable grant left on a profile, a profile-only element in a permission set, PSVP-DESC-01 (255+ characters), or PSVP-RT-DEFAULT-01 — all CRITICAL/ERROR/HIGH-class findings; a standard-profile edit, a duplicate grant, PSVP-FLS-01, PSVP-FLS-02, or an empty scan print as WARN/INFO and exit 0, and --strict promotes those to a failure. PSVP-DESC-02 is a separate advisory bucket — always printed and counted, never promoted even by --strict. A missing --manifest-dir is a usage error and exits 1 immediately.
  6. Deploy in order, permission set first — validate-only, then permission set, then group, then the stripped profile. Reversing the order leaves a window with no access; and because profile deploy overlays, any permission you mean to revoke must be written out explicitly as false
  7. Verify against the org, not the deploy log — run the IsOwnedByProfile and assignment-count SOQL in references/metadata-examples.md § Verify, and open Setup → Users → the migrated user → View Summary to confirm effective access matches the pre-migration baseline

For a full org-scale decomposition of a live profile — inventory, category classification, reuse scoring, PSG composition and a phased cutover — hand off to agents/profile-to-permset-migrator/AGENT.md (/migrate-profile-to-permset). This skill owns the decision; that agent owns the mechanics.


Salesforce-Specific Gotchas

Deeper treatment of all fifteen, with the guide passages behind them, in references/gotchas.md.

Proactive Triggers

Surface these WITHOUT being asked:

Output Artifacts

When you ask for...You get...
Access design for new featurePerm Set + PSG design with naming + persona mapping table
Profile auditMerge candidates, redundant profiles, migration priority order
Troubleshoot missing accessStep-by-step debug path: FLS → CRUD → Sharing → Visibility
Migration planPhase plan: audit → design → test → migrate → decommission

Reference Files

FileRead it when
references/metadata-examples.mdWriting the base profile and the receiving permission set — element-by-element split table, deployable XML for all three types, package.xml, retrieve/deploy commands, verification SOQL
references/gotchas.mdA deploy "worked" but nothing changed, a retrieve came back empty, an assignment was rejected, or a migrated user lost their app and layout
references/examples.mdWorking a five-profile-to-three-PSG migration end to end, or picking audit SOQL for the current-state inventory
references/well-architected.mdJustifying the decomposition against Security and Operational Excellence now that the Spring '26 forcing function is gone, or citing the official sources
references/llm-anti-patterns.mdSelf-checking generated output for profile cloning, a cancelled deadline, "revoke with a permission set", or a migration plan that ignores managed packages
templates/permission-set-design-template.mdDocumenting the design for review — residue list, persona matrix, PS and PSG definitions, FLS audit, cutover plan
scripts/check_access_model.pyLinting profile and permission-set XML before a deploy

Package files