Salesforce Sharing Sets and Portal Sharing

Overview

This document covers Experience Cloud (Community) sharing patterns, Sharing Sets, field-level sharing considerations, performance optimization, best practices, common patterns, troubleshooting, and code examples.

Related Patterns:

Prerequisites

Experience Cloud (Community) Sharing

Sharing Sets

What Sharing Sets Are

Sharing Sets are Experience Cloud (Community) mechanisms for enforcing data visibility rules per user type. They replace traditional sharing rules for portal/community users, as Customer Community licenses do not support sharing rules.

How Sharing Sets Work

Sharing Sets grant community users access to records based on:

Configuration:

Configuration Patterns

Pattern 1: Users See Only Their Own Records

Sharing Set: Customer Self-Service
- Profile: Customer Community User
- Object: Case
- Rule: Cases where Contact equals User's Contact
- Access Level: Read/Write

Pattern 2: Users See Records for Their Account

Sharing Set: Partner Portal Access
- Profile: Partner Community User
- Object: Opportunity
- Rule: Opportunities where Account equals User's Account
- Access Level: Read Only

Pattern 3: Users See Records They Own

Sharing Set: Community User Owned Records
- Profile: Customer Community User
- Object: Custom_Project__c
- Rule: Projects where Owner equals User
- Access Level: Read/Write

Sharing Set Rules

Use Cases for Portal/Community Users

Limitations and Considerations

Community Sharing Patterns

Users See Only Their Own Records

Pattern: Community users see only records where they are the contact or owner.

Implementation:

Use Cases:

Users See Records for Their Associated Account

Pattern: Community users see records associated with their account.

Implementation:

Use Cases:

Users See Records Based on Custom Relationships

Pattern: Community users see records based on custom lookup relationships.

Implementation:

Use Cases:

Multi-Tenant Portal Patterns

Pattern: Different user types in the same portal see different record sets.

Implementation:

Use Cases:

Related Patterns: See rag/architecture/portal-architecture.md for portal architecture patterns and rag/data-modeling/case-management-data-model.md for multi-tenant data isolation patterns.

Community vs. Internal Sharing

Differences in Sharing Behavior

Portal User Restrictions

Best Practices for Community Sharing

Field-Level Sharing Considerations

How FLS Interacts with Sharing

Field-Level Security (FLS) and sharing are evaluated independently:

  1. Sharing: Determines if user can see the record
  2. FLS: Determines if user can see specific fields on the record

Key Principle: A user must have both record-level access (sharing) and field-level access (FLS) to see a field value.

Field-Level Security vs. Record-Level Sharing

Example: A user may have access to a Case record (through sharing) but not see the SSN field (restricted by FLS).

Sharing Rules Don’t Override FLS

Sharing rules grant record-level access but do not override field-level security. If a field is restricted by FLS, sharing rules cannot grant access to that field.

Exception: View All Fields and Modify All Fields permissions bypass FLS.

Best Practices

Sharing Calculation and Performance

How Sharing is Calculated

Salesforce calculates sharing in real-time when users access records. The sharing calculation:

  1. Evaluates OWD settings
  2. Checks role hierarchy
  3. Evaluates sharing rules (owner-based, criteria-based, territory-based)
  4. Checks manual shares
  5. Evaluates Apex managed shares
  6. Checks View All / Modify All permissions
  7. Checks View All Data / Modify All Data permissions

Result: User has access if any mechanism grants access.

Sharing Recalculation

Sharing is recalculated when:

Automatic Recalculation: Salesforce automatically recalculates sharing when relevant changes occur.

Performance Considerations

Large Data Volume (LDV) Sharing Patterns

For orgs with large data volumes:

Optimization Strategies

Decision Frameworks

When to Use Each Sharing Mechanism

Decision Tree for Selecting Sharing Approach

  1. Start with OWD: Set OWD to most restrictive setting that meets requirements
  2. Role Hierarchy: Use role hierarchy if organizational structure matches access needs
  3. Sharing Rules: Use sharing rules for cross-functional or criteria-based access
  4. Manual Sharing: Use manual sharing only for exceptions
  5. Apex Managed Sharing: Use Apex managed sharing for complex, dynamic requirements
  6. View All / Modify All: Use sparingly for administrative or reporting needs

OWD Selection Framework

Choose Private When:

Choose Public Read Only When:

Choose Public Read/Write When:

Sharing Rule vs. Role Hierarchy Decision

Use Role Hierarchy When:

Use Sharing Rules When:

Apex Managed Sharing vs. Declarative Sharing

Use Declarative Sharing (Sharing Rules) When:

Use Apex Managed Sharing When:

Community Sharing Decision Framework

When to Use Sharing Sets

Use Sharing Sets When:

