Why This Is Useful
Apex managed sharing is a way to grant record access programmatically, using your own logic, when nothing declarative will do the job.
At some point in your Salesforce career you’ll hit a sharing requirement where sharing rules won’t work, the role hierarchy won’t work, territories won’t work, and manual sharing (which I rarely suggest anyway) won’t work either. That’s when this comes in.
And here’s my honest advice up front: use it only when you’ve genuinely exhausted the declarative options. Every share you create in code is a share you now own, test and maintain, and sharing recalculation is one of the more painful things to debug in a large org.
Share Objects
Every object with a private or read-only org-wide default gets an automatic share object:Account → AccountShareCase → CaseShareMy_Object__c → My_Object__Share
Note the naming difference on custom objects: it’s __Share, not __c__Share. That trips people up constantly.
These are real records you can insert, query and delete. Granting access is just DML.
Standard Objects
public with sharing class AccountSharingService
{
public void shareAccounts(Map<Id, Id> accountIdToUserId)
{
List<AccountShare> sharesToCreate = new List<AccountShare>();
for (Id accountId : accountIdToUserId.keySet())
{
AccountShare share = new AccountShare();
share.AccountId = accountId;
share.UserOrGroupId = accountIdToUserId.get(accountId);
share.AccountAccessLevel = 'Edit';
share.OpportunityAccessLevel = 'Read';
share.CaseAccessLevel = 'Read';
share.RowCause = Schema.AccountShare.RowCause.Manual;
sharesToCreate.add(share);
}
//One DML statement, however many shares
Database.insert(sharesToCreate, false);
}
}Four fields matter on every share: the record, who gets access, what level, and why.UserOrGroupId can be a User or a public Group, and groups are almost always the better answer. One share pointing at a group beats fifty shares pointing at individuals, and adding somebody later means adding them to the group rather than recalculating anything.Database.insert(list, false) with false for allOrNone means one duplicate doesn’t blow up the entire batch. Worth it here, because inserting a share that already exists throws.
Note AccountShare also carries access levels for child Opportunities and Cases. Accounts are special like that.
The Standard Object Problem
Look at that RowCause. On standard objects, Manual is your only option.
Which matters enormously, because as I covered in this quick tip, manual shares are deleted automatically when a record’s owner changes. Silently. Including shares your code created.
So if you’re doing Apex sharing on standard objects, you need to handle that. Usually an after update trigger that recreates the shares when OwnerId changes.
It’s a real limitation and worth knowing before you design around it.
Custom Objects Are Much Better
On custom objects you can define your own Apex sharing reason, and that changes everything.
Set one up under Object Manager → your object → Apex Sharing Reasons, then:
My_Object__Share share = new My_Object__Share();
share.ParentId = recordId;
share.UserOrGroupId = groupId;
share.AccessLevel = 'Edit';
share.RowCause = Schema.My_Object__Share.RowCause.Project_Team__c;
insert share;Note ParentId rather than a named field, and the __c on the row cause.
Custom row causes give you two genuinely valuable things:
1. They survive ownership changes. No trigger needed to patch things up.
2. Admins can’t casually delete them through the UI, so your sharing model stays intact.
You also get to see why someone has access. A sharing hierarchy showing “Project Team” is far more useful than one showing “Manual” for everything.
Things That Will Catch You Out
You can’t share a record with its owner. They already have full access, and trying throws an error.
You can’t grant less than they already have. A share can only add access, never reduce it.
Share objects only exist when they need to. If your org-wide default is Public Read/Write, there’s nothing to share and the object may not be usable.
Removing access means deleting the share:
List<AccountShare> toRemove = [SELECT Id FROM AccountShare
WHERE AccountId IN :accountIds
AND RowCause = 'Manual'];
delete toRemove;Always filter by RowCause. Delete indiscriminately and you’ll remove shares created by the role hierarchy and sharing rules, which is a genuinely bad afternoon.
Use without sharing deliberately. Your sharing service usually needs to create shares for records the running user can’t see. That’s a legitimate use of without sharing, and one of the few places I’d actively recommend it. Just be deliberate about it.
Before You Build Any Of This
Genuinely check the declarative options first:
Criteria based sharing rules handle a surprising amount and recalculate automatically.
Public groups plus sharing rules cover most team-based scenarios.
Role hierarchy handles anything manager-shaped.
Manual sharing covers genuine one-offs.
If any of those work, use them. They’re maintained by Salesforce, they recalculate themselves, and an admin can change them without a deployment.
Apex managed sharing is for the genuinely bespoke cases. When you do need it, it’s powerful, and now you know both flavours and the traps in each.
Full details in the Apex managed sharing documentation.
Get Coding With The Force Merch!!
We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.
Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!
Check Out More Coding With The Force Stuff!
If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!
Youtube
Patreon
Github
Facebook
Twitter
Instagram
Salesforce Development Books I Recommend
Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps
Good Non-SF Specific Development Books:
Clean Code
Clean Architecture