Field-Level Security (FLS) Best Practices
Overview
Field-Level Security (FLS) controls which users can view and edit specific fields on records. Proper FLS configuration is essential for data security and compliance.
Understanding FLS
Two Levels of Field Access
- Readable: User can see the field value
- Editable: User can modify the field value (implies readable)
FLS vs Object Permissions
User Access = Object Permissions + Field-Level Security + Record Access
Object Permissions: Can user access the object at all?
FLS: Can user see/edit specific fields?
Record Access: Can user access specific records?
Where FLS is Configured
- Profiles (base access)
- Permission Sets (additive access)
- Permission Set Groups (combined sets)
Best Practices
1. Use Permission Sets Over Profiles
Why:
- Profiles are monolithic and hard to maintain
- Permission Sets are additive and composable
- Easier to manage feature-based access
Pattern:
Profile: Base access (read-only on most fields)
Permission Set: Feature-specific edit access
Example:
<!-- Permission Set for Invoice Editors -->
<PermissionSet>
<label>Invoice Editor</label>
<fieldPermissions>
<editable>true</editable>
<field>Invoice__c.Amount__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Invoice__c.Status__c</field>
<readable>true</readable>
</fieldPermissions>
</PermissionSet>
2. Principle of Least Privilege
Rule: Grant minimum access needed for the job function
Steps:
- Start with no access
- Add read access where needed
- Add edit access only where required
- Review periodically
Anti-Pattern:
<!-- DON'T: Grant edit on everything -->
<fieldPermissions>
<editable>true</editable>
<field>Account.AnnualRevenue</field>
<readable>true</readable>
</fieldPermissions>
Better:
<!-- DO: Grant read-only unless edit is required -->
<fieldPermissions>
<editable>false</editable>
<field>Account.AnnualRevenue</field>
<readable>true</readable>
</fieldPermissions>
3. Protect Sensitive Fields
Identify Sensitive Fields
| Category | Examples |
|---|---|
| PII | SSN, Date of Birth, Driver's License |
| Financial | Bank Account, Credit Card, Salary |
| Health | Medical conditions, Insurance |
| Security | Password, Token, API Key |
| Business | Trade secrets, Pricing formulas |
Protection Strategies
- Restrict to Specific Permission Sets
<PermissionSet>
<label>PII_Access</label>
<description>Access to sensitive PII fields</description>
<fieldPermissions>
<editable>false</editable>
<field>Contact.SSN__c</field>
<readable>true</readable>
</fieldPermissions>
</PermissionSet>
- Use Shield Platform Encryption
- Encrypts data at rest
- Searchable with deterministic encryption
- Requires Shield license
- Use Classic Encryption (Encrypted Text Field)
- Masks display (shows last 4 characters)
- Limited functionality
- No additional license needed
4. FLS in Apex Code
Always Check FLS in Apex
With SOQL:
// Good: Respects FLS
List<Account> accounts = [
SELECT Name, Industry
FROM Account
WITH USER_MODE
];
// Alternative: Security.stripInaccessible
List<Account> accounts = [SELECT Name, Industry, Revenue FROM Account];
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
accounts
);
List<Account> safeAccounts = decision.getRecords();
With DML:
// Good: Respects FLS
Database.insert(accounts, AccessLevel.USER_MODE);
// Alternative: Check before DML
if (Schema.sObjectType.Account.fields.Industry.isUpdateable()) {
account.Industry = 'Technology';
}
5. Organize Permission Sets by Function
Pattern:
[Object]_[AccessLevel]
[Feature]_[Role]
[Integration]_Access
Example Structure:
Permission Sets/
├── Account_Read_Only.permissionset-meta.xml
├── Account_Full_Access.permissionset-meta.xml
├── Invoice_Approver.permissionset-meta.xml
├── Invoice_Creator.permissionset-meta.xml
├── ERP_Integration_Access.permissionset-meta.xml
└── Reports_Power_User.permissionset-meta.xml
6. Use Permission Set Groups
Why:
- Combine related permission sets
- Easier role-based assignment
- Single assignment per user role
Example:
Permission Set Group: Sales_Representative
├── Account_Full_Access
├── Contact_Full_Access
├── Opportunity_Full_Access
├── Quote_Creator
└── Reports_Basic
7. Handle Formula Fields Correctly
Important: Formula fields inherit FLS from referenced fields
Formula: Account.Owner.Manager.Name
User needs READ access to:
- Account.OwnerId
- User.ManagerId
- User.Name (on Manager)
Best Practice: Test formulas with different user profiles
8. Document FLS Decisions
For each sensitive field, document:
- Why it exists
- Who should have access
- Which permission sets grant access
- Review date
Example:
<CustomField>
<fullName>SSN__c</fullName>
<description>
Social Security Number - SENSITIVE
Access: HR_PII_Access permission set only
Review: Quarterly by Security team
</description>
</CustomField>
Common Mistakes
1. Relying on Page Layout for Security
❌ "Users can't see it because it's not on the layout"
✅ FLS controls true visibility; layouts are UX only
2. Granting Edit Without Business Need
❌ "Give them edit just in case they need it"
✅ Start read-only, add edit when requested
3. Not Checking FLS in Apex
❌ Assuming controller/trigger runs as system
✅ Always use WITH USER_MODE or Security.stripInaccessible
4. Over-Using System Administrator
❌ "Just use System Admin profile"
✅ Create specific permission sets for each function
5. Not Testing FLS Changes
❌ Deploy FLS changes without testing
✅ Test with actual user profiles before deploy
FLS Audit Checklist
For Each Object
- All fields reviewed for sensitivity
- Sensitive fields have restricted access
- Permission Sets exist for each access pattern
- No unnecessary edit permissions
- Formula fields tested with limited users
- Integration users have minimal required access
For Apex Code
- All SOQL uses WITH USER_MODE or stripInaccessible
- All DML uses USER_MODE or checks isUpdateable()
- Tests verify FLS enforcement
- No hardcoded field access bypass
For Deployment
- FLS changes reviewed by security team
- Changes tested with representative users
- Rollback plan exists
- Documentation updated
Quick Reference
SOQL FLS Modes
| Mode | Behavior |
|---|---|
WITH USER_MODE |
Enforces FLS and sharing |
WITH SYSTEM_MODE |
Bypasses FLS (use carefully) |
| Default (no mode) | Depends on context |
Schema Methods
| Method | Purpose |
|---|---|
isAccessible() |
Can user read field? |
isUpdateable() |
Can user edit field? |
isCreateable() |
Can user set on create? |
Security.stripInaccessible
| AccessType | Strips Fields Not... |
|---|---|
READABLE |
Readable by user |
CREATABLE |
Creatable by user |
UPDATABLE |
Updatable by user |
UPSERTABLE |
Creatable or updatable |