TL;DR
- In Spring '27, Salesforce removes non-public system fields from custom object data returned in Aura action responses, filtering out internal fields that were never part of the public API.
- Salesforce's own guidance is the giveaway about severity: "Review your Lightning components that process custom object data to avoid JavaScript errors or missing data." Both outcomes are named, and the second is the dangerous one.
- Nothing in your pipeline catches this. Apex tests pass, because the Apex did not change. The build passes, because there is nothing to compile against. It fails at runtime, in a browser, only when a particular component renders particular data.
- Aura, not LWC. If a component was migrated to LWC it is not in scope here, which makes this a tax specifically on the parts of an org nobody has got round to modernising.
- The realistic check is the release update's Test Run option in a sandbox, followed by someone actually clicking through the affected screens.
What You'll Learn
- What "non-public system field" means in practice, and why a component ends up reading one by accident
- Why the missing-data outcome is more dangerous than the JavaScript error
- How to find candidate components without reading every one
- Why this is one of the few changes where clicking through the UI is the primary test rather than a formality
The Problem
When an @AuraEnabled Apex method returns an SObject, the platform serialises that record into a response the component receives as a plain JavaScript object. Historically that serialisation carried more than the documented API fields. Internal system fields, the ones that are not part of the public Salesforce API and are not in the object's described schema, came along too.
Nobody designed a component to use those fields. What happens is more ordinary than that. A developer inspects the response in the browser console, sees a property that looks useful, and reads it. Or a component iterates the keys of the returned object to build a table. Or a record is spread into a state object and something downstream depends on a shape that includes fields nobody enumerated. In each case the dependency is real and undocumented, and it exists because the data was there rather than because anyone chose it.
Spring '27 removes those fields from the response. The component asks for a property that no longer exists and receives undefined.
Quick Answer
Spring '27 filters non-public system fields out of custom object data returned in Aura action responses, so any Aura component reading an internal field that was never part of the public API receives undefined from that release onward. Salesforce warns this produces JavaScript errors or missing data, and the missing data case is the one to plan around because it does not announce itself. Find candidates by locating @AuraEnabled Apex methods that return SObjects or lists of SObjects, then checking the Aura components that consume them for property reads and key iteration that are not backed by the object's described schema. Components already migrated to LWC are out of scope. Because nothing in a build or an Apex test exercises this path, the only reliable verification is to enable the release update's Test Run option in a sandbox and click through the affected screens with real data.
Why missing data is worse than the error
Salesforce names two outcomes. They are not equally bad, and the ordering in their sentence is the opposite of the ordering by risk.
A JavaScript error is loud. Something throws, a component fails to render, somebody raises a ticket, and you find it. Unpleasant, but the feedback loop works.
Missing data is quiet. A field that used to appear in a column is now blank. A conditional that used to be truthy is now falsy, so a section does not render, or a badge does not show, or a row is filtered out of a list. The screen still works. Nothing throws. The page just shows less than it did, and whether anyone notices depends entirely on whether a human happens to know what should have been there.
That is the case worth designing your testing around, because a smoke test that checks "the page loads" passes cleanly through it.
Step 1: Narrow the search before reading any components
You do not need to review every Aura component. You need the ones that receive SObject data from Apex.
Start from the Apex side, because it is a much smaller and more searchable set:
grep -rn --include='*.cls' -B2 -A6 "@AuraEnabled" force-app | \
grep -iE "@AuraEnabled|List<[A-Za-z_]+__c>|[A-Za-z_]+__c |SObject" | head -60
What you are looking for is a method annotated @AuraEnabled whose return type is a custom object, a list of them, or a bare SObject. Those are the methods whose responses change shape. A method that returns a purpose-built wrapper class with named properties is not affected, because the wrapper only ever carried the fields somebody wrote down. That distinction is the useful one, and it doubles as the fix: wrappers were always the more defensible pattern, and this change is the platform making that concrete.
Retrieve the Apex if it is not already local:
sf project retrieve start --metadata ApexClass --target-org yourorg
Then work forward from each affected method to the components that call it.
Step 2: Look for the two patterns that create the dependency
In the Aura component controller and helper, two shapes matter more than the rest.
Direct reads of a property that is not in the schema. A line like record.SomeInternalField where SomeInternalField does not appear in Setup under the object's fields. The absence from Setup is the signal: if an admin cannot see the field on the object, a component should not be reading it from the response.
Iteration over the keys of the returned record. Anything shaped like Object.keys(record).forEach(...) or a loop that builds columns from whatever came back. This is the harder case, because there is no field name to grep for. The component's behaviour simply changes when the set of keys changes, and it changes silently. If you find one of these, it goes at the top of the list for a manual test rather than a code review, because reading it will not tell you what it renders.
Cross-check anything suspicious against the object's real schema, which you can pull without leaving the terminal:
sf sobject describe --sobject Your_Object__c --target-org yourorg --json | \
python3 -c "import json,sys; print(sorted(f['name'] for f in json.load(sys.stdin)['result']['fields']))"
Anything the component reads that is not in that list is a candidate.
Step 3: Test it the only way that works
This is the part where the usual pipeline has nothing to offer, so it is worth being blunt about it.
Apex tests will pass. The Apex is unchanged, and a test asserting that a method returns three records will still find three records. The serialisation layer is not what your tests exercise.
A deployment will succeed. There is no compile-time relationship between a component reading a property in JavaScript and the fields the platform chooses to serialise.
Static analysis will not help either, because the dependency is on data that arrives at runtime.
What does work is the release update's own Test Run option. Salesforce documents this as standard for release updates: "Often, release updates provide a Test Run option so you can enable an update and examine any changes to your org, including changes to customizations, before that update's Complete Steps By date." Enable it in a sandbox, then have someone open each affected screen with realistic data and compare against what they expect to see.
Realistic data matters more than usual here. A record with most fields empty looks the same before and after a field stops being returned.
Scope: this is an Aura problem
Components migrated to LWC are not in scope for this change, which puts it in an unusual category: a deadline that only affects the parts of an org that have not been modernised.
That is worth saying out loud when the work is being prioritised, because it changes the calculation. If a component is going to be rebuilt in LWC in the next year anyway, doing that work now satisfies this deadline as a side effect. If it is not, the fix is to stop depending on undocumented fields, which is the same discipline you would apply for any other reason.
Frequently Asked Questions
Q: How do I know which fields are the non-public ones?
A: The reliable test is the object's own description rather than a list of names. Anything that appears in a describe call, or in Setup under the object's fields, is a public API field and is unaffected. Anything a component is reading that does not appear there is a candidate for removal. Working from the schema instead of a field list also means you do not have to trust a list staying current, including this post.
Q: We use LWC everywhere. Can we ignore this?
A: Almost certainly, but confirm rather than assume. Orgs that describe themselves as LWC-only frequently still have Aura in three places: an old utility bar item, something inside a managed package, and an app page nobody has opened in two years. The first two are easy to check and the third is the one that surprises people.
Q: Our components read the record with a wrapper class. Are we affected?
A: No, and that is the pattern to move toward. A wrapper only ever carries properties someone declared, so the response shape does not depend on what the platform happens to serialise. If you are fixing components anyway, converting a bare SObject return into a wrapper is the change that makes the whole class of problem go away rather than just this instance of it.
Q: What if we cannot test before the enforcement date?
A: Then prioritise by blast radius rather than trying to cover everything. Components on screens that many people use daily surface a problem quickly through ordinary use. Components on a quarterly process do not, and those are where an unnoticed missing value can sit for months. Test the rare paths first, because the common ones will tell you themselves.
Q: Is this really a security change?
A: It is filed as one, and the reasoning holds: internal fields being visible to any component that receives a record is information disclosure, even if nothing sensitive is in them today. The reason it reads like a breaking change is that the fix for the disclosure is removal, and removal is what breaks dependents. Both descriptions are accurate.
Key Takeaways
- Spring '27 removes non-public system fields from custom object data in Aura action responses.
- Missing data is the outcome to plan for, not the JavaScript error. The error tells you; the blank field does not.
- Your pipeline will not catch it. Apex tests pass, deployments succeed, static analysis sees nothing. This one needs a browser.
- Narrow from the Apex side first, to
@AuraEnabledmethods returning SObjects, then work forward to their components. - Wrapper classes are immune, which makes converting a bare SObject return the durable fix rather than a patch.
- LWC is out of scope, so anything already migrated has nothing to do here.
What's Next?
Run the @AuraEnabled search today to find out whether you have a list at all. Many orgs will find only wrapper-returning methods and can stop there.
If you do have a list, get the release update onto a sandbox with Test Run enabled well before the Spring '27 date, because the testing here is manual and manual testing needs scheduling rather than a spare afternoon.
For the rest of the release calendar and what has already enforced, see Salesforce Winter '27 Security Readiness. For the OAuth deadlines running alongside this one, the nearest is the device flow restriction on 30 November 2026.
Resources & References
- Salesforce Release Updates, under Scheduled to Be Enforced in Spring '27, in Salesforce Help. Confirm the status for your own org in Setup, then Release Updates, where the Test Run option also lives.
sf sobject describein the Salesforce CLI, for the authoritative list of public fields on an object.- Salesforce Winter '27 Security Readiness for the wider release picture.
- Salesforce Security Enforcement in 2026 for the enforcement calendar this sits at the end of.
Responses
Checking your session.
Loading responses.