Skip to main content

Latest Post

Agentforce: Agent Access vs Data Access

💬 In plain words:   A user needs two separate grants before an agent is useful to them. One lets them use the agent at all. The other lets the agent reach data. They fail in completely different ways. 📌 Example:   A Meridian rep is granted every object the agent touches, but not Agent Access. The agent simply does not appear for them. Nothing is wrong with the data model. 🎬 Real-Life Example: The Agent Nobody Could Find     The Old/Bad Way:   An admin built an agent, activated it, tested it in preview, and told the team it was live. Nobody could see it — including the admin, outside preview.   Why this fails:   Agent Access is a separate grant inside a permission set. Building an agent does not grant you the right to use it. Preview bypasses this, which is exactly why the gap goes unnoticed.     The New/Good Way:   1. In the permission set, find the Agent Access section. 2. Add the specific agent to the enabled list. 3. As...

Salesforce Basics: Manual & Apex Managed Sharing (the Share Object)

💬 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.

Salesforce Basics Manual & Apex Managed Sharing (the Share Object)

🧭 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.