---
title: "Enforcing CRUD and field-level security in Apex, now that SECURITY_ENFORCED is gone"
url: "https://trailassembly.com/apex-enforcing-crud-and-fls/"
topic: "Apex and Development"
published: "2026-09-11T02:54:54+00:00"
updated: "2026-10-06T13:52:29+00:00"
tested_on: "Summer ’26"
---

# Enforcing CRUD and field-level security in Apex, now that SECURITY_ENFORCED is gone

**The answer, briefly:** use `WITH USER_MODE` on your queries and `AccessLevel.USER_MODE` on your Database calls. The syntax most tutorials still teach, `WITH SECURITY_ENFORCED`, no longer compiles. Running it in a Summer ’26 org returns, verbatim: *“WITH SECURITY_ENFORCED is no longer supported, use WITH USER_MODE instead.”*

## Apex runs in system context, which is the whole problem

Apex ignores [object permissions](https://trailassembly.com/object-permissions-vs-record-access/), field-level security and sharing by default. `with sharing` on the class restores record-level sharing, and it does nothing at all for object or field permissions — a distinction that catches people who believe one keyword covered everything.

So a controller that queries a field the [running user](https://trailassembly.com/dashboard-running-user/) cannot see will happily return it, and your Lightning component will happily display it. Nothing errors. That is the failure mode: not a crash, a disclosure.

## The three eras of enforcement, and where we are now

**Manual checks.** `Schema.sObjectType.Account.isAccessible()` and `isUpdateable()` per object, per field, by hand. Verbose, easy to forget, and still what a lot of older code does.

**`WITH SECURITY_ENFORCED`.** A SOQL clause that checked field and object access for the query. Better, and limited: it only covered the query, it threw rather than filtered, and it did not apply to DML.

**`WITH USER_MODE` and `AccessLevel.USER_MODE`.** The current answer, and the only one that still compiles. User mode applies object permissions, field-level security *and* [sharing rules](https://trailassembly.com/sharing-rules/), on both queries and DML.

Verified in a Summer ’26 Developer org on 10 September 2026: `WITH USER_MODE` compiles and returns rows; `Database.query(..., AccessLevel.USER_MODE)` compiles and returns rows; `WITH SECURITY_ENFORCED` fails to compile with the message quoted above.

## What the current syntax looks like

```apex
// Query in the user's context: object, field and sharing all apply.
List<Account> accounts = [SELECT Id, Name, AnnualRevenue FROM Account WITH USER_MODE];

// Dynamic SOQL takes the access level as an argument.
List<Account> more = Database.query('SELECT Id FROM Account', AccessLevel.USER_MODE);

// DML too — this is the part SECURITY_ENFORCED never covered.
Database.insert(newAccounts, AccessLevel.USER_MODE);
Database.update(changed, AccessLevel.USER_MODE);
```

The DML line is the upgrade that matters most. Before user mode, a class could enforce access perfectly on the way in and then write a field the user had no permission to edit.

## When you want filtering rather than an exception

User mode throws when the running user lacks access. Sometimes you want the operation to continue with the inaccessible fields removed — a list view that shows what it can, rather than failing entirely.

```apex
SObjectAccessDecision decision = Security.stripInaccessible(AccessType.READABLE, records);
List<Account> safe = decision.getRecords();
System.debug(decision.getRemovedFields());
```

`stripInaccessible` returns the records with unreadable fields blanked, and tells you which fields it removed. That second part is genuinely useful in a log: it turns “a user says a field is empty” into a fact.

## What to do with the code you already have

You cannot leave `WITH SECURITY_ENFORCED` in place. It will not compile, so any deployment touching that class fails. The migration is mechanical, and the ordering matters:

1. **Find every occurrence.** A search across your metadata, not a memory of where it was used.

2. **Replace with `WITH USER_MODE`**, one class at a time.

3. **Run the tests, and expect new failures.** User mode is stricter. It enforces sharing as well as field access, so a test whose data was created by a system-context setup can now legitimately see nothing.

4. **Decide per failure** whether the test data was unrealistic or the code was over-permissive. Both happen, and the second one is the reason to do this.

Do not switch a whole codebase to user mode in one deployment. The failures are informative and you want to read them, not race them.

## Where user mode is the wrong choice

Some code is supposed to run in [system context](https://trailassembly.com/flow-system-vs-user-context/): a batch job recalculating a rollup across records nobody owns, an integration that must write regardless of the calling user, a trigger maintaining data integrity. Forcing user mode there breaks legitimate work.

The test is not “is this Apex” but “is this acting on behalf of a person”. A controller behind a Lightning component: user mode. A nightly recalculation: system mode, deliberately, with a comment saying why.

## Frequently asked

**Does `with sharing` still matter?** Yes, for sharing, and only for sharing. It is not a substitute for user mode and never was.

**Will this break my managed package?** Packaged code compiled against an older version is not recompiled by an upgrade, but any class you edit and redeploy is. That is why this arrives as a deployment failure rather than a runtime one.

**Is user mode slower?** There is a cost to enforcing sharing on a query, and it is the same cost the platform pays for a user running a report. If it matters at your volume, that is a [selectivity problem](https://trailassembly.com/selective-queries-at-scale/) before it is an enforcement one.

**What about SOSL?** It takes `WITH USER_MODE` too, and the same reasoning applies.

---

Source: https://trailassembly.com/apex-enforcing-crud-and-fls/
Trail Assembly — independent Salesforce write-ups.
