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.

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.
- Profile. One per user. Legacy. Treat this as read-only in a POC.
- Permission Sets. Additive. Grant object, field, tab, Apex, and page access on top of the profile.
- Permission Set Groups. A bundle of permission sets with optional mute settings. The modern way to assemble access.
- Sharing Rules and Role Hierarchy. Who can see which records once they have the CRUD permission.
- 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.xmlThen assign:
sf org assign permset --name CS_Agent_Object_Access --target-org my-scratchBuilding 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-scratchAfter 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-scratchYou'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-permissionsskill so the agent builds these for you instead of you hand-writing XML.