Sharing in Code and Flow, and System Mode
이 콘텐츠는 아직 번역되지 않았습니다.
Module 6 of 9 in the chapter Steve’s 25 Ways to Share a Record: The Map, chapter 5 of Securing Salesforce: Sharing and Visibility. Each module stands on its own.
The question this module answers: When none of the out-of-the-box ways can express who should see a record, how does code share it, and what does it take to keep that sharing right?
Sharing rules are powerful, but they have limits: 50 buckets for criteria, 300 in all. And there are times you need to decide based on the record and something about it, or one of its parents, or a linked record, or something about the user, a skill or a region. When you get down to that level of detail, programmatic sharing is the best tool you have.
It used to be called Apex sharing, because you needed Apex. Now it can be Apex or flows. That’s why it’s its own set: Sets 1 to 4 are what Salesforce gives you, and Set 5 is what you build. Two ways: programmatic sharing, and code that runs in system mode.
And my rule for this set: the code should be saved for the cases that go beyond what the others can do.
Way 16: programmatic sharing, inserting a share record
Section titled “Way 16: programmatic sharing, inserting a share record”Here’s what programmatic sharing really is. You’re inserting a record into the share table. That’s it. The same table you’ve watched grow in this chapter, the shadow image of the main table. Every other way in this chapter has Salesforce write that row for you. Here, you write it yourself.
That insert can come from Apex. It can come from a flow. It can come from an integration through the API. Whichever one does it, the main thing is the same: a new row in the share table, and that row is the share.
“To access sharing programmatically, you must use the share object associated with the standard or custom object for which you want to share,” such as AccountShare, or MyCustomObject__Share for a custom object. A detail object in a master-detail relationship has no share object of its own: its access comes from the master (Sharing a Record Using Apex (opens in a new tab), Apex Developer Guide, Winter ‘27, checked 2026-11-01).
The pieces of a share record
Section titled “The pieces of a share record”Every share record you insert has four pieces. Which record, who, how much, and why. The field names differ a little between a custom object’s share table and a standard one like AccountShare:
| The question | On a custom object: Runway__Share | On a standard object: AccountShare | What goes in it |
|---|---|---|---|
| Which record? | ParentId | AccountId | The ID of the record you’re sharing. It can’t be changed later. |
| Who? | UserOrGroupId | UserOrGroupId | A user, or a group: a public group, a role’s group, or a territory’s group. It can’t be changed later. |
| How much? | AccessLevel | AccountAccessLevel, plus OpportunityAccessLevel and CaseAccessLevel | Read or Edit. It must be more than the org-wide default gives. |
| Why? | RowCause | RowCause | The sharing reason. Manual, or a reason of your own. It can’t be changed later. |
Sources for the table: Sharing a Record Using Apex, and AccountShare (opens in a new tab) (Object Reference, Winter ‘27, checked 2026-11-01).
A few details that catch people:
- Who can be a group. That’s very powerful. One share row to a group reaches everyone in it, and as module 3 showed, groups can contain groups, roles, and roles and subordinates. One thing you can’t do: “You can’t grant access to unauthenticated guest users using Apex” (Sharing a Record Using Apex).
- How much stops at Edit. “The All access level is an internal value and can’t be granted” (same page). Full access stays with the owner and the people above them.
- An account share carries its children. On AccountShare, one row also sets access to the account’s opportunities and cases. Its
ContactAccessLevelisn’t one you can set when you create the row (AccountShare). - Why is the piece people skip. The row cause decides what happens to the share later: whether an owner change deletes it, and which code can find it and remove it.
Inserting it in Apex
Section titled “Inserting it in Apex”Here’s the insert in Apex, for my runway object and a sharing reason I called Pilot:
// Share one runway with one pilot, Read Only, for the reason Pilot.Runway__Share share = new Runway__Share();share.ParentId = runwayId; // which recordshare.UserOrGroupId = pilotUserId; // who: a user or a groupshare.AccessLevel = 'Read'; // how much: Read or Editshare.RowCause = Schema.Runway__Share.RowCause.Pilot__c; // why
// Writing shares for other people is done as the system, on purpose.Database.SaveResult result = Database.insert(share, false, AccessLevel.SYSTEM_MODE);Why system mode on the insert? Because the person whose action runs this code usually isn’t allowed to share the record themselves. Salesforce: “Only users with ‘Modify All Data’ permission can add or change Apex managed sharing on a record” (Understanding Sharing (opens in a new tab), Apex Developer Guide, Winter ‘27, checked 2026-11-01). So the code asks for system mode, out loud, where a reviewer can see it. More on that in way 17.
The same insert on an account, with a group as the target:
AccountShare share = new AccountShare();share.AccountId = accountId; // which recordshare.UserOrGroupId = groupId; // who: here, a public groupshare.AccountAccessLevel = 'Edit'; // how much, on the accountshare.OpportunityAccessLevel = 'Read'; // ...on its opportunitiesshare.CaseAccessLevel = 'None'; // ...on its casesshare.RowCause = Schema.AccountShare.RowCause.Key_Account__c; // why: a reason of your ownDatabase.insert(share, false, AccessLevel.SYSTEM_MODE);That last line about the reason is new. Until this month, the only reason you could write on AccountShare was Manual. The next section is about why that mattered.
Watch the rows go in
Section titled “Watch the rows go in”Here’s my demo. Paul the pilot sees three runways. I query the runway share table for Paul: three rows. One because he owns a runway, one a manual share, one a share my code already wrote with a reason called Pilot. Then I take another runway’s ID, run the insert above for Paul with the Pilot reason, and query again: four. Paul refreshes, and there’s a fourth runway. Do it twice more, and he has six.
Unlike teams, there’s no screen to manage these. Nobody adds or removes them by hand. The share just grants the access, and it shows up with your reason’s label when someone looks at the record’s sharing.
Inserting it in a flow
Section titled “Inserting it in a flow”A flow does the same insert with a Create Records element on the share object. Salesforce’s own help article on making a lookup behave like Controlled by Parent does exactly this: a record-triggered flow finds an account’s contacts, checks for share records that already exist, and creates Contact Share records for the account owner (Enforce Parent Sharing on Lookup Relationships (opens in a new tab), Salesforce Help, checked 2026-11-01).
The four pieces are the same four fields, filled in on the element: the record’s ID, the user or group, the access level, and the row cause. Notice that the flow checks first whether the share already exists. Your code should too.
Sharing reasons, and why they matter
Section titled “Sharing reasons, and why they matter”“Apex managed sharing must use an Apex sharing reason.” With one, “Apex managed sharing is maintained when the record owner changes or is deactivated” (Sharing a Record Using Apex; Understanding Sharing).
On a custom object, you create a sharing reason on the object. A tip: you still go to Classic to do it. “Apex sharing reasons aren’t available in Lightning Experience. Use Salesforce Classic to create sharing reasons within the UI” (Sharing a Record Using Apex).
Standard objects: what changed in October 2026
Section titled “Standard objects: what changed in October 2026”For over ten years, standard objects didn’t allow a custom sharing reason. A student asked me about this in a class, and I made a video to find out. On an account, the only reason my code could use was Manual. Change the owner, and the share my code wrote was wiped out, just like a manual share. I tried the Team reason instead: not allowed. So the only answer was a trigger listening for an owner change, putting the shares back.
That’s changed. Salesforce’s own Developers blog, from the product manager for sharing, says: “Custom row causes, also called Apex sharing reasons, are rolling out for standard objects in the Metadata API and will reach all orgs by the end of October.” “You can also assign a custom row cause in Flow,” and with one, “Salesforce doesn’t delete them when the owner changes” (Protect Apex Shares: Custom Row Causes on Standard Objects (opens in a new tab), Salesforce Developers blog, checked 2026-11-01). The Apex Developer Guide says the same: for standard objects, create sharing reasons “using the Metadata API or Tooling API” (Sharing a Record Using Apex). Not every page has caught up yet: the AccountShare reference still says that when you create a share, “the only permitted value is Manual” (AccountShare). Until the rollout reaches your org, Manual is all you’ll get.
The same blog points at the other Winter ‘27 change from module 4, the org setting to keep manual shares when records are transferred. Its advice: “If you don’t create shares in code, turn on Keep Manual Shares when Transferring Records… If code, a Flow, or an integration creates shares on standard objects, switch to a custom row cause.” I agree. A reason of your own keeps your code’s shares apart from the shares people make, and shows why each user has access. If your org has old code writing Manual shares on standard objects, it’s time to review it. And test it, with the three users, before you trust it.
The work behind it
Section titled “The work behind it”Programmatic sharing is very powerful, but you own all of it. Before you write the first share, have these in place:
- The criteria. Which records go to which users or groups, and where that’s decided: hard-coded, driven by another object, or in custom metadata.
- Changes to records. When a record is created or edited, who should gain or lose access? Usually a record-triggered flow or a trigger on the object.
- Changes to users. A new user, or a user who gains a skill or moves to another region, may need shares added or removed. Often a flow or trigger on the user too.
- Changes to the criteria. If the rule itself changes, you need a batch to recalculate. “Apex managed sharing recalculation is supported for both custom and standard objects” (Sharing a Record Using Apex).
- Volume. Governor limits, data loads, and the first load: a million records, or a thousand users at once. Is your code or flow bulkified? Will it write the shares, or will you load them yourself?
And one more from Winter ‘27. Salesforce now does some sharing recalculation asynchronously after large changes to groups or roles, and “If Apex code and flows require that share records be updated immediately, the code and flows can break when this release update is enforced” (Winter ‘27 release notes: Permissions and Sharing (opens in a new tab), Salesforce, checked 2026-11-01).
So before you write a share, ask whether something with screens can do it. A flow can add a team member, and the user can see and manage it. A flow can put accounts and users into territories, even a hidden branch of territories used only as targets. That’s a kind of programmatic sharing too, without writing a single share row.
Way 17: system mode
Section titled “Way 17: system mode”The other way code decides who sees a record is by skipping sharing altogether. Code that runs in system mode reads past the share table, like View All.
In Apex, that’s no longer the default. Since Summer ‘26, database operations in code saved at API version 67.0 or later run in user mode by default: they “enforce the sharing rules, field-level security, and object permissions of the current user,” and they skip those settings “only if you explicitly set them to run in system mode. This requirement promotes intentionality” (Summer ‘26 release notes: Database Operations Run in User Mode by Default, Not System Mode (opens in a new tab), checked 2026-11-01).
In a flow, it’s a setting on the flow. A flow set to System Context without Sharing “can access all data” (Set Flow Run Context (opens in a new tab), Salesforce Help, checked 2026-11-01).
What system mode looks like in code
Section titled “What system mode looks like in code”Salesforce’s advice: “To determine the access mode of a database operation regardless of the API version, we recommend that you always set an access mode explicitly” (Summer ‘26 release notes). So in good code, you can see the mode on every line that touches data.
On a query, it’s a clause:
// User mode: this user's sharing, object permissions and field-level security.List<Account> mine = [SELECT Id, Name FROM Account WITH USER_MODE];
// System mode: on purpose. The class's sharing keyword decides about records (below).List<Account> all = [SELECT Id, Name FROM Account WITH SYSTEM_MODE];On a save, it’s two words:
update as user acct; // checked against this user's accessupdate as system acct; // not checked: on purposeOn a Database method, it’s a parameter:
List<Account> hot = Database.query( 'SELECT Id, Name FROM Account WHERE Rating = \'Hot\'', AccessLevel.USER_MODE);The old WITH SECURITY_ENFORCED clause “is removed as of API version 67.0. Use WITH USER_MODE instead” (Summer ‘26 release notes).
System mode and the class’s sharing keyword
Section titled “System mode and the class’s sharing keyword”Here’s the part that trips people up. System mode skips object permissions and field-level security. Whether it also skips sharing depends on the class. Salesforce: “if you set the operation to system mode, the sharing declaration of the calling class determines whether the operation respects the user’s record-level permissions” (Summer ‘26 release notes).
// System mode, but sharing still applies: only the accounts this user can see,// without checking their object permissions or field-level security.public with sharing class AccountLookup { public static List<Account> find() { return [SELECT Id, Name FROM Account WITH SYSTEM_MODE]; }}
// System mode, and no sharing: every account in the org.// This is way 17. Have a reason, and a reviewer.public without sharing class AccountLookupForEveryone { public static List<Account> find() { return [SELECT Id, Name FROM Account WITH SYSTEM_MODE]; }}And the reverse: “If you set a database operation to user mode, the operation respects the user’s sharing rules and overrides the calling class’s sharing declaration” (same notes). So WITH USER_MODE is the safe one: it means the user’s access, whatever the class says.
So when you review code for way 17, look for two things together: system mode on the operation, and without sharing on the class. That pair reads past every row in the share table.
Sometimes that’s exactly what you want. Chapter 4, module 5 used it: one field only the assigned rep sees, through an LWC whose Apex reads it in system mode, on purpose, and checks the user. But every piece of code in system mode is a door around everything in this chapter. Know where each one is, why it’s there, and who reviewed it.
The share table: the full power
Section titled “The share table: the full power”This is the full power of the share table. Every other way writes the rows Salesforce decides. Code inserts the rows you decide, with a reason you name. On AccountShare, your code’s row carries your own row cause, next to Owner, Rule, Manual, Team, Territory and ImplicitParent.
And system mode, like View All, writes no row at all. You won’t find it by querying the share table. You find it by reading the code and the flow settings.
Where this goes deep
Section titled “Where this goes deep”Chapter 11, Programmatic Sharing: Apex and Flow Managed Sharing, builds it: sharing reasons, the triggers and flows that keep shares right, recalculation and volume. Chapter 17, Sharing When Writing Apex: Secure Code, covers user mode, system mode and reviewing code that skips sharing. For now, know the two, and know what you’re signing up for.
What an architect decides here
Section titled “What an architect decides here”- Which requirements truly need code, after Sets 1 to 4 and the screens of teams and territories.
- The sharing reasons, one per purpose, on custom and now standard objects.
- What keeps the shares right: on record changes, user changes, criteria changes and data loads.
- Whether old code writing Manual shares on standard objects moves to a reason of its own.
- Every piece of Apex and every flow that runs in system mode, with its reason and its reviewer.
Beyond the exam: what AI changes. An agent’s actions are often flows and Apex. If an action runs in system mode, the agent reads past sharing when it calls it, whatever its user can see. Before you give an agent an action, check its run context. And if code writes shares for an agent’s user, give them a reason of their own, so you can find and remove them all later. Chapter 20 covers agents.
Explore
Section titled “Explore”- Sharing a Record Using Apex (opens in a new tab), Apex Developer Guide (checked 2026-11-01)
- Understanding Sharing (opens in a new tab), Apex Developer Guide (checked 2026-11-01)
- Protect Apex Shares: Custom Row Causes on Standard Objects (opens in a new tab), Salesforce Developers blog (checked 2026-11-01)
- Winter ‘27 release notes: Permissions and Sharing (opens in a new tab), Salesforce (checked 2026-11-01)
- Summer ‘26 release notes: Database Operations Run in User Mode by Default, Not System Mode (opens in a new tab), Salesforce (checked 2026-11-01)
- Enforce Parent Sharing on Lookup Relationships (opens in a new tab), Salesforce Help (checked 2026-11-01)
- AccountShare (opens in a new tab), Object Reference (checked 2026-11-01)
- Set Flow Run Context (opens in a new tab), Salesforce Help (checked 2026-11-01)
