INVALID_FIELD: No such column 'X' on entity 'Y'
Either the field truly doesn't exist on the object, or it exists but the running user / API session can't see it. The same error message is used for "field doesn't exist" and "you don't have FLS to read it" — and disambiguating those two is the whole job.
Also seen asINVALID_FIELD·No such column·No such column on entity·INVALID_FIELD: No such column
A reporting tool that pulls Opportunity data nightly has been running for a year. This morning it logs INVALID_FIELD: No such column 'AnnualRevenue' on entity 'Opportunity'. The query lists fifteen columns. Fourteen of them are valid. One doesn't exist on Opportunity, but the report owner swears it ran yesterday.
What the platform is actually telling you
INVALID_FIELD fires when a SOQL query references a field name that the platform cannot resolve on the named object. The cause falls into a few buckets: the field really doesn't exist (typo, wrong API name, looking at the wrong object), the field exists but on a different object than the one referenced, the field exists but the running user lacks field-level access, or the field was renamed or deleted since the code was written.
The error message names the field and the object, which is helpful, but it doesn't tell you which of those four causes is yours. The error fires before any row processing, which means a malformed SELECT clause aborts the entire query without returning anything.
This same error shape applies in dynamic SOQL via Database.query, in REST API queries, in Bulk API queries, in Apex describe calls, and in reports created via the Reports REST API. Every place SOQL gets parsed is a potential error site.
The broken example
A scheduled Apex class that scans opportunities:
public class OpportunityRevenueExport {
public static void exportToday() {
List<Opportunity> opps = [
SELECT Id, Name, AnnualRevenue, CloseDate, StageName
FROM Opportunity
WHERE LastModifiedDate = TODAY
];
for (Opportunity o : opps) {
ExportRow.publish(o);
}
}
}
AnnualRevenue exists on Account, not Opportunity. Someone wrote the query thinking they could pull the Account's revenue directly. The query throws INVALID_FIELD: No such column 'AnnualRevenue' on entity 'Opportunity'.
A second shape, the same idea but via a parent reference:
List<Opportunity> opps = [
SELECT Id, Name, AccountRevenue__c, CloseDate
FROM Opportunity
WHERE LastModifiedDate = TODAY
];
The field AccountRevenue__c was created on Opportunity to denormalize the parent revenue. It worked for a while. Then someone deleted it during a cleanup. The query that depends on it throws.
A third shape, a managed-package field without the namespace:
List<Opportunity> opps = [
SELECT Id, Name, Renewal_Notice_Days__c
FROM Opportunity
];
If Renewal_Notice_Days__c lives in a managed package with namespace revenue, the actual API name is revenue__Renewal_Notice_Days__c. The unprefixed reference fails.
Three paths to a fix
The likeliest causes ranked by frequency:
Use the correct parent-field syntax. To pull a parent field, use the relationship name and dot notation: Account.AnnualRevenue on Opportunity reads the parent Account's AnnualRevenue. This works because Opportunity has a lookup to Account. The relationship name for the standard parent is Account; for custom parents, it's the relationship name configured on the lookup field (visible in Setup, then the field, then "Child Relationship Name" if reading from the parent's side).
Confirm the field exists and the user has access. Open Setup, then the object, then Fields & Relationships. Find the field by label or API name. Confirm the API name matches what's in the query. Check the field's Field-Level Security to confirm the running user's profile or permission sets grant read access. A user without FLS on a field gets the same INVALID_FIELD error even though the field exists.
Include the namespace prefix for managed-package fields. For fields in installed packages, prefix the API name with the package namespace and two underscores. Setup, then Installed Packages, lists the namespace for each installed package. The same field in your own custom package uses your own namespace (or none if you're in the package's source org).
The fixed example
The same export, corrected:
public class OpportunityRevenueExport {
public static void exportToday() {
// Read the parent's AnnualRevenue via the relationship.
List<Opportunity> opps = [
SELECT
Id,
Name,
Account.AnnualRevenue,
AccountId,
CloseDate,
StageName
FROM Opportunity
WHERE LastModifiedDate = TODAY
];
for (Opportunity o : opps) {
Decimal accountRev = o.Account != null ? o.Account.AnnualRevenue : null;
ExportRow.publish(o, accountRev);
}
}
}
The relationship traversal works regardless of whether the parent has the field populated. If the parent is null (rare for required lookups but possible), the guard prevents an NPE.
For managed-package fields, the namespaced version:
List<Opportunity> opps = [
SELECT Id, Name, revenue__Renewal_Notice_Days__c, CloseDate
FROM Opportunity
];
Validating fields before query
For dynamic SOQL where the field list is built at runtime, validate before query:
public class SafeQuery {
public static List<SObject> queryWithCheck(String objectName, List<String> fields, String whereClause) {
Map<String, Schema.SObjectType> globalDesc = Schema.getGlobalDescribe();
if (!globalDesc.containsKey(objectName)) {
throw new IllegalArgumentException('Unknown object: ' + objectName);
}
Map<String, Schema.SObjectField> fieldsMap = globalDesc.get(objectName)
.getDescribe()
.fields
.getMap();
List<String> unknownFields = new List<String>();
for (String f : fields) {
// Strip relationship traversals; describe maps don't include them as keys.
String localField = f.contains('.') ? f.substringBefore('.') : f;
if (!fieldsMap.containsKey(localField)) {
unknownFields.add(f);
}
}
if (!unknownFields.isEmpty()) {
throw new IllegalArgumentException('Unknown fields: ' + unknownFields);
}
String soql = 'SELECT ' + String.join(fields, ', ')
+ ' FROM ' + objectName
+ ' WHERE ' + whereClause;
return Database.query(soql);
}
}
This catches field mistakes at the validation step rather than at SOQL parse time, with a clearer error message.
When the field exists but FLS hides it
A subtle case: the field is defined on the object but the running user can't see it. The SOQL parser doesn't tell you "FLS hides this field"; it just returns INVALID_FIELD as if the field didn't exist. From the platform's perspective, hiding the field's existence is part of the security model.
To debug:
- Run the same query as a user with broad permissions (a System Administrator clone). If the query succeeds for an admin and fails for a specific user, FLS is the cause.
- Check the user's profile or permission sets for field-level read access on the field.
- Grant read via a permission set rather than profile edit (more flexible, easier to revoke).
For Apex code running in with sharing context, FLS is enforced. For without sharing context, FLS is also enforced for the SOQL parser; the difference is just record-level sharing. SOQL never bypasses FLS, regardless of sharing context.
The describe-and-cache pattern
Repeated Schema.getGlobalDescribe() calls are expensive. For code that does many field validations, cache the describes:
public class FieldCache {
private static Map<String, Map<String, Schema.SObjectField>> cache =
new Map<String, Map<String, Schema.SObjectField>>();
public static Map<String, Schema.SObjectField> fieldsFor(String objectName) {
if (!cache.containsKey(objectName)) {
cache.put(objectName,
Schema.getGlobalDescribe()
.get(objectName)
.getDescribe()
.fields
.getMap());
}
return cache.get(objectName);
}
}
Per-transaction cache (Apex statics reset between transactions) saves CPU on repeated lookups.
When the field was renamed
If an admin renames a field's API name (Setup, then the field, edit, change API name), every reference to the old name breaks. Salesforce attempts to update some references automatically (formula fields, reports built in the UI), but Apex code, custom integrations, and metadata files written by hand don't get updated.
The fix:
- Grep the repo for the old API name.
- Replace with the new one.
- Test.
- Deploy.
If you're the admin doing the rename, audit dependencies before pressing Save. The Salesforce UI shows you a "Where is this used?" report on most metadata items. Run it before renaming.
The bulk-error variant
When a Bulk API job runs a SOQL with an invalid field, the entire job fails before processing any batches:
{
"id": "750xx000000ABCD",
"operation": "query",
"object": "Opportunity",
"createdById": "005xx000001Sv8z",
"state": "Failed",
"errorMessage": "INVALID_FIELD:No such column 'AnnualRevenue' on entity 'Opportunity'"
}
The job's errorMessage carries the same diagnostic. The fix is the same: correct the field reference in the source.
For Bulk API V2 query jobs, fix the source query and re-create the job. The job id is single-use; you cannot mutate the SOQL of an existing job.
Cross-object describes
Sometimes you have to traverse relationships in describes:
Schema.SObjectField accField = Schema.SObjectType.Opportunity.fields.AccountId;
Schema.DescribeFieldResult dfr = accField.getDescribe();
List<Schema.SObjectType> references = dfr.getReferenceTo();
// references[0] is Account; you can traverse from there.
This kind of code is useful in generic frameworks that walk object graphs. It's also a place where field-rename bugs hide: if the AccountId lookup's referenceTo changed (because someone deleted the underlying field), the framework breaks.
Test patterns
Two tests cover most regressions:
A field-existence test that asserts every field your code references is present on the target object. Run on every deploy.
@isTest
static void revenue_export_fieldsExist() {
Map<String, Schema.SObjectField> oppFields =
Schema.SObjectType.Opportunity.fields.getMap();
System.assert(oppFields.containsKey('amount'), 'Amount required');
System.assert(oppFields.containsKey('closedate'), 'CloseDate required');
System.assert(oppFields.containsKey('stagename'), 'StageName required');
System.assert(oppFields.containsKey('accountid'), 'AccountId required');
}
A user-context test that runs the actual query as a user with restricted permissions to confirm FLS doesn't break the code:
@isTest
static void revenue_export_runsAsBusinessUser() {
User bu = TestDataFactory.createBusinessUser();
System.runAs(bu) {
Test.startTest();
OpportunityRevenueExport.exportToday();
Test.stopTest();
}
}
Related errors
The field-failure family has cousins worth recognizing:
INVALID_FIELD: No such column 'X' on entity 'Y'(the topic of this page)INVALID_TYPE: sObject type 'Y' is not supported(the object itself is missing, not the field)MALFORMED_QUERY: <syntax error>(the SOQL is syntactically invalid, not just a bad field)INSUFFICIENT_ACCESS_OR_READONLY(field exists, user lacks edit access at write time)NO_APPLICABLE_RECORD_TYPE_FOUND(a related issue with record types and FLS)
Each has a different remediation. The field family is specifically about SOQL parse-time field resolution.
The SOQL Builder and IDE help
Modern Salesforce IDEs (the official VS Code extension, Illuminated Cloud, Welkin Suite) include SOQL syntax validation. They parse your SOQL against the org's live describe and flag invalid field references at write time. Adopting one of these eliminates an entire class of INVALID_FIELD bugs.
For browser-based work, the Developer Console's "SOQL Query Editor" runs queries directly against the org and shows errors immediately. Use it as a sanity check before pasting SOQL into production code.
Cross-version compatibility
Salesforce occasionally renames standard fields or changes their availability across API versions. Code written against API v40.0 might reference a field that v60.0 removed.
The Apex @RestResource and SOAP API endpoints carry the API version in the URL or header. The same SOQL can succeed at one version and fail at another.
For cross-version code, check the field's availability in the Object Reference for both your minimum and maximum supported versions. Document the version range in the class comment.
The hidden-field gotcha
Salesforce has a category of fields that exist but aren't accessible via standard SOQL: certain audit fields, certain framework-internal fields, certain fields gated by features that aren't activated. These can show up in describes but fail in SOQL.
Examples:
RecordTypeIdon objects without record types enabled.PersonContactIdand related fields on Account without Person Accounts enabled.- Various Compliance Categorization fields without that feature enabled.
If a field name looks standard but throws INVALID_FIELD, check whether the underlying feature is enabled for your org.
Habits that prevent this
Three habits eliminate most field-resolution failures:
Validate SOQL against the live org schema as part of CI. A simple Apex test that runs every documented query against the sandbox catches missing fields before deploy. The test doesn't need to assert on the returned data; just running the query confirms the SOQL is parseable in the target org. A test that loops through a catalog of "every SOQL string my service emits" gives you broad coverage cheaply.
Use IDE tooling that parses SOQL against the org. The feedback loop is seconds instead of hours, and the parser flags the same issues the platform would flag at deploy time. The official Salesforce VS Code extension does this well; the Developer Console's SOQL editor is the in-browser alternative for cases where you don't have local tooling installed.
Document field dependencies in your code. A class comment listing every field the class touches lets future readers (including yourself in six months) understand the metadata footprint at a glance. When an admin asks "can I rename this field safely?", a quick grep of your codebase plus the documented dependency list gives a confident answer in minutes rather than requiring a full deploy validation run.
Further reading from Salesforce
Related dictionary terms
Share this fix
Related SOQL errors
INVALID_FIELD: <field> is not filterable
SOQLYou used a field in `WHERE`, `ORDER BY`, or `GROUP BY` that the platform refuses to filter or sort on — almost always a Long Text Area, Rich…
INVALID_FIELD: NULL_FOR_NON_REFERENCE_FIELD: <field> can not be null
SOQLYou assigned `null` to a field type that doesn't accept null — usually a Boolean (which expects `true` or `false`, not null) or a primitive …
INVALID_SEARCH: search term must be longer than one character
SOQLA SOSL `FIND` expression had a search term shorter than two characters, or used wildcards in a way the parser rejects. SOSL has stricter rul…
MALFORMED_QUERY: unexpected token
SOQLThe SOQL parser couldn't make sense of your query. The error message tells you the exact word it choked on — that's where the syntax broke. …
OPERATION_TOO_LARGE: Aggregate query has too many rows for direct assignment, use FOR loop
SOQLAn aggregate or relationship query returned more rows than Apex will assign to a `List<AggregateResult>` variable in one go. The platform te…