Dylan AndersenDylan Andersen's Docs
Cursor + SalesforceExpert Cursor

Permission Sets & Access

"Why can't my user see this?" solved once, with permission sets, permission set groups, and a small amount of discipline

Every demo ends the same way. You log in as the test user, your new object or agent or tab isn't there, and you spend ten minutes hunting through Setup for the permission you forgot. This page is the version of that hunt you only do once.

Permission Sets and Access hero

The shortcut

If you only read one thing: build a Permission Set Group for your POC, put every permission set you create into it, and assign the group to your users. You'll never wonder which permission set has what again.

The access layers

Salesforce stacks access in a specific order. An SE needs to know which layer they're working at.

  1. Profile. One per user. Legacy. Treat this as read-only in a POC.
  2. Permission Sets. Additive. Grant object, field, tab, Apex, and page access on top of the profile.
  3. Permission Set Groups. A bundle of permission sets with optional mute settings. The modern way to assemble access.
  4. Sharing Rules and Role Hierarchy. Who can see which records once they have the CRUD permission.
  5. Record Types and Page Layouts. What a user sees on the record once they can open it.

SEs hit the Permission Set / Permission Set Group layer most. The rest matters for real implementations.

Why groups, not sets

An SE instinct is to build one giant permission set called POC_Access and dump everything in it. Don't. Three months later you'll have no idea what's in there.

Build one permission set per capability, group them, and assign the group.

Permission Set Group: CS_Agent_Demo_User
├── CS_Agent_Object_Access       (CRUD on Case, Contact, CustomerInsight__c)
├── CS_Agent_LWC_Access          (Expose the agent LWC)
├── CS_Agent_Apex_Access         (CustomerRoutingService.cls)
└── CS_Agent_Agentforce_Access   (The Agentforce permission set from the managed app)

When the demo breaks because the user can't see a field, you open one permission set, not four hundred lines of XML.

The fast path with Cursor

The sf-permissions skill exists for exactly this. Install it, then:

Activate the sf-permissions skill. I need a permission set called
CS_Agent_Object_Access that grants Read and Edit on Case, Read on Contact,
and full access on CustomerInsight__c including all its fields. Read the
object files in @force-app/main/default/objects/ to pick up field names.

The skill produces a clean .permissionset-meta.xml with no stray defaults. Deploy it:

sf project deploy start --source-dir force-app/main/default/permissionsets/CS_Agent_Object_Access.permissionset-meta.xml

Then assign:

sf org assign permset --name CS_Agent_Object_Access --target-org my-scratch

Building a group

Once you have two or three permission sets, wrap them in a group. You can hand-write the XML or have the agent do it.

<?xml version="1.0" encoding="UTF-8"?>
<PermissionSetGroup xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>Everything a demo user needs for the CS Agent POC</description>
    <label>CS Agent Demo User</label>
    <permissionSets>CS_Agent_Object_Access</permissionSets>
    <permissionSets>CS_Agent_LWC_Access</permissionSets>
    <permissionSets>CS_Agent_Apex_Access</permissionSets>
    <permissionSets>CS_Agent_Agentforce_Access</permissionSets>
</PermissionSetGroup>

Save that to force-app/main/default/permissionsetgroups/CS_Agent_Demo_User.permissionsetgroup-meta.xml, deploy, and assign:

sf org assign permsetgroup --name CS_Agent_Demo_User --target-org my-scratch

After assignment, Salesforce recalculates the effective permissions. This can take a minute in a fresh org. If sf org open shows the old state, wait and refresh.

The "who has access to what" conversation

Customers ask this a lot. Point the agent at your permission sets directly:

Read every file in @force-app/main/default/permissionsets/ and @force-app/main/default/permissionsetgroups/.
List who (which permission set group) has access to the CustomerInsight__c
object and what level of access they get.

Paste the answer into your hand-off doc. For real implementations, the sf-permissions skill also does a proper audit report.

Common traps

The tab doesn't show up

You granted object access, not tab access. Tabs are a separate permission. Either add the tab to the permission set or enable it from App Settings.

The LWC is on the page but the user can't see its data

Two layers to check: does the user have Read on the SObject the wire is pulling, and does the user have access to the Apex controller (if it's Apex-backed)?

Agentforce isn't showing up for the demo user

The Agentforce managed app ships its own permission sets. They're named AgentforceUser, AgentforceSessions, and a few others depending on release. Add the relevant ones to your group.

"The scratch org admin has everything, but my demo user doesn't"

That's the whole point. Scratch org admins have the System Administrator profile, which is not reproducible for customer users. Always test your demo logged in as a non-admin user. A scripted demo reset (see Demo Reset & Repeatability) should create and assign that user for you.

Profiles sneak into your retrieve

If you retrieve metadata from a customer sandbox, Profile entries come with it and they're enormous. Add profiles/** to .forceignore unless you specifically need them. See Source Tracking & .forceignore.

Verification

Run this in your terminal after assigning the group:

sf org assign permset list --target-org my-scratch

You'll get a table of assignments by user. If your demo user isn't in the list with the expected permission set or group, assignment didn't take.

Next moves

  • Project Anatomy for where the permissionsets/ folder sits.
  • Demo Reset & Repeatability to script the "create demo user, assign group" step.
  • Install the sf-permissions skill so the agent builds these for you instead of you hand-writing XML.

On this page