Straight answer: work down the layers in order — org-wide default, role hierarchy, sharing rules, team membership, manual and Apex shares, implicit sharing, then profile-level View All. Stop at the first layer that grants access, because that is the one to change. Doing this in any other order is how people spend an afternoon deleting a sharing rule that was never the cause.
The order to check, and why it is this order
Record access is additive. Every mechanism grants. None takes away. So the question is never “what is letting them in” in the singular — it is “which of these is letting them in”, and you need all of them before you change anything.
| # | Layer | Where to look |
|---|---|---|
| 1 | Org-wide default | Setup → Sharing Settings |
| 2 | Ownership | The record’s Owner field |
| 3 | Role hierarchy | Setup → Roles, and the OWD’s “Grant Access Using Hierarchies” |
| 4 | Sharing rules | Setup → Sharing Settings, per object |
| 5 | Teams | Account/Opportunity/Case team on the record |
| 6 | Manual and Apex shares | The Sharing button, or the share object |
| 7 | Implicit sharing | Not in Setup anywhere — see below |
| 8 | View All / Modify All | Profile or permission set |
Start with the answer, not the theory
Before reasoning about any of it, query the share object. It tells you what the platform actually did.
SELECT Id, UserOrGroupId, AccessLevel, RowCause
FROM AccountShare
WHERE AccountId = '001XXXXXXXXXXXXXXX'
For a custom object, the share object is MyObject__Share and the parent field is ParentId.
RowCause is the whole answer:
Owner. They own it.ImplicitChild/ImplicitParent. Implicit sharing, layer 7.Rule. A sharing rule.Manual. Somebody clicked Share.Team. Account or opportunity team membership.- A custom label. An Apex managed share using a declared row cause.
One caveat: shares granted purely by the role hierarchy are not always materialised as rows you can query in the way explicit shares are, and profile-level View All Data never appears here at all. So an empty result for a user who can see the record means the access came from the hierarchy or from a profile permission, which narrows it usefully rather than leaving you stuck.
The layers people forget
Implicit sharing
Access nobody granted, because the platform grants it structurally. A user with access to an Account gets read access to its Contacts, Opportunities and Cases depending on the org-wide defaults for those objects. And a user with access to a child record gets read access to the parent Account.
That second one surprises people every time. Giving a rep one Opportunity gives them the Account it hangs off, which may be an account they were deliberately not given.
Implicit sharing is not configurable and does not appear in Setup. You see it only in the share rows, as ImplicitChild and ImplicitParent.
View All Data on the profile
It overrides everything and shows up nowhere in the sharing model. Check it early when the user in question is an administrator or a consultant with a cloned admin profile, which is most “why can they see this” questions about internal staff.
SELECT Name, PermissionsViewAllData, PermissionsModifyAllData
FROM Profile
WHERE Id = :someProfileId
And permission sets can grant the same thing independently of the profile:
SELECT PermissionSet.Label
FROM PermissionSetAssignment
WHERE AssigneeId = '005XXXXXXXXXXXXXXX'
AND (PermissionSet.PermissionsViewAllData = true
OR PermissionSet.PermissionsModifyAllData = true)
Object-level access is a different question
CRUD on the object and record-level sharing are separate layers that fail separately. A user with no Read on Opportunity cannot see any opportunity regardless of sharing. A user with Read but no share cannot see that one. If the symptom is “cannot see any”, stop looking at sharing entirely.
The opposite question: why can they not see it
Same order, inverted, with one addition. Check whether the record is in the Recycle Bin, whether the user’s role is genuinely above the owner’s in the hierarchy (people assume a title implies a role position), and whether a restriction rule applies. Restriction rules are the one mechanism that subtracts rather than adds, which is exactly why they break the mental model everyone carries.
A procedure that does not waste an afternoon
- Query the share object for the record. Read
RowCause. - If the user is not there, check View All Data on profile and permission sets.
- If still not there, check the role hierarchy position against the owner’s.
- Only then open Sharing Settings.
- Before changing anything, ask who else the change affects. Sharing rules are rarely one-person problems.
Frequently asked
Can I see all of a user’s record access in one place?
No single screen gives you that. The share object per record is the closest thing, and it is why step one is a query rather than a Setup page.
Does “Grant Access Using Hierarchies” apply to custom objects?
For custom objects you can turn it off, which is the one place the role hierarchy becomes optional. For most standard objects it cannot be disabled.
Why does removing a sharing rule not remove access?
Because another layer also granted it. Access is additive, so you have to remove every grant, and the share rows tell you how many there were.
Do sharing recalculations happen immediately?
Not always. Large sharing changes are processed asynchronously and can take a while on big objects, so a change that looks ineffective may simply not have finished.
Does Apex respect all of this?
Only when it is asked to. Apex runs in system mode by default and ignores sharing unless the class is with sharing or the query uses USER_MODE. A “user can see data they should not” report sometimes has nothing to do with sharing configuration and everything to do with a controller.
Reference: Sharing Rules and Role Hierarchy Guidelines, Salesforce Help; Record-Level Access: Under the Hood, Salesforce Architect resources.