Prepare for Salesforce sharing and security interviews with practical, scenario-based questions that test both sharing concepts and problem-solving skills.
Question: A user should see only their own records, but managers should see team records. How do you design role hierarchy + OWD?
Answer: Use Role Hierarchy with OWD set to Private:
PrivateKey Points:
Question: You need to give access to a specific group for some records only. Do you use sharing rules or manual sharing?
Answer: Choose based on volume and pattern:
| Aspect | Sharing Rules | Manual Sharing |
|---|---|---|
| Use Case | Systematic, repeatable access (e.g., all Oppty for Region = "West" to West Sales group) | One-off, ad-hoc access (e.g., share specific deal with executive) |
| Automation | ✅ Automatic, rule-based | ❌ Manual action per record |
| Rule Limit | Max 300 rules per org | No limit (but slow at scale) |
| Criteria | Based on owner role, territory, or field value | User/Group/Role individually |
Best Practice: Use Sharing Rules for predictable patterns, Manual Sharing for exceptions.
Question: A record should be private, but certain users get access dynamically via logic. How do you implement Apex sharing?
Answer: Create sharing records programmatically:
// Apex: Share Opportunity with specific user if condition is met
public class OpportunityShareService {
public static void shareWithApprover(List<Opportunity> opportunities) {
List<OpportunityShare> shares = new List<OpportunityShare>();
for (Opportunity opp : opportunities) {
// Only share if amount > $100k and approver exists
if (opp.Amount > 100000 && opp.ApproverIds__c != null) {
List<Id> approverIds = opp.ApproverIds__c.split(';');
for (Id approverId : approverIds) {
OpportunityShare share = new OpportunityShare();
share.OpportunityId = opp.Id;
share.UserOrGroupId = approverId;
share.OpportunityAccessLevel = 'Edit'; // Read or Edit
shares.add(share);
}
}
}
if (!shares.isEmpty()) {
insert shares;
}
}
// Remove access when condition no longer met
public static void removeApproverShare(List<Opportunity> opportunities) {
List<Id> oppIds = new List<Id>(new Map<Id, Opportunity>(opportunities).keySet());
List<OpportunityShare> sharesToDelete = [
SELECT Id
FROM OpportunityShare
WHERE OpportunityId IN :oppIds
AND RowCause = 'Manual'
];
if (!sharesToDelete.isEmpty()) {
delete sharesToDelete;
}
}
}
// Trigger
trigger OpportunitySharingTrigger on Opportunity (after insert, after update) {
if (Trigger.isAfter) {
OpportunityShareService.shareWithApprover(Trigger.new);
OpportunityShareService.removeApproverShare(Trigger.new);
}
}
Key Points:
AccessLevel can be 'Read' only or 'Edit'RowCause indicates why record is shared (Manual, Rule, etc.)with sharing keyword to enforce FLSQuestion: Difference between Profile vs Permission Set vs Sharing in real scenario?
Answer: Three different layers of access control:
| Layer | What It Controls | Scope | Example |
|---|---|---|---|
| Profile | Object access, field access, system permissions | User-wide | Sales Profile can't access Finance Object |
| Permission Set | Extends profile permissions (add, never remove) | User-assigned | Add "Manage Quotes" to Sales Rep temporarily |
| Sharing | Record-level access control | Per-record | Sales Rep A can see Opp X but not Opp Y |
Real Scenario:
Sales Profile (base permissions)
├─ Can access Account, Opportunity objects
├─ Cannot access Finance object
└─ Cannot see phone field
+ Permission Set (temporary)
├─ Add "Approve Quotes" ability
└─ Keep all other restrictions
+ Sharing (record control)
├─ Sally can see Account 123
├─ Sally cannot see Account 456
└─ Both accounts visible to Manager due to role hierarchy
Question: How do you handle field-level security vs object-level vs record-level access?
Answer: Three layers of restriction:
| Level | Controls | Set By | Example |
|---|---|---|---|
| Object-Level | Can user access object at all? | Profile / Permission Set | Sales user cannot access Finance object |
| Field-Level | Can user see/edit specific fields? | Profile / Permission Set | Sales user can see Amount but not Cost__c |
| Record-Level | Can user access specific records? | OWD / Sharing Rules / Apex Sharing | Sales user can see their own Oppty, not competitor's |
Access Check Order (top to bottom):
1. Can user access the Object? (OWD)
2. Can user access the Field? (FLS in Profile/PermSet)
3. Can user see this Record? (Sharing Rules / Apex Sharing)
4. Can user Edit this Field? (FLS for Edit + Record Access)
If ANY layer says NO, access is DENIED.
Example:
Sales user:
✅ Can access Opportunity object (OWD = Private)
✅ Can see Amount field (FLS allows read)
❌ Cannot see this specific Opp (not owner, not shared, manager)
❌ Result: User cannot see record
---
Manager:
✅ Can access Opportunity object
✅ Can see Amount field
✅ Can see Opp because below Sales Rep in hierarchy
✅ Can edit Amount (FLS allows edit)
✅ Result: Manager can view and edit
Question: A user can see a record but not edit it. What could be the reasons?
Answer: Check in this order:
Question: When would you use Public Groups vs Roles?
Answer: Different purposes and behaviors:
| Aspect | Roles | Public Groups |
|---|---|---|
| Hierarchy | Hierarchical (tree structure) | Flat (just membership) |
| Inheritance | Parent sees child records | No inheritance |
| Use Case | Org structure, manager visibility | Cross-functional teams, skill-based groups |
| Example | CEO → Manager → Sales Rep | "Finance Approvers", "East Coast Team", "Data Analysts" |
| Sharing Rules | Can use "Based on Role" | Can't (must use "Based on Role" for hierarchy) |
Scenario: Share records to "Finance Team" (includes people from Sales, Ops, Legal)
Solution: Use Public Group "Finance Team" with members from different roles.
Sharing Rule: IF Approver_Group__c = 'Finance', share with PublicGroup.FinanceTeam
Question: A community user needs limited access to related records. How do you design external sharing model?
Answer: Use Account-based sharing:
// Community User sees:
// ✅ Their own User record
// ✅ Their Account (customer's account)
// ✅ Contacts related to that Account (via relationship)
// ✅ Cases they created or are related to Account
// ❌ Sensitive opportunity details
// Setup:
Setup → Sharing Settings:
- Account OWD: Controlled by Parent
- Contact OWD: Controlled by Parent (Account)
- Case OWD: Public Read/Write or Controlled by Parent
- Opportunity OWD: Private (not visible to community)
// Apex: Ensure portal user only sees their Account data
public with sharing class ContractController {
@AuraEnabled(cacheable=true)
public static List<Contract__c> getMyContracts() {
// with sharing automatically filters to user's accessible records
return [
SELECT Id, Name, Amount__c, Status__c
FROM Contract__c
WHERE Account__c IN (SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId())
];
}
}
Question: What happens when OWD is Public Read/Write but you still want restrictions?
Answer: Use Anti-Sharing Rules or custom logic:
// If OWD = Public Read/Write, everyone sees all records.
// To restrict: Use anti-sharing or custom validation.
// Option 1: Validation Rule (prevents edit, not view)
IF(
AND(
$Profile.Name != 'System Administrator',
Sensitivity__c = 'Confidential'
),
TRUE
)
Error: "Non-admins cannot edit sensitive records"
// Note: Users can still SEE sensitive records, just can't edit
// Option 2: Custom Logic in Apex (deny read/write)
public with sharing class AccountController {
@AuraEnabled
public static Account getAccount(Id accountId) {
Account acc = [SELECT Id, Name, SensitiveData__c FROM Account WHERE Id = :accountId];
// Deny access if sensitive and not admin
if (acc.Sensitivity__c = 'Confidential' && !isCurrentUserAdmin()) {
throw new AuraHandledException('Access Denied');
}
return acc;
}
}
// Option 3: Remove OWD = Public Read/Write, use Sharing Rules instead
// ✅ Better approach: Set OWD to Private/Read Only, grant access via Sharing Rules
Question: How do you troubleshoot "user can't see record" issue step by step?
Answer: Use this checklist:
STEP 1: Check Org-Wide Defaults (OWD)
├─ Go to Setup → Sharing Settings → [Object]
├─ Read: "The account level is set to Private"
├─ Q: Can user see ANY records of this type?
│ └─ If NO → Likely OWD or Field-Level Security issue
STEP 2: Check Record Sharing
├─ Open the record → sharing icon (bottom right)
├─ Q: Is user listed with 'Read' or 'Edit' access?
│ └─ If NO → Add via sharing rule or manual share
│ └─ If YES → Drill down to field level
STEP 3: Check Profile/Permission Set
├─ Setup → Users → User Details → [User]
├─ Check Profile → Object Permissions
├─ Q: Does profile have READ access to object?
│ └─ If NO → Add to profile or permission set
│ └─ If YES → Check field access next
STEP 4: Check Field-Level Security
├─ Profile → Field Permissions → [Object]
├─ Q: Is the FIELD (or key fields) readable?
│ └─ If NO → Enable field in Profile/PermSet
│ └─ If YES → Check record ownership/hierarchy
STEP 5: Check Role Hierarchy
├─ Is user owner of record? YES → can see
├─ Is user above owner in hierarchy? YES → can see
├─ Is record shared? Check sharing rules/manual share
├─ Is sharing rule matching? YES → can see
STEP 6: Check Apex Sharing
├─ Query ShareWith__c or custom sharing logic
├─ Is record dynamically shared? Check trigger logs
STEP 7: Last Resort – Check System
├─ Clear browser cache (Ctrl+Shift+Del)
├─ Login as different user and test
├─ Check org debug logs for AuraEnabled exceptions
└─ Run: SOSL search to see if record appears
Question: Sales reps should see only their records, managers should see team data. How do you design access?
Answer: Combine OWD + Role Hierarchy:
// Org Structure:
VP Sales
├─ Regional Manager - West
│ ├─ Sales Rep 1
│ └─ Sales Rep 2
└─ Regional Manager - East
└─ Sales Rep 3
// Configuration:
1. OWD for Opportunity = PRIVATE
2. Sales Rep owns their own Opportunities
3. Role Hierarchy grants manager visibility
// Results:
Sales Rep 1:
- Sees their own Oppty (as owner)
- Cannot see Oppty owned by Sales Rep 2
- Cannot see Regional Manager's Oppty
Regional Manager - West:
- Sees all Oppty owned by Sales Rep 1 & 2 (below in hierarchy)
- Sees their own Oppty (as owner)
- Cannot see Regional Manager - East's Oppty
VP Sales:
- Sees all Oppty (everyone below in hierarchy)
// Adding Apex Sharing for exceptions:
If an Oppty needs review by Finance team:
public class OpportunityShareService {
public static void shareWithFinanceForReview(List<Opportunity> opportunities) {
List<OpportunityShare> shares = new List<OpportunityShare>();
for (Opportunity opp : opportunities) {
if (opp.FinanceReview__c == true) {
OpportunityShare share = new OpportunityShare();
share.OpportunityId = opp.Id;
share.UserOrGroupId = PublicGroupId.Finance; // Public Group ID
share.OpportunityAccessLevel = 'Read';
shares.add(share);
}
}
insert shares;
}
}
Question: Finance needs read-only access to all Opportunities across regions. What approach will you use?
Answer: Use Sharing Rule with Public Group:
// Step 1: Create Public Group for Finance
Setup → Users → Public Groups → New
Name: Finance Team
Add members: All Finance users, Finance manager
// Step 2: Create Sharing Rule
Setup → Sharing Settings → Opportunity → New
Rule Name: Finance View All Oppty
Type: Opportunity
Share With: Public Group (Finance Team)
Access Level: Read
Criteria:
- All Opportunities (no filters)
OR
- RecordType = "Enterprise"
OR
- StageName IN ('Proposal', 'Negotiation')
// Result:
- Every Finance user can READ all Oppty
- Finance users cannot EDIT Oppty
- Finance users cannot delete Oppty
- Access applies globally across regions
// Apex: Verify access
public with sharing class FinanceReporting {
@AuraEnabled(cacheable=true)
public static List<Opportunity> getAllOpportunitiesForReporting() {
// with sharing respects OWD and sharing rules
return [
SELECT Id, Name, Amount, StageName, OwnerId, Account.Name
FROM Opportunity
ORDER BY Amount DESC
];
}
}
Question: You need to give temporary access of a record to a user. How would you handle it?
Answer: Use scheduled Apex or workflow to auto-revoke access:
import { LightningElement, api, track } from 'lwc';
import grantTemporaryAccess from '@salesforce/apex/SharingService.grantTemporaryAccess';
export default class TemporaryAccessComponent extends LightningElement {
@api recordId;
@track accessDays = 7;
@track selectedUser;
async handleGrantAccess() {
try {
await grantTemporaryAccess({
recordId: this.recordId,
userId: this.selectedUser,
days: this.accessDays
});
alert('Access granted until ' + new Date(Date.now() + this.accessDays * 24 * 60 * 60 * 1000));
} catch (error) {
alert('Error: ' + error.body.message);
}
}
}
// Apex: Grant and auto-revoke access
public class SharingService {
@AuraEnabled
public static void grantTemporaryAccess(Id recordId, Id userId, Integer days) {
// Insert share record
OpportunityShare share = new OpportunityShare();
share.OpportunityId = recordId;
share.UserOrGroupId = userId;
share.OpportunityAccessLevel = 'Edit';
share.RowCause = 'Manual';
insert share;
// Create scheduled action to remove access
scheduleAccessRevoke(recordId, userId, days);
}
private static void scheduleAccessRevoke(Id recordId, Id userId, Integer days) {
RevokeAccessJob job = new RevokeAccessJob(recordId, userId);
String cronExpression = getCronExpression(days);
System.schedule('Revoke_' + recordId, cronExpression, job);
}
}
// Scheduled Job: Revoke access
public class RevokeAccessJob implements Schedulable {
private Id recordId;
private Id userId;
public RevokeAccessJob(Id recordId, Id userId) {
this.recordId = recordId;
this.userId = userId;
}
public void execute(SchedulableContext ctx) {
List<OpportunityShare> shares = [
SELECT Id
FROM OpportunityShare
WHERE OpportunityId = :recordId
AND UserOrGroupId = :userId
AND RowCause = 'Manual'
];
if (!shares.isEmpty()) {
delete shares;
}
}
}
Question: A record should be shared dynamically based on a field value. What solution will you use?
Answer: Use Apex trigger with field-based logic:
// Scenario: Share Opportunity with Account team when Status = "Reviewing"
trigger OpportunityFieldBasedSharing on Opportunity (after insert, after update) {
if (Trigger.isAfter) {
OpportunityFieldSharingService.shareBasedOnStatus(Trigger.new, Trigger.oldMap);
}
}
public class OpportunityFieldSharingService {
public static void shareBasedOnStatus(List<Opportunity> newOppty, Map<Id, Opportunity> oldMap) {
List<OpportunityShare> sharesToAdd = new List<OpportunityShare>();
List<Id> opptyToUnshare = new List<Id>();
for (Opportunity opp : newOppty) {
Opportunity oldOpp = oldMap.get(opp.Id);
String newStatus = opp.StageName;
String oldStatus = oldOpp.StageName;
// If changed to "Reviewing", share with Account team
if (newStatus == 'Reviewing' && oldStatus != 'Reviewing') {
// Get account team members
List<AccountTeamMember> teamMembers = [
SELECT UserId FROM AccountTeamMember
WHERE AccountId = :opp.AccountId
];
for (AccountTeamMember member : teamMembers) {
OpportunityShare share = new OpportunityShare();
share.OpportunityId = opp.Id;
share.UserOrGroupId = member.UserId;
share.OpportunityAccessLevel = 'Read';
sharesToAdd.add(share);
}
}
// If changed from "Reviewing" to other status, revoke access
if (oldStatus == 'Reviewing' && newStatus != 'Reviewing') {
opptyToUnshare.add(opp.Id);
}
}
// Insert new shares
if (!sharesToAdd.isEmpty()) {
insert sharesToAdd;
}
// Remove old shares
if (!opptyToUnshare.isEmpty()) {
List<OpportunityShare> sharesToDelete = [
SELECT Id FROM OpportunityShare
WHERE OpportunityId IN :opptyToUnshare
AND RowCause = 'Manual'
];
delete sharesToDelete;
}
}
}
Question: Users can see records but cannot edit certain fields. How do you control this?
Answer: Combine Field-Level Security + Validation Rules + Lightning Components:
// Approach 1: Field-Level Security (simplest)
Setup → Profiles → [Profile] → Field Permissions
- Amount field: Read enabled, Edit disabled
- Result: User sees Amount but input field is read-only
// Approach 2: Validation Rule (more control)
IF(
AND(
NOT($Profile.Name = 'System Administrator'),
CHANGED(Renewal_Date__c),
Renewal_Date__c > TODAY()
),
TRUE
)
Error: "Non-admins cannot edit future renewal dates"
// Approach 3: Lightning Component (most control)
import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getFieldEditability from '@salesforce/apex/FieldSecurityService.getFieldEditability';
export default class RestrictedFieldForm extends LightningElement {
@api recordId;
@track fieldPermissions = {};
@wire(getFieldEditability, { recordId: '$recordId' })
wiredPermissions({ data, error }) {
if (data) {
this.fieldPermissions = data;
}
}
get canEditAmount() {
return this.fieldPermissions.Amount?.editable;
}
handleSave() {
// Validate field edits before sending to Apex
if (!this.canEditAmount) {
alert('You cannot edit Amount field');
return;
}
// Proceed with save
}
}
// Apex: Check FLS before allowing edit
public with sharing class FieldSecurityService {
@AuraEnabled
public static Map<String, FieldPermission> getFieldEditability(Id recordId) {
Map<String, FieldPermission> fieldAccess = new Map<String, FieldPermission>();
Schema.DescribeSobjectResult describe = recordId.getSObjectType().getDescribe();
Map<String, Schema.SObjectField> fields = describe.fields.getMap();
for (String fieldName : new List<String>{ 'Amount', 'RenewalDate', 'Description' }) {
Schema.SObjectField field = fields.get(fieldName.toLowerCase());
Schema.DescribeFieldResult fieldDesc = field.getDescribe();
FieldPermission perm = new FieldPermission();
perm.readable = fieldDesc.isAccessible();
perm.editable = fieldDesc.isUpdateable();
fieldAccess.put(fieldName, perm);
}
return fieldAccess;
}
public class FieldPermission {
@AuraEnabled public Boolean readable { get; set; }
@AuraEnabled public Boolean editable { get; set; }
}
}
Question: External (community) users should only see their own related records. How do you design it?
Answer: Use Account-based sharing + Apex with sharing:
// Community user setup:
1. Portal User linked to Account (customer account)
2. OWD settings for Customer Portal:
- Account: Controlled by Parent (Account)
- Contact: Controlled by Parent (Account)
- Case: Public Read/Write (so they can see all cases)
- Opportunity: Private
3. Portal User sees:
✅ Their Account (the company they work for)
✅ Contacts on their Account
✅ Cases on their Account
❌ Other companies' data
// Apex: Enforce access in code
public with sharing class CommunityDataService {
@AuraEnabled(cacheable=true)
public static List<Contact> getAccountContacts() {
// with sharing automatically filters to user's account
User portalUser = [SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId()];
return [
SELECT Id, Name, Email, Phone
FROM Contact
WHERE AccountId = :portalUser.AccountId
];
}
@AuraEnabled(cacheable=true)
public static List<Case> getMyAccountCases() {
User portalUser = [SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId()];
return [
SELECT Id, CaseNumber, Subject, Status
FROM Case
WHERE AccountId = :portalUser.AccountId
];
}
}
// LWC: Display community data
import { LightningElement, track, wire } from 'lwc';
import getAccountContacts from '@salesforce/apex/CommunityDataService.getAccountContacts';
export default class CommunityView extends LightningElement {
@track contacts = [];
@wire(getAccountContacts)
wiredContacts({ data, error }) {
if (data) {
this.contacts = data;
}
}
}
Question: A user cannot see a record they should have access to. How do you troubleshoot step by step?
Answer: Use this systematic troubleshooting approach:
// Step 1: Verify user can see records at all (OWD baseline)
SELECT Id, Name FROM Opportunity LIMIT 10
// If query returns records → OWD allows some visibility
// If query returns 0 records → Check OWD (likely Private) or FLS
// Step 2: Check if user owns the record
SELECT OwnerId FROM Opportunity WHERE Id = :recordId
IF (OwnerId = UserInfo.getUserId()) {
CONCLUSION: User is owner, should have full access
CHECK: Profile edit permissions
}
// Step 3: Check role hierarchy
User u = [SELECT UserRoleId FROM User WHERE Id = :UserInfo.getUserId()];
UserRole r = [SELECT Id, ParentRoleId FROM UserRole WHERE Id = :u.UserRoleId];
IF (record.OwnerId's role is below current user's role hierarchy) {
CONCLUSION: User should see record via role hierarchy
CHECK: OWD is not blocking it (set to Private at minimum)
}
// Step 4: Check if record is shared explicitly
List<OpportunityShare> shares = [
SELECT Id, AccessLevel
FROM OpportunityShare
WHERE OpportunityId = :recordId
AND (UserOrGroupId = :userId OR UserOrGroupId IN (public groups user belongs to))
];
IF (shares.size() > 0) {
CONCLUSION: Record is explicitly shared
IF (AccessLevel = 'Read') → User can READ but not EDIT
}
// Step 5: Check Field-Level Security
Schema.DescribeFieldResult fieldDesc = Opportunity.Amount.getDescribe();
IF (!fieldDesc.isAccessible()) {
PROBLEM: User cannot read Amount field
FIX: Enable field in Profile/PermSet
} ELSE IF (!fieldDesc.isUpdateable()) {
PROBLEM: User can read but cannot edit Amount
FIX: User has Read access to record but not edit to this field
}
// Step 6: Check for API filters
// Some record types may be hidden by validation rules or SOSL
SELECT COUNT() FROM Opportunity WHERE Id = :recordId
IF (count = 0) {
PROBLEM: Record doesn't match user's query filters
CHECK: Are you using with sharing clause?
FIX: Ensure Apex uses with sharing for correct filtering
}
// Step 7: Verify record actually exists and isn't deleted
SELECT Id FROM Opportunity WHERE Id = :recordId ALL ROWS
IF (IsDeleted = true) {
CONCLUSION: Record is in recycle bin
FIX: Restore from recycle bin if needed
}
// Debug Query to get complete picture:
List<Opportunity> opps = [
SELECT
Id, Name, OwnerId, Owner.Name, Amount,
(SELECT Id, UserOrGroupId, OpportunityAccessLevel FROM OpportunityShares)
FROM Opportunity
WHERE Id = :recordId
];
Question: You need to restrict access even when OWD is Public Read/Write. What can you do?
Answer: OWD = Public Read/Write gives global access; use alternatives:
// PROBLEM: If OWD = Public Read/Write, EVERYONE can see ALL records
// SOLUTION OPTIONS:
// Option 1: Change OWD to Private or Read Only ✅ BEST
Setup → Sharing Settings → Opportunity
Change: Public Read/Write → Private
Then grant access via Sharing Rules/Apex Sharing
// Option 2: Use Validation Rules (prevents EDIT, not VIEW)
IF(
AND(
$Profile.Name != 'System Administrator',
Confidentiality__c = 'Sensitive'
),
TRUE
)
Error: "Non-admins cannot edit sensitive records"
// Note: Users can still SEE sensitive records, just can't edit
// Option 3: Use Apex with custom restrictions
public with sharing class OpportunitySecurity {
@AuraEnabled(cacheable=true)
public static List<Opportunity> getAccessibleOpportunities() {
// Apex with sharing respects OWD and Sharing Rules
List<Opportunity> opps = [
SELECT Id, Name, Amount
FROM Opportunity
];
// Add custom filter to deny based on field
List<Opportunity> filtered = new List<Opportunity>();
for (Opportunity opp : opps) {
if (opp.Confidentiality__c != 'TopSecret' || isCurrentUserAdmin()) {
filtered.add(opp);
}
}
return filtered;
}
}
// Option 4: Use record-level access control in LWC
// Even if record is visible via SOQL, hide in LWC based on field
import { LightningElement, track } from 'lwc';
export default class OpportunitiesList extends LightningElement {
@track visibleOpportunities = [];
handleDataLoaded(event) {
const allOpps = event.detail;
// Filter in UI if Confidentiality = TopSecret
this.visibleOpportunities = allOpps.filter(
opp => opp.Confidentiality__c !== 'TopSecret'
);
}
}
// RECOMMENDATION:
// Change OWD from Public Read/Write to Private
// Grant access explicitly via Sharing Rules or Apex
// This is the proper security model
Question: Multiple teams need access to the same set of records. Do you use Roles or Public Groups? Why?
Answer: Depends on team structure:
| Scenario | Use Roles | Use Public Groups |
|---|---|---|
| Sales team hierarchy (VP → Manager → Rep) | ✅ YES – hierarchy matters | ❌ No – not hierarchical |
| Cross-functional teams (Sales + Finance + Legal) | ❌ No – not structural | ✅ YES – skill-based, not org hierarchy |
| Finance needs all Oppty (across all regions) | ❌ No – they're not in sales org | ✅ YES – create "Finance Approvers" group |
| Multiple account teams for same account | ❌ No – not structural | ✅ YES – "West Region Team", "East Region Team" |
Real Example:
Scenario: Three teams need access to "Enterprise Opportunities"
- Sales team (hierarchical)
- Finance review team (cross-functional)
- Legal review team (ad-hoc)
Solution:
1. Create Sharing Rule for "Sales" based on Roles
Share with role "Sales" + subordinates
2. Create Public Group "Finance Approvers"
Add: Finance VP, Finance Manager, Auditor role
3. Create Public Group "Legal Team"
Add: Legal counsel, in-house attorney, paralegal
4. Create two Sharing Rules:
Rule 1: IF Requires_Finance_Review__c = true
Share with Public Group "Finance Approvers"
Rule 2: IF Requires_Legal_Review__c = true
Share with Public Group "Legal Team"
Result:
- Sales sees records in their role
- Finance sees only records needing finance review
- Legal sees only records needing legal review
Question: You need to share records from a custom object based on complex logic. Would you use Sharing Rules or Apex Sharing? Why?
Answer: Choose based on complexity:
| Scenario | Sharing Rules | Apex Sharing |
|---|---|---|
| Share if field = "X" | ✅ Simple and fast | ❌ Overkill |
| Share if field = "X" AND role = "Y" | ✅ Good (use role-based rule) | ❌ Sharing rules handle this |
| Share based on lookup field value (Account region) | ❌ Can't do cross-object logic | ✅ YES – query parent record, evaluate |
| Share if related Contact matches criteria | ❌ No cross-object logic | ✅ YES – query related records |
| Complex: Share to users on same team as record owner | ❌ Can't query team membership | ✅ YES – use Apex for complex joins |
Example: Complex Sharing Rule
Requirement: Share Project record with all users in same Department as owner
// Sharing Rules can't do this (no cross-object logic)
// Solution: Use Apex
trigger ProjectSharingTrigger on Project__c (after insert, after update) {
ProjectSharingService.shareWithDepartment(Trigger.new);
}
public class ProjectSharingService {
public static void shareWithDepartment(List<Project__c> projects) {
// Get owner departments
Set<Id> ownerIds = new Set<Id>();
for (Project__c proj : projects) {
ownerIds.add(proj.OwnerId);
}
// Query owners and their department
Map<Id, String> ownerDepartments = new Map<Id, String>();
for (User u : [SELECT Id, Department FROM User WHERE Id IN :ownerIds]) {
ownerDepartments.put(u.Id, u.Department);
}
// Find all users in same departments
Set<String> departments = new Set<String>(ownerDepartments.values());
List<User> departmentUsers = [
SELECT Id FROM User
WHERE Department IN :departments
];
// Create shares
List<Project__Share> shares = new List<Project__Share>();
for (Project__c proj : projects) {
String ownerDept = ownerDepartments.get(proj.OwnerId);
for (User u : departmentUsers) {
if (u.Department == ownerDept) {
Project__Share share = new Project__Share();
share.ParentId = proj.Id;
share.UserOrGroupId = u.Id;
share.AccessLevel = 'Edit';
share.RowCause = 'Manual';
shares.add(share);
}
}
}
insert shares;
}
}
// DECISION RULE:
// If: Simple field-based rule → Use Sharing Rules ✅
// If: Complex, cross-object, queryable logic → Use Apex ✅
// If: Mix of both → Sharing Rules + Apex (don't duplicate logic)