🔐 Implementing Field-Level Security (FLS) and CRUD in Apex – The Right Way
In the world of Salesforce development, ensuring data security is just as important as business logic. Apex allows powerful customizations, but with great power comes great responsibility — especially when dealing with Field-Level Security (FLS) and CRUD (Create, Read, Update, Delete) access.
In this post, we’ll explore:
- What CRUD & FLS mean in Apex
- Why respecting them matters
- How to properly enforce them in code
- The difference between with sharing, without sharing, and stripInaccessible
- Real-world examples and best practices
📘 What Are CRUD and FLS?
Type | Description |
CRUD | Determines whether a user has access to create, read, update, or delete records of a given object |
FLS (Field-Level Security) | Determines whether a user has access to view or edit specific fields on an object |
Apex does NOT automatically enforce CRUD/FLS. You must enforce them manually using the tools Salesforce provides.
❌ What Happens If You Don’t Enforce It?
If you bypass FLS/CRUD:
- Users may access sensitive data they shouldn’t see (security risk)
- You may expose PII or financial data to unauthorized users
- Your app may break for users with limited access
- You could fail Salesforce security reviews
✅ How to Enforce CRUD and FLS in Apex
- CRUD Check
Use Schema.sObjectType.Account.isAccessible() and related methods to check object-level access.
if (Schema.sObjectType.Account.isAccessible()) {
Account acc = [SELECT Name FROM Account LIMIT 1];
}
Other options:
- isCreateable()
- isUpdateable()
- isDeletable()
- Field-Level Security (FLS) Check
if (Schema.sObjectType.Account.fields.Name.isAccessible()) {
System.debug(acc.Name);
}
You can also check isUpdateable() or isCreateable() on fields.
🧪 Full CRUD + FLS Example
if (Schema.sObjectType.Contact.isCreateable() &&
Schema.sObjectType.Contact.fields.Email.isCreateable()) {
Contact c = new Contact(FirstName = ‘Jane’, LastName = ‘Doe’, Email = ‘jane@example.com’);
insert c;
} else {
throw new AuthorizationException(‘Insufficient access to create contact or set email’);
}
🚀 Modern Way: Using Security.stripInaccessible()
Introduced in API v45.0, Security.stripInaccessible() is a powerful tool that removes fields a user doesn’t have access to from a record (instead of throwing an error).
Example – Reading Only Accessible Fields
List<Account> rawAccounts = [SELECT Id, Name, AnnualRevenue FROM Account LIMIT 10];
List<SObject> secureAccounts = Security.stripInaccessible(AccessType.READABLE, rawAccounts).getRecords();
Example – Before DML
Contact c = new Contact(FirstName=’John’, LastName=’Smith’, Email=’john@example.com’);
List<SObject> sanitized = Security.stripInaccessible(AccessType.CREATABLE, new List<SObject>{c}).getRecords();
insert sanitized;
Why Use stripInaccessible()?
✅ Automatically removes non-accessible fields
✅ Reduces boilerplate code
✅ Recommended for Lightning Platform Security Review
✅ Supports bulk operations
🧩 Bonus: With Sharing vs Without Sharing
Keyword | Description |
with sharing | Enforces sharing rules (OWD, role hierarchy, sharing rules) |
without sharing | Ignores sharing rules |
Default | Uses the sharing setting of the caller class |
🔒 FLS and CRUD are not impacted by sharing keywords — they must always be enforced separately!
🧠 Best Practices Summary
Tip | Why It Matters |
Use Schema.sObjectType to check CRUD/FLS for manual control | Basic protection |
Use Security.stripInaccessible() whenever possible | Cleaner, scalable code |
Always bulkify your access checks | Prevent governor limits |
Log or throw meaningful errors on access violations | Easier debugging |
Never assume a field is accessible | Even Admins may have custom profiles |
Cover access checks in test methods using System.runAs() | Boosts test realism and security review readiness |