When to Use Apex Managed Sharing for Communities

Use Apex Managed Sharing When:

Portal Sharing Patterns Selection

Users See Only Their Own Records:

Users See Records for Their Account:

Users See Records Based on Custom Relationships:

Multi-Tenant Portals:

Best Practices

Security Best Practices

Performance Best Practices

Maintenance and Governance

Documentation Requirements

Testing Sharing Rules

Troubleshooting Sharing Issues

Common Patterns

Multi-Tenant Data Isolation

Record Type-Based Separation

Pattern: Use Record Types to distinguish between different tenant data, then apply Record Type-specific sharing.

Implementation:

Use Cases:

Related Patterns: See rag/data-modeling/case-management-data-model.md for multi-tenant data isolation patterns.

Sharing Set Patterns for Portals

Pattern: Use Sharing Sets to ensure portal users see only their tenant’s records.

Implementation:

Use Cases:

Cross-Tenant Access Prevention

Pattern: Ensure users cannot access records from other tenants.

Implementation:

Use Cases:

Hierarchical Access Patterns

Manager Sees Subordinate Records

Pattern: Managers can see all records owned by their subordinates.

Implementation:

Use Cases:

Territory-Based Access

Pattern: Users see records assigned to their territory.

Implementation:

Use Cases:

Account-Based Access

Pattern: Users see records associated with their account.

Implementation:

Use Cases:

Collaborative Access Patterns

Team-Based Sharing

Pattern: Team members share records with each other.

Implementation:

Use Cases:

Project-Based Sharing

Pattern: Users see records associated with projects they’re assigned to.

Implementation:

Use Cases:

Cross-Functional Access

Pattern: Users from different departments see each other’s records.

Implementation:

Use Cases:

Troubleshooting

Common Sharing Issues

Users Cannot See Records They Should See

Possible Causes:

Solutions:

Users Can See Records They Shouldn’t See

Possible Causes:

Solutions:

Sharing Rules Not Working

Possible Causes:

Solutions:

How to Debug Sharing Problems

Use Sharing Reasons

Salesforce provides sharing reasons that explain why a user has access to a record:

// Query sharing reasons for a record
List<CaseShare> shares = [
    SELECT Id, UserOrGroupId, CaseAccessLevel, RowCause
    FROM CaseShare
    WHERE CaseId = :caseId
];

// RowCause values:
// - 'Owner' - User owns the record
// - 'Manual' - Manually shared
// - 'Rule' - Sharing rule
// - 'Territory' - Territory sharing
// - Custom Apex Sharing Reason API names

Check Sharing Settings

Use Setup → Sharing Settings to review:

SOQL Queries for Sharing Analysis

Query Shares for a Record:

SELECT Id, UserOrGroupId, CaseAccessLevel, RowCause, UserOrGroup.Name
FROM CaseShare
WHERE CaseId = '001XX000004XXXXXXX'

Query Shares for a User:

SELECT Id, CaseId, CaseAccessLevel, RowCause, Case.Subject
FROM CaseShare
WHERE UserOrGroupId = '005XX000004XXXXXXX'

Query Manual Shares:

SELECT Id, CaseId, UserOrGroupId, CaseAccessLevel
FROM CaseShare
WHERE RowCause = 'Manual'

Query Sharing Rules:

SELECT Id, Name, SobjectType, AccessLevel, SharingRuleType
FROM SharingRules
WHERE SobjectType = 'Case'

Tools for Analyzing Sharing

Code Examples

Apex Managed Sharing Examples

Basic Share Creation

public with sharing class CaseSharingService {
    /**
     * Share a Case record with a user
     * @param caseId Case record ID
     * @param userId User ID to share with
     * @param accessLevel Read or Read/Write
     */
    public static void shareCaseWithUser(Id caseId, Id userId, String accessLevel) {
        CaseShare caseShare = new CaseShare();
        caseShare.CaseId = caseId;
        caseShare.UserOrGroupId = userId;
        caseShare.CaseAccessLevel = accessLevel;
        caseShare.RowCause = Schema.CaseShare.RowCause.Manual;
        
        try {
            insert caseShare;
        } catch (DmlException e) {
            if (e.getDmlType(0) == StatusCode.INVALID_FIELD) {
                // Share already exists or invalid
                System.debug('Share creation failed: ' + e.getMessage());
            }
            throw e;
        }
    }
}

Bulk Share Creation

