__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.
OpportunityShare with RowCause = Manual. Your "reviewer field" automation writing the same kind of row from a trigger is Apex managed sharing.
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, and regulators may too.
The New/Good Way:
- Keep OWD Private.
- A trigger writes one
Delivery__Sharerow:ParentId= the delivery,UserOrGroupId= the backup,AccessLevel= Read,RowCause= your custom sharing reason. - 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.
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 = Manualare 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 your trigger design, because writing Share rows needs the right sharing context, and calculation logic usually lives in async Apex.
There is no standard setup 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 relies on one person clicking, and nothing keeps it correct automatically.
Later: This is the escape hatch for everything standard sharing rules cannot express.
At volume: Share rows are real database rows. Millions of them slow down recalculation. Grant to groups where possible, not to individual users.
RowCause = Manual. Transfer the record owner and that share is deleted silently. Custom objects keep custom reasons!
Core Q&A
A: The grant is per-record and data-driven, so neither the role hierarchy nor sharing rules can express it.
- Implementation: On insert or update of the reviewer lookups, an after-save trigger computes the
MyObj__Sharerows you want. UserOrGroupIdis the reviewer.AccessLevelis Read.RowCauseis a custom Apex Sharing Reason — call itReviewer__c.- The trigger inserts the new grants and deletes the stale ones.
- Compute the delta: Common mistake: wiping and rewriting all shares, which churns the share table unnecessarily.
- Using a custom
RowCausemeans 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) statement each way.
Follow-ups (Scenario-Based)
A1: Custom Apex Sharing Reasons exist only for custom objects.
- On a standard object, a programmatic share must use
RowCause = Manual. An ownership transfer then deletes your grants silently. On standard objects, the design must include an explicit recalculation path. - Usually that is an async job — a Queueable or a Batch Apex job — fired on owner change, which rebuilds the programmatic shares.
- Make the rebuild safe to re-run, so you can also trigger it after a bulk data migration.
- Architect Note: Interviewers use this question to check whether you have actually shipped managed sharing in production, or only read about it.
A2: When the approval engine creates an approval step, it writes Share rows granting Read (or Edit, per configuration) to that step's approvers under a dedicated system RowCause.
- When the approval step completes, the system automatically revokes them.
- Access tracks the state of the process exactly, ensuring nobody carries a standing over-grant.
- The process rule configuration is held in system metadata, keeping the sharing behavior fully automated and aligned with process state.
- Edge case worth volunteering: A step whose approver is a Queue shares to the Queue's Group ID rather than iterating over individual members. That keeps the Share row count proportional to the number of steps, not user headcount.