The SchemaForce Team

Migrating from profiles to permission sets: how to compare what you have

Salesforce points you toward permission sets, but moving off profiles means proving the new setup grants exactly what the old one did. How to compare a profile and a permission set, field by field.

Cover Image for Migrating from profiles to permission sets: how to compare what you have
Sales User (profile) → Sales Access (permission set)
equivalence check
3 fields differ · 1 object permission missing
Blast radiusobject accessfield-level securitysystem perms

Salesforce has pointed admins toward permission sets for years: field-level security, object access, and system permissions are meant to live on permission sets and permission set groups, with profiles kept thin. There's no firm deadline — the hard retirement date came and went — but the direction is settled, and most teams will move their access model off profiles eventually.

The migration is conceptually simple and operationally nerve-wracking: recreate what a profile grants as one or more permission sets, assign them, and stop leaning on the profile. The risk is in the word "exactly." If the new permission set grants a little more or a little less than the profile did, you've either opened access you didn't intend or broken someone's day — and you won't know which until you compare the two, grant for grant.

What you actually have to compare

"What a profile grants" is three separate layers, and a faithful migration has to match all three:

  • Object access — Create, Read, Edit, Delete, plus View All and Modify All, per object.
  • Field-level security — Read and Edit on each field.
  • System permissions — the standalone ones like API Enabled and Modify All Data.

Miss a row in any layer and the permission set isn't equivalent. The field layer is where it usually goes wrong, because it's the longest and the most tedious to eyeball.

Comparing by hand

The two layers that matter most are queryable, so you can pull a profile and a permission set side by side. For field-level security:

SELECT Parent.Profile.Name, Parent.Label, SobjectType, Field,
       PermissionsRead, PermissionsEdit
FROM FieldPermissions
WHERE Parent.Profile.Name = 'Sales User'
   OR Parent.Label = 'Sales Access'
ORDER BY SobjectType, Field

ObjectPermissions works the same way for the object layer. The one subtlety: a profile's grants live on its auto-created permission set, so you match the profile on Parent.Profile.Name and a regular permission set on Parent.Label — which is why the WHERE clause uses both. Then you drop the two result sets into a spreadsheet and diff them.

It's doable, and for a single small profile it's fine. But the diff is manual, the field list is long, and the exercise has the property you least want in an access change: a single missed row reads as success. You don't get an error — you get a quiet gap that surfaces weeks later as "why can this rep edit that field?"

The new permission set has to grant exactly what the profile did — no more, no less. "Roughly the same" is an access bug waiting to be filed.

the standard a migration has to meet

Comparing two grantors directly

Once the org's metadata is in SchemaForce, this becomes a side-by-side instead of a spreadsheet. On the permissions page you pick two grantors — a profile on one side, the candidate permission set (or permission set group) on the other — and it diffs all three layers at once: object access, field-level security, and system permissions, with a "show only differences" view so you see the gaps and nothing else. A grantor detail view lets you read everything a single profile grants first, which is the natural place to start when you're deciding what the replacement permission set needs to contain.

Because the comparison runs over the same metadata for any two grantors, it answers the migration question directly: are these two equivalent, and if not, exactly where do they diverge?

What this checks — and what it doesn't

A few honest boundaries. The comparison is about what each grantor grants, within a single connected org, as of your last full sync — it isn't a live readout, and it doesn't reach across two orgs. It compares configuration, not assignments: it won't tell you who is assigned to the profile versus the permission set, so it can't confirm user-level coverage of your migration. And SchemaForce is read-only on your Salesforce metadata — it surfaces the gap precisely, but you make the change in Setup. As everywhere here, it works from metadata alone and never touches the contents of your records.

Moving off profiles is mostly one careful comparison repeated many times. The faster that comparison is — and the harder it is to miss a row — the less the migration rides on nobody making a spreadsheet mistake.

Sales User (profile) → Sales Access (permission set)
grant-for-grant diff
3 fields differ
1 object permission missing
Blast radiusobject accessfield-level securitysystem perms
Compare a profile and a permission set across object, field, and system access — differences only.Compare grantors free
ShareLinkedInX

See your own org in minutes.

Connect Salesforce, explore every object and field, and ask your schema anything — metadata only, free to start.