public with sharing class CaseSharingService {
    /**
     * Share multiple Cases with multiple users
     * @param caseIdToUserIds Map of Case ID to Set of User IDs
     * @param accessLevel Read or Read/Write
     */
    public static void shareCasesWithUsers(
        Map<Id, Set<Id>> caseIdToUserIds, 
        String accessLevel
    ) {
        List<CaseShare> sharesToInsert = new List<CaseShare>();
        
        for (Id caseId : caseIdToUserIds.keySet()) {
            for (Id userId : caseIdToUserIds.get(caseId)) {
                CaseShare share = new CaseShare();
                share.CaseId = caseId;
                share.UserOrGroupId = userId;
                share.CaseAccessLevel = accessLevel;
                share.RowCause = Schema.CaseShare.RowCause.Manual;
                sharesToInsert.add(share);
            }
        }
        
        if (!sharesToInsert.isEmpty()) {
            Database.insert(sharesToInsert, false); // Allow partial success
        }
    }
}

Share with Custom Apex Sharing Reason

public with sharing class ProjectSharingService {
    /**
     * Share Project records with team members using custom sharing reason
     * @param projectId Project record ID
     * @param teamMemberIds Set of team member User IDs
     */
    public static void shareProjectWithTeam(Id projectId, Set<Id> teamMemberIds) {
        List<Custom_Project__Share> sharesToInsert = new List<Custom_Project__Share>();
        
        for (Id userId : teamMemberIds) {
            Custom_Project__Share share = new Custom_Project__Share();
            share.ParentId = projectId;
            share.UserOrGroupId = userId;
            share.AccessLevel = 'Read';
            share.RowCause = Schema.Custom_Project__Share.RowCause.Project_Team_Member__c;
            sharesToInsert.add(share);
        }
        
        if (!sharesToInsert.isEmpty()) {
            Database.insert(sharesToInsert, false);
        }
    }
}

Update Existing Shares

public with sharing class CaseSharingService {
    /**
     * Update access level for existing shares
     * @param caseId Case record ID
     * @param userId User ID
     * @param newAccessLevel New access level (Read or Read/Write)
     */
    public static void updateCaseShareAccess(
        Id caseId, 
        Id userId, 
        String newAccessLevel
    ) {
        List<CaseShare> existingShares = [
            SELECT Id, CaseAccessLevel 
            FROM CaseShare 
            WHERE CaseId = :caseId 
            AND UserOrGroupId = :userId 
            AND RowCause = 'Manual'
        ];
        
        if (!existingShares.isEmpty()) {
            for (CaseShare share : existingShares) {
                share.CaseAccessLevel = newAccessLevel;
            }
            update existingShares;
        }
    }
}

Delete Shares

public with sharing class CaseSharingService {
    /**
     * Remove shares for a Case
     * @param caseId Case record ID
     * @param userIdsToRemove Set of User IDs to remove access for
     */
    public static void removeCaseShares(Id caseId, Set<Id> userIdsToRemove) {
        List<CaseShare> sharesToDelete = [
            SELECT Id 
            FROM CaseShare 
            WHERE CaseId = :caseId 
            AND RowCause = 'Manual'
            AND UserOrGroupId IN :userIdsToRemove
        ];
        
        if (!sharesToDelete.isEmpty()) {
            delete sharesToDelete;
        }
    }
}

Sharing Calculation Queries

Query Shares for Analysis

public with sharing class SharingAnalysisService {
    /**
     * Get all shares for a record with details
     * @param recordId Record ID
     * @return List of Share records with user/group names
     */
    public static List<CaseShare> getRecordShares(Id recordId) {
        return [
            SELECT Id, UserOrGroupId, CaseAccessLevel, RowCause,
                   UserOrGroup.Name, UserOrGroup.Type
            FROM CaseShare
            WHERE CaseId = :recordId
            ORDER BY RowCause, UserOrGroup.Name
        ];
    }
    
    /**
     * Get all records shared with a user
     * @param userId User ID
     * @return List of Share records with record details
     */
    public static List<CaseShare> getUserShares(Id userId) {
        return [
            SELECT Id, CaseId, CaseAccessLevel, RowCause,
                   Case.Subject, Case.Status
            FROM CaseShare
            WHERE UserOrGroupId = :userId
            ORDER BY Case.Subject
        ];
    }
}

Sharing Rule Testing Patterns

Test Sharing Rule with Different Users

@isTest
private class CaseSharingRuleTest {
    @isTest
    static void testSharingRuleGrantsAccess() {
        // Create test users with different roles
        User managerUser = TestDataFactory.createUser('Manager');
        User salesUser = TestDataFactory.createUser('Sales Rep');
        
        // Create test case owned by sales user
        Case testCase = TestDataFactory.createCase();
        testCase.OwnerId = salesUser.Id;
        update testCase;
        
        Test.startTest();
        // Run as manager user
        System.runAs(managerUser) {
            // Query case - should have access via role hierarchy
            List<Case> accessibleCases = [
                SELECT Id, Subject 
                FROM Case 
                WHERE Id = :testCase.Id
            ];
            
            System.assertEquals(1, accessibleCases.size(), 
                'Manager should have access via role hierarchy');
        }
        Test.stopTest();
    }
}

