💬 In plain words: Every object has a hidden __Share table — each row is one key handed to one user/group with a reason. Manual sharing writes that row by hand. Apex managed sharing writes it in code when your logic decides who should see what.
📌 Example: On one confidential deal, the owner clicks Share and adds the legal counsel. That's one row in OpportunityShare with rowCause=Manual. Your 'reviewer field' automation writing the same kind of row from a trigger = Apex managed sharing.
🎬 Real-Life Example: The Backup Buddy Who Could Not See the Parcel Every Delivery has a backup driver, picked at runtime from a lookup. OWD on Delivery__c is Private. The backup must read the record. Nothing in roles or sharing rules can know who that person is in advance.
The Old/Bad Way: Give drivers View All on Delivery__c, or open OWD to Public Read. Now every driver sees every parcel — payment details, home addresses, all of it.
Why this is bad: You solved one person's access by removing everyone's privacy. Auditors will find it. Regulators may too.
The New/Good Way: 1. Keep OWD Private. 2. A trigger writes one Delivery__Share row: ParentId = the delivery, UserOrGroupId = the backup, AccessLevel = Read, RowCause = your custom sharing reason. 3. When the backup changes, delete that row and write a new one.
The payoff: Every share object is just a table of "who gets in" rows. When record access depends on data, write the row with Apex — do not blow a hole in the wall.
🧠A key with a reason: Every __Share row = one key, one holder, one reason. Manual sharing writes it by hand; Apex managed sharing writes it in code.
Concept
Every shareable object has a companion Share object — AccountShare, MyObj__Share. Its rows ARE the access grants. Each row carries a UserOrGroupId, an AccessLevel, and a RowCause.
• Manual sharing, through the Share button, writes rows with RowCause = Manual.
• Apex managed sharing writes rows from code.
• On CUSTOM objects you can define your own Apex Sharing Reasons and use them as the RowCause.
• Here is the difference that matters operationally.
• Rows with RowCause Manual are deleted when the record's ownership changes.
• Rows with a custom reason survive an ownership transfer. That is the whole point of defining a reason. This is the mechanism behind any 'share dynamically with the people named on the record' requirement.
• It connects to 3.5, because writing Share rows needs the right sharing context. And to Module 6. Because the recalculation logic usually lives in async Apex.
🧠360 Card — Manual & Apex Managed Sharing
There is no screen listing your Apex shares. You query the share object itself — for a custom object that is MyObject__Share — and filter on RowCause. That query is also how you prove to an auditor which grants exist and why.
Rule: when access is per record and data-driven, write the share row yourself.
Gain: a named grant with a reason. Custom Apex sharing reasons make the intent auditable, and those rows survive an owner change.
Price: you now own the lifecycle. Every add, change and revoke is your code’s job.
Limits: custom Apex sharing reasons exist for custom objects only. On a standard object a programmatic share must use RowCause Manual, and an ownership transfer deletes it silently. Writing share rows needs system context.
Mirror — manual sharing by the user: no code at all. But it is one person clicking, and nothing keeps it correct.
Later: this is the escape hatch for everything sharing rules cannot express. Reach for it only after 3.2 runs out.
At volume: share rows are real rows. Millions of them slow recalculation. Grant to groups, not to each user.
⚠ INTERVIEW TRAP: An Apex share on a STANDARD object must use RowCause Manual. Transfer the record owner and that share is deleted silently. Custom objects keep custom reasons.
Core Q&A
Q: Design record access for 'each record must be visible to the specific reviewers selected on that record'. Why is this Apex managed sharing, and what are the moving parts?
🎯 Say this first: Per-record chosen reviewers: write rows to the __Share table in code. Use a custom row cause. Maintain the rows with a trigger on the reviewer field.
A: The grant is per-record and data-driven, so neither the hierarchy nor sharing rules can express it.
• Here is the implementation. On insert or update of the reviewer lookups, an after-save trigger computes the MyObj__Share rows you want.
• UserOrGroupId is the reviewer. AccessLevel is Read.
• RowCause is a custom Apex Sharing Reason — call it Reviewer__c.
• The trigger inserts the new grants and deletes the stale ones.
• Compute the delta. Common mistake: wiping and rewriting the shares, which churns the share table for nothing.
• Using a custom RowCause means the grants survive an ownership change, and the Share table documents itself.
• Bulkify it: one query for the existing shares across every record in the trigger context, a set-based diff, and a single Data Manipulation Language (DML) each way.
// 1. Build a share row by hand. This is how Apex grants access to one record.
MyObj__Share s = new MyObj__Share(
ParentId = rec.Id,
UserOrGroupId = rec.Reviewer__c,
AccessLevel = 'Read',
// 2. Say WHY the access was granted. A custom reason survives an owner change.
RowCause = Schema.MyObj__Share.RowCause.Reviewer__c);
// 3. Write the share. This needs system context, so the class runs without sharing.
insert s; // requires ability to write shares (system context)
Follow-ups (scenario-based)
Q1: Why can't you use a custom RowCause on AccountShare, and what changes about your design on standard objects?
A1: Custom Apex Sharing Reasons exist only for custom objects.
• On a standard object, a programmatic share must use RowCause = Manual. And an ownership transfer then deletes your grants, silently. So on standard objects the design has to include a recalculation path.
• Usually that is an async job — a Queueable or a Batch — fired on owner change, which rebuilds the programmatic shares.
• Make the rebuild safe to re-run, so you can also run it after a data migration.
• Interviewers use this to check whether you have actually shipped managed sharing, or only read about it.
Q2: In an approval process, pending approvers need access to records they may otherwise not see. How do you grant it?
A2: When the engine creates an approval step, it writes Share rows granting Read — or Edit, per config — to that step's approvers, under a dedicated RowCause.
• When the step completes, it revokes them.
• Access tracks the state of the process exactly, and nobody carries a standing over-grant.
• The rule configuration is data, held in custom metadata or custom objects. So the sharing behavior is itself configurable per process. That keeps the engine's no-code promise intact.
• One edge case worth volunteering. A step whose approver is a queue shares to the queue's group ID, rather than iterating over its members. That keeps the share rows proportional to the number of steps, not the number of users..png)