Skip to main content

Salesforce Basics: Apex API v67 Security Changes: User Mode by Default Guide

💬 In plain words: Starting in API version 67 (Summer '26), Apex is safe by default. All queries and DML operations automatically enforce the running user's permissions, field-level security (FLS), and record sharing unless you explicitly ask for system power. An unmarked class now defaults to with sharing. Bypassing security with without sharing is something you must explicitly declare in your code. Legacy classes (API v66 and below) retain their old behavior.
📌 Example: You upgrade a helper class from API v58 to v67, and suddenly a user gets an INSUFFICIENT_ACCESS error on a field update. Nothing broke—the class simply runs in user mode by default now. It was over-reaching silently for years, and API v67 turned the lights on.
🎬 Real-Life Example: The Upgrade That Changed Code Behavior

Skyline updates its Apex classes to API v67. A background batch job that quietly processed every record for years suddenly processes fewer rows. Nothing in the code changed—the platform default did.

The Old/Bad Way: Responding with panic in one of two wrong ways: either freezing every class on an old API version forever, or recompiling everything to API v67 in one weekend without proper test coverage and uncovering errors in production.

Why This Fails: Legacy code often depended on system-mode permissions it never requested explicitly. Freezing API versions buries tech debt, while blind mass upgrades break functionality.

The New/Good Way:

  • Understand the new default: on API v67+, database operations execute in user mode by default.
  • Migrate class by class, running comprehensive unit tests first.
  • When a background process truly requires full access, request it explicitly using system-mode controls (such as AccessLevel.SYSTEM_MODE) and document why.

The Payoff: Security is now the default setting. System power is an exception you must ask for by name.

Core Security Concepts in API v67

This is the largest security shift in Apex history, consisting of three interconnected changes anchored to API version 67.0 and later:

  • User Mode Operations by Default: Database operations—including SOQL, SOSL, DML, and Database methods—run in USER MODE by default. They enforce object permissions, FLS, and sharing rules automatically. Earlier versions defaulted to system mode.
  • Default Class Sharing: A class compiled at API v67 without a sharing keyword defaults to with sharing. Bypassing sharing requires explicit declaration.
  • Deprecation of Legacy Syntax: The WITH SECURITY_ENFORCED clause is completely removed and fails compilation at v67+.
  • Explicit Elevation: Elevated access remains available but must be declared explicitly: WITH SYSTEM_MODE in SOQL, as system in DML, AccessLevel.SYSTEM_MODE in Database methods, or an explicit without sharing on the class.
  • Inheritance Trap: In an inheritance chain, if any class in the hierarchy is on API v67+, an omitted sharing declaration across the chain defaults to with sharing.
  • Triggers Context: Triggers always run in system mode regardless of API version. Trigger handler classes must declare their own explicit sharing and access contexts.
🧠 v67 Core Rule: "SAY IT TO BYPASS IT"
Unmarked class = WITH sharing | Unmarked operation = USER mode | SECURITY_ENFORCED = Removed | Triggers = System mode.
🧭 360 Card — Secure-by-Default Apex (v67+)
  • Rule: Starting from API v67, say it to bypass it. Unmarked classes mean with sharing; unmarked operations mean user mode.
  • Gain: Secure execution is now the default. New code enforces user permissions, field security, and sharing rules automatically.
  • Price: This is an execution behavior change. Legacy code assuming system mode will throw INSUFFICIENT_ACCESS errors once saved under API v67.
  • Limits: Applies only to classes saved at API v67 or later. WITH SECURITY_ENFORCED no longer compiles. Triggers run in system mode, requiring explicit handler declarations.
  • Operation-Level Control: Adding WITH USER_MODE or using AccessLevel parameters overrides the class keyword for that single operation.
  • Migration Strategy: Make every class sharing declaration explicit before upgrading API version numbers across an org.
⚠ INTERVIEW TRAP: API v67 did not "break" your class. The class was over-reaching its permissions for years without visibility. API v67 simply enabled secure defaults.

Core Q&A

Q: What changed in Apex security at API version 67, and why does it matter?
🎯 Say this first: From API v67 onwards, Apex defaults to user mode and with sharing. You must explicitly state when you want to bypass security.

A: Before API v67, Apex assumed code had already checked permissions. Operations ran in system mode, ignoring CRUD, FLS, and sharing. An unmarked class ran without sharing at its entry point, requiring developers to remember to add security manually.

From API v67, database operations run in user mode, automatically enforcing object permissions, FLS, and record sharing. Unmarked classes default to with sharing, and WITH SECURITY_ENFORCED no longer compiles. System power still exists, but must be written explicitly: without sharing on the class, WITH SYSTEM_MODE in queries, or AccessLevel.SYSTEM_MODE in Database methods.

Scenario-Based Follow-Ups

Q1: A class is declared 'without sharing', but a query inside uses WITH USER_MODE. What is enforced? What about the reverse scenario?

A1: The operation-level setting always wins for that specific operation.

  • In a without sharing class, a query using WITH USER_MODE enforces object permissions, FLS, and sharing rules for that single query.
  • In a with sharing class running a plain system-mode query, the class keyword enforces row-level record sharing, but the system query does not enforce CRUD or FLS. Users can still receive field values forbidden by their profile.
  • Sharing keywords control ROWS only. User mode controls ROWS, OBJECTS, and FIELDS.
Q2: Your org has 800 Apex classes on API v58–v62. Leadership wants everything upgraded to v67. What is your migration plan?

A2: Treat this as a behavior migration rather than a simple version bump:

  • Step 1: Make Sharing Explicit: Add explicit sharing declarations (with sharing, without sharing, or inherited sharing) across all existing classes to avoid inheritance chain defaults.
  • Step 2: Upgrade in Phased Waves: Upgrade utilities and selectors first, and integration entry points last. Run test suites after every wave.
  • Step 3: Error Triage: Triage errors into two buckets: if the code was over-reaching, keep user mode and fix permissions; if it is legitimate system work, declare without sharing or SYSTEM_MODE explicitly.
  • What to Refuse: Refuse to fix user-mode errors by broadly expanding profile or permission set access, as this exposes data across list views, reports, and APIs.
Q3: If API v67+ makes Apex safe by default, why still write 'with sharing' explicitly?

A3: Legacy classes on v66 and below retain old behaviors, making unannotated code ambiguous. Writing explicit keywords communicates developer intent clearly to reviewers and prevents unexpected behavior in inherited sharing contexts.

📝 2-Minute Self-Check

Q1. OWD is Private, yet a user sees a record they shouldn't. Where do you look?
A1. Check the record's Sharing Hierarchy first, then user permissions for "View All" overrides.

Q2. Which tool is used when one team must see LESS data than everyone else?
A2. A Restriction Rule, which is the only security tool that subtracts access.

Q3. An Apex class has no sharing keyword and was saved at API v67. What mode does it run in?
A3. It runs with sharing in user mode by default.