Community Sharing Implementation Examples

Apex Managed Sharing for Community Users

public with sharing class CommunityCaseSharingService {
    /**
     * Share Case with community user based on contact relationship
     * @param caseRecord Case record
     */
    public static void shareCaseWithCommunityUser(Case caseRecord) {
        if (caseRecord.ContactId == null) {
            return;
        }
        
        // Find community user associated with contact
        List<User> communityUsers = [
            SELECT Id 
            FROM User 
            WHERE ContactId = :caseRecord.ContactId
            AND Profile.UserLicense.Name LIKE '%Community%'
            LIMIT 1
        ];
        
        if (!communityUsers.isEmpty()) {
            CaseShare share = new CaseShare();
            share.CaseId = caseRecord.Id;
            share.UserOrGroupId = communityUsers[0].Id;
            share.CaseAccessLevel = 'Read/Write';
            share.RowCause = Schema.CaseShare.RowCause.Manual;
            
            try {
                insert share;
            } catch (DmlException e) {
                // Handle error (e.g., share already exists)
                System.debug('Share creation failed: ' + e.getMessage());
            }
        }
    }
}

Q&A

Q: What are Sharing Sets and when do I use them?

A: Sharing Sets are Experience Cloud (Community) mechanisms for enforcing data visibility rules per user type. They replace traditional sharing rules for portal/community users, as Customer Community licenses do not support sharing rules. Use Sharing Sets when community users need record access based on user, account, or owner relationships.

Q: What is the difference between Sharing Sets and Sharing Rules?

A: Sharing Rules are for internal users and support owner-based, criteria-based, and territory-based sharing. Sharing Sets are for Experience Cloud (Community) users and support user-based, account-based, and owner-based sharing. Customer Community licenses do not support sharing rules, so Sharing Sets are required for community user access.

Q: How do I implement multi-tenant data isolation in Experience Cloud?

A: Use multiple Sharing Sets for different profiles, combine with Record Type-based separation, and use Apex managed sharing for complex patterns. Ensure Sharing Sets only grant access to the user’s tenant records. Use restrictive OWD (Private) and validation rules to prevent cross-tenant data access.

Q: How does Field-Level Security (FLS) interact with sharing?

A: FLS and sharing are evaluated independently. Sharing determines if a user can see the record; FLS determines if the user can see specific fields on the record. A user must have both record-level access (sharing) and field-level access (FLS) to see a field value. Sharing rules do not override FLS.

Q: What are the performance considerations for sharing?

A: Limit the number of sharing rules per object (recommended: < 50 per object). Use selective, indexed criteria for criteria-based sharing rules. Use public groups to simplify sharing rule management. Limit role hierarchy depth. Monitor sharing calculation performance. Consider View All for reporting users instead of many sharing rules.

Q: How do I troubleshoot sharing access issues?

A: Use sharing reasons to understand why users have access. Check OWD settings, verify role hierarchy structure, check sharing rule criteria and configuration, check for manual shares, verify View All/Modify All permissions, and use sharing debug tools and SOQL queries to analyze sharing.

Q: When should I use Apex managed sharing for Experience Cloud users?

A: Use Apex managed sharing for Experience Cloud users when sharing requirements are complex and not met by Sharing Sets, when sharing is based on custom relationships, when sharing changes dynamically based on record field values, or when sharing is based on external system data.

Q: How do I optimize sharing for large data volumes?

A: Use highly selective sharing rules with indexed criteria. Use public groups instead of individual user sharing. Consider Apex managed sharing for complex patterns. Optimize sharing rules for performance. Monitor sharing calculation performance. Consider View All/Modify All for reporting users instead of many sharing rules.

Edge Cases and Limitations

Edge Case 1: Sharing Sets with Large Contact/Account Hierarchies

Scenario: Sharing Sets processing large numbers of related Contacts or Accounts, causing performance issues.

Consideration:

Edge Case 2: Field-Level Sharing with Many Fields

Scenario: Objects with many fields requiring field-level sharing configuration, creating maintenance complexity.

Consideration:

Edge Case 3: Sharing Sets with Complex Relationship Traversal

Scenario: Sharing Sets requiring traversal of multiple relationship levels (e.g., Account → Contact → Case → Case Comments).

Consideration:

Edge Case 4: Multi-Tenant Portal Sharing

Scenario: Multiple Experience Cloud sites in same org requiring data isolation between sites.

Consideration:

Edge Case 5: Sharing Sets with High-Volume Data Changes

Scenario: High-volume data changes (e.g., bulk updates) triggering Sharing Set recalculations, causing performance issues.

Consideration:

Limitations

When to Use This Document