Implementing Field-Level Security (FLS) and CRUD in Apex – The Right Way

🔐 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 

  1. 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() 
  1. 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 

 

 

Leave a Comment

Your email address will not be published. Required fields are marked *