apex-managed-sharing

Sharing records programmatically via Apex: Share objects, row cause, sharing recalculation, with/without sharing patterns. NOT for declarative sharing rules (use sharing-and-visibility).

Best use case

apex-managed-sharing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Sharing records programmatically via Apex: Share objects, row cause, sharing recalculation, with/without sharing patterns. NOT for declarative sharing rules (use sharing-and-visibility).

Teams using apex-managed-sharing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-managed-sharing/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-managed-sharing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-managed-sharing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-managed-sharing Compares

Feature / Agentapex-managed-sharingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Sharing records programmatically via Apex: Share objects, row cause, sharing recalculation, with/without sharing patterns. NOT for declarative sharing rules (use sharing-and-visibility).

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Apex Managed Sharing

This skill activates when a practitioner needs to grant or revoke record-level access programmatically using Apex — by directly inserting or deleting rows in a Share object — rather than through declarative sharing rules, manual sharing, or role hierarchy. Use it for territory-based sharing, dynamic cross-role access, or any scenario where the sharing grant logic belongs in code, not setup.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm the object's OWD (Org-Wide Default). Apex managed sharing only extends access beyond what OWD already grants. If OWD is Public Read/Write, programmatic sharing has no practical effect.
- Identify whether a custom Apex sharing reason is required (required for custom objects when you want the grant to survive manual sharing recalculation or when you need auditable row causes beyond `Manual`).
- Confirm that the Apex sharing reason has been created in Setup (Object Manager > Object > Apex Sharing Reasons) before trying to insert rows that reference it. Referencing a non-existent reason causes a runtime DML exception.
- Know the governor limits in play: 10,000 DML rows per synchronous transaction, 10,000 DML rows per batch execute method. Sharing recalculation frequently requires batch Apex.

---

## Core Concepts

### Share Object Anatomy

Every Salesforce object that supports sharing has a corresponding Share object. For standard objects the name is `<ObjectName>Share` (e.g., `AccountShare`, `OpportunityShare`). For custom objects the name is `<ObjectAPIName>__Share` (e.g., `Territory_Assignment__Share`).

Every Share record has four core fields:

| Field | Type | Description |
|---|---|---|
| `ParentId` | ID | The ID of the record being shared |
| `UserOrGroupId` | ID | The ID of the User, Role, or Group being granted access |
| `AccessLevel` | String | `Read`, `Edit`, or `All` (All = owner-level; rarely granted via Apex) |
| `RowCause` | String | Why the share was granted. `Manual` is the platform default. Custom reasons use the `__c` suffix. |

For custom objects there is also a `RowCause` value of `Owner` (auto-created by the platform) and any Apex sharing reasons you have defined.

### Apex Sharing Reasons (Custom Row Causes)

An Apex sharing reason is a metadata record created in Setup under Object Manager > [Custom Object] > Apex Sharing Reasons. Once created, its API name is available as a string constant on the Share object: `<ObjectAPIName>__Share.rowCause.<ReasonName>__c`.

Using a custom row cause instead of `Manual`:

- The platform preserves the rows during manual sharing recalculation (clicking Recalculate on the sharing reason).
- The rows are still cleared when the object's OWD changes or when a full sharing recalculation is triggered at the org level.
- The reason name becomes auditable — queries on the Share table can filter by `RowCause` to isolate Apex-managed grants.

Standard objects (Account, Opportunity, etc.) do not support custom Apex sharing reasons. For standard objects, use `RowCause = 'Manual'` (constant `Schema.OpportunityShare.rowCause.Manual`).

### with sharing / without sharing / inherited sharing

Apex class sharing keywords control whether the running user's record access is enforced for SOQL and DML inside that class:

- `with sharing` — enforces the user's sharing rules. Use for most user-facing code.
- `without sharing` — bypasses sharing rules. Required in sharing recalculation batch classes because the class must query every record regardless of the running user's access.
- `inherited sharing` — adopts the sharing mode of the calling context. Use in service/utility classes that may be called from both `with sharing` and `without sharing` contexts.

A critical point: inserting Share records does **not** require `without sharing`. Any Apex code that has access to the parent record's ID can insert a share row. The `without sharing` requirement applies specifically to the **query** step inside recalculation — to read all records being reshared.

### Sharing Recalculation

When programmatic sharing logic changes (e.g., territory assignments are restructured), stale share rows that were created with a custom row cause must be deleted and recreated. The platform provides a recalculation hook:

1. Create a class that implements `Database.Batchable<SObject>`.
2. Annotate it with `global class` and implement `start`, `execute`, and `finish`.
3. Declare the class `without sharing` so it can query all records.
4. In `execute`: delete existing share rows for the row cause, then insert fresh ones.
5. Register the class on the Apex Sharing Reason in Setup. This makes a Recalculate button appear on the sharing reason and allows the platform to call the class automatically when triggered.

The `start` method typically returns a `Database.QueryLocator` over the parent records; `execute` processes each batch of parent records, computes the correct users, and performs the share DML.

---

## Common Patterns

### Pattern 1: Insert Share Records from a Trigger

**When to use:** A trigger event (insert or update of a related record) determines which users gain access to a parent record. Example: an Account is shared with a partner when a Partner_Relationship__c record is created.

**How it works:**

1. Collect the `ParentId` values from the triggering records.
2. Query the Share table to determine which grants already exist (avoids duplicates).
3. Build a list of new Share sObjects for rows that do not yet exist.
4. Insert with `Database.insert(shareList, false)` to allow partial success (duplicate rows produce a `DUPLICATE_VALUE` error; using `false` lets other rows succeed).
5. For removals, query the Share table by `RowCause` and `ParentId`, then delete.

**Why not the alternative:** Using declarative Sharing Rules would require a static criteria field. When sharing logic depends on related-object data (junction records, custom metadata), declarative rules cannot model the relationship.

### Pattern 2: Full Sharing Recalculation via Batch Apex

**When to use:** The underlying sharing logic has changed at bulk (e.g., territory reassignment affecting thousands of records) and all existing Apex-managed share rows must be rebuilt.

**How it works:**

1. Implement `Database.Batchable<SObject>` declared `without sharing`.
2. In `start`, return a `Database.QueryLocator` for all parent records: `Database.getQueryLocator('SELECT Id FROM CustomObject__c')`.
3. In `execute`, for the batch scope:
   a. Collect the parent IDs.
   b. Delete all existing `CustomObject__Share` rows where `RowCause = CustomObject__Share.rowCause.TerritoryAccess__c` and `ParentId IN :parentIds`.
   c. Compute the correct users by querying the related assignment records.
   d. Insert new share rows.
4. Register the class as the recalculation class on the Apex Sharing Reason in Setup.

**Why not the alternative:** Running recalculation in a trigger context hits DML row limits immediately for large datasets. A batch job stays within per-execute limits and can run asynchronously.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Need to share a record with a specific user based on a junction record | Apex trigger inserts `__Share` row with custom row cause | Declarative sharing rules cannot model junction-record logic |
| Sharing logic has changed org-wide and all shares need rebuilding | Batch Apex recalculation class registered on sharing reason | Volume exceeds synchronous DML limits; batch stays within per-execute limits |
| Standard object (Account, Opportunity) needs programmatic sharing | Insert share row with `RowCause = 'Manual'` | Standard objects do not support custom Apex sharing reasons |
| A service method must read-then-share records in a mixed calling context | Declare service class `inherited sharing`; sharing class `without sharing` | Preserves caller's context for queries; bypasses for share DML only where needed |
| Share records must survive a manual recalculation run | Use a custom Apex sharing reason (not `Manual`) | `Manual` rows are cleared by manual sharing recalculation; custom row causes are preserved |
| Audit which records a user can access via Apex sharing | Query `__Share` filtered by `RowCause` | Row cause makes grants distinguishable from role-hierarchy or declarative grants |

---


## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner activating this skill:

1. Gather context — confirm the org edition, relevant objects, and current configuration state
2. Review official sources — check the references in this skill's well-architected.md before making changes
3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
4. Validate — run the skill's checker script and verify against the Review Checklist below
5. Document — record any deviations from standard patterns and update the template if needed

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] OWD for the object is Private or Public Read Only — if OWD is Public Read/Write, Apex sharing has no effect
- [ ] Apex sharing reason is created in Setup before code that references it is deployed
- [ ] Share inserts use `Database.insert(list, false)` to handle `DUPLICATE_VALUE` gracefully
- [ ] Recalculation class is declared `without sharing` so all parent records are queryable
- [ ] Stale share rows are deleted before inserting fresh ones in recalculation (delete then insert, not upsert)
- [ ] Recalculation class is registered on the Apex sharing reason in Setup
- [ ] DML row count is within limits: 10,000 rows per synchronous transaction, 10,000 per batch execute

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **Apex managed share rows are cleared on OWD change or full recalculation** — When an administrator changes the OWD for an object or runs a full sharing recalculation from Setup, all Apex-managed share rows for that object are deleted. The recalculation class registered on the Apex sharing reason is then called automatically to rebuild them. If no class is registered, the grants are permanently lost until manually re-triggered.

2. **`without sharing` is required in recalculation classes** — The recalculation class must query all parent records to rebuild shares. If the class runs `with sharing`, records the running user cannot see are silently excluded from the query scope, producing incomplete share coverage with no error thrown.

3. **Custom row causes must exist in Setup before deployment** — The `RowCause` value on a share row is validated at DML time against the Apex sharing reasons defined in Setup. Deploying code that references a custom row cause before the metadata record exists in the target org causes a `DmlException: FIELD_INTEGRITY_EXCEPTION` at runtime. Include the sharing reason in the deployment package or create it manually in Setup first.

4. **AccessLevel must be at least Read; `All` (owner-level) is only grantable via ownership change** — Valid values for `AccessLevel` via Apex are `Read` and `Edit`. `All` is reserved for the record owner and cannot be granted via a Share row insert. Attempting to insert a row with `AccessLevel = 'All'` throws a `DmlException`.

5. **Duplicate share rows cause `DUPLICATE_VALUE` errors** — The platform enforces a unique constraint on `(ParentId, UserOrGroupId, RowCause)`. Inserting a share row for a combination that already exists throws `DUPLICATE_VALUE`. Always query existing rows before inserting, or use `Database.insert(list, false)` and inspect `SaveResult` errors to handle duplicates gracefully.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Share record DML snippet | Apex code to insert or delete share rows for a given object |
| Sharing recalculation batch class | Full `Database.Batchable` implementation for rebuilding Apex-managed shares |
| Apex sharing reason setup guidance | Steps to create the sharing reason metadata in Setup before code deployment |
| `check_apex_managed_sharing.py` report | Static analysis of Apex classes for sharing patterns and common errors |

---

## Related Skills

- sharing-and-visibility — Use for declarative sharing rules, OWD decisions, role hierarchy design, and manual sharing; this skill handles programmatic extension of that foundation
- apex-security-patterns — Use alongside this skill when designing the `with sharing` / `without sharing` / `inherited sharing` class hierarchy for the full feature
- async-apex — Use when sharing recalculation volume requires batch or queueable patterns beyond what triggers can handle

Related Skills

dynamic-sharing-recalculation

8
from PranavNagrecha/AwesomeSalesforceSkills

Force or orchestrate sharing recalculation after bulk data loads, rule changes, or user/role reorgs so row access catches up with policy. NOT for designing new sharing rules — use sharing-selection tree.

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-runtime-context-and-sharing

8
from PranavNagrecha/AwesomeSalesforceSkills

Decide and audit the security boundary a Flow runs at — System Context With Sharing, System Context Without Sharing, or User Context — plus the per-element runInMode override and the implications for sharing rules, FLS, CRUD, and $User/$Profile/$Permission merge fields. NOT for Apex sharing keywords (see apex/with-without-sharing-and-context). NOT for record-access troubleshooting at the user level (see security/record-access-troubleshooting).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-apex-defined-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.

second-generation-managed-packages

8
from PranavNagrecha/AwesomeSalesforceSkills

2GP managed package development: creating managed packages with Dev Hub and Salesforce CLI, semantic versioning, patch versions, namespace linking, ISV AppExchange listing, and subscriber upgrade management. NOT for unlocked packages, unmanaged packages, or 1GP-only workflows.

managed-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or maintaining Salesforce first-generation managed packages (1GP) for ISV distribution — covers namespace registration, packaging org structure, PostInstall/UninstallHandler Apex interface, push upgrades, Flow version management, and subscriber org considerations. NOT for second-generation managed packages (2GP), unlocked packages, or AppExchange listing setup.

sharing-recalculation-performance

8
from PranavNagrecha/AwesomeSalesforceSkills

Plan, batch, and monitor Salesforce sharing recalculation jobs — including OWD changes, sharing rule add/remove, role hierarchy restructuring, and Apex managed share rebuild — to avoid multi-hour background jobs and data-access blackouts. NOT for diagnosing data-skew root causes (use admin/data-skew-and-sharing-performance), NOT for designing the sharing model itself (use admin/sharing-and-visibility), and NOT for Apex managed sharing row-cause creation (use apex/apex-managed-sharing).

external-user-data-sharing

8
from PranavNagrecha/AwesomeSalesforceSkills

Configure record visibility for external users (Customer Community, Customer Community Plus, Partner Community) using External OWDs, Sharing Sets, and external sharing rules. Trigger keywords: sharing data with external users, portal user record visibility, Experience Cloud sharing model, sharing set configuration, external OWD setup, Customer Community data access, High-Volume Portal sharing. NOT for internal sharing model configuration. NOT for internal user roles and hierarchies. NOT for guest user profile hardening.

scheduled-apex-failure-detection-and-monitoring

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).