Permission-Set-Led Security Model

Here are resources from my May 30, 2025 Texas Dreamin’ presentation, “Diving Deep into Profiles and Permissions”:

First, I recommend the following (free!) Salesforce tools:

Use these to migrate your org from a Profile-led model to a Permission-Set-led model.

Salesforce MVP Louise Lockie created terrific resources explaining why and how to migrate.

In my presentation, I walked through the practical steps of migrating a clean Developer Edition org‘s three Custom Profiles to Permission Sets:

  1. download your Profile XMLs
  2. create upload-ready zip files1 for the Salesforce Metadata API; and finally
  3. deploy those zip files

In the “Bonus Round!” section of the preso, I showed how Permission Sets’ field settings can be queried using SOQL, like this:

  SELECT Parent.Name, Field, 
         PermissionsEdit, 
         PermissionsRead 
    FROM FieldPermissions
ORDER BY Parent.Name, Field

and how a Permission Set can be created by adding rows to the PermissionSet and FieldPermissions tables.2

Rights to all code in this post are subject to The MIT License.3

  1. Expanding on step 2: I created ChatGPT scripts to

    create a “Common Permissions” Permission Set for privileges that all three Profiles have in common. (You’ll need to attach the Profiles’ XMLs to your request.)

    Create a Salesforce Metadata API-deployable zip file that contains a Permission Set named "Common Permissions".
    • The Permission Set should be pretty-formatted and have the Field Permissions, Object Permissions, and User Permissions that are common to all Profiles attached to this request. The Profiles' filenames contain special characters that might cause a file access issue. The XML parser should account for the fact that all entries are found within the namespace "http://soap.sforce.com/2006/04/metadata".
    • It should have a tag named "license" with the value "Salesforce".
    • It should have a tag named "label" with the value "Common Permissions".
    • It should exclude any tags that cannot be specified in a Permission Set.
    • The "Common Permissions" Permission Set should be in a folder named "permissionsets" inside the zip file.
    • The zip file should also include a package.xml file.


    create Permission Sets for privileges that are distinct to some (ie, one or more) of the Profiles, but not all. (You’ll need to attach the Profiles’ XMLs to your request.)

    Compare the attached Salesforce Profile XMLs.
    • Note that the filenames contain special characters that might cause a file access issue.
    • The XML parser should account for the fact that all entries are found within the namespace "http://soap.sforce.com/2006/04/metadata".

    For each Salesforce object, compare (1) the Object Permissions, and (2) the associated Field Permissions for that object, across all Profiles.

    For each Salesforce object where there are differences, create pretty-formatted Permission Sets that contain each permutation of Object Permissions and Field Permissions that differ. Each Permission Set should be named "[Object] Permissions [Create], [Delete], [Edit], [Read], [Modify All], [View All] V[X], E[Y]", where 1) "[Object]" is the name of the Salesforce object; 2) each of the values "[Create]", "[Delete]", "[Edit]", "[Read]", "[Modify All]", and "[View All]" is shown if that particular value is set to True for that particular Permission Set; and 3) "[X]" is the number of fields that are readable in that Permission Set, and "[Y]" is the number of fields that are editable in that Permission Set.

    Create a Salesforce Metadata API-deployable zip file that contains all the Permission Sets.
    • Each Permission Set in the zip file should be well-formatted and have the Field Permissions and Object Permissions for a single object.
    • Each Permission Set should have a tag named "license" with the value "Salesforce".
    • Each Permission Set should have a tag named "label" with the value equal to the Permission Set's name.
    • Each Permission Set file should have a Salesforce Metadata API-friendly version of the name (where characters have been replaced by escape values where needed).
    • The Permission Sets should be in a folder named "permissionsets" inside the zip file.
    • The zip file should also include a package.xml file.

    reassign Users to the Minimal Profile, and assign them to the correct Permission Sets. (You’ll need to edit the text to reflect the UI labels of your Profiles and Permission Sets. Once updated, this script should be run from Developer Console’s Execute Anonymous window.)

    write Apex code that can run from Salesforce's Execute Anonymous window.

    it should
    1) assign all active users to the Profile named "Minimum Access - Salesforce",
    2) assign them to the Permission Set "Common Permissions", and
    3) also assign them to the appropriate Permission Sets created above (their labels are "Case Permissions Create, Delete, Edit, Read V[20], E[13]", "Case Permissions Create, Edit, Read V[19], E[12]", "Lead Permissions Create, Delete, Edit, Read V[18], E[15]", "Lead Permissions Create, Delete, Edit, Read V[20], E[17]", "Lead Permissions Create, Delete, Edit, Read V[22], E[19]", "Opportunity Permissions Create, Delete, Edit, Read V[13], E[13]", "Opportunity Permissions Create, Delete, Edit, Read V[16], E[16]", "Solution Permissions Create, Delete, Edit, Read V[3], E[3]", and "Solution Permissions Create, Read V[3], E[3]") that replicate the Permissions they had when they were assigned to the Profiles "Custom: Marketing Profile", "Custom: Sales Profile", or "Custom: Support Profile".
    ↩︎
  2. Here’s the ChatGPT prompt which generates the Apex code to create the “Opportunity – Read/Write All Fields” Permission Set. (Before you run the Apex code, you need to create a Remote Site Setting in Salesforce for your base URL, something like https://[name]-dev-ed.develop.my.salesforce.com for a Developer Edition, or https://[name].my.salesforce.com/ for a Production org.)
    write Apex code that I can run from the "Execute Anonymous" window in Salesforce's Developer Console that queries the EntityParticle table via Tooling API v63.0 to determine all Opportunity fields whose "IsPermissionable" attribute is True. Use getOrgDomainUrl() instead of getSalesforceBaseUrl() to get my org's base URL. I've already created a Remote Site Setting that references the base URL of my Salesforce org.

    create a Permission Set named "Opportunity - Read/Write All Fields" (by inserting a new record in the PermissionSet table) that grants
    1) Read and Edit access to Opportunity (by inserting a record in the ObjectPermissions table),
    2) read/write access to the subset of those "IsPermissionable = True" fields where the "IsUpdatable" attribute is True (by inserting records in the FieldPermissions table), and
    3) read access to the subset of the "IsPermissionable = True" fields where only the "IsUpdatable" attribute is False (by inserting records in the FieldPermissions table).
    ↩︎
  3. Copyright © 2025 Ezra Kenigsberg

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ↩︎