Salesforce error reference

field 'recordname' is not marked queryable

"Field is not marked queryable" — what it means and how to fix it

Salesforce returns this error when your SOQL or Tooling API request selects a field whose IsQueryable metadata flag is false. The field exists on the object — it just can't be returned in a query. Here are the three cases that produce it and the concrete fix for each.

Quick answer

Stop selecting the offending field. For dynamic SOQL, gate every selected field through Schema.DescribeFieldResult.isQueryable(). For Tooling API queries, replace Name/RecordName with the queryable equivalent — usually DeveloperName, MasterLabel, or QualifiedApiName.

What "is not marked queryable" actually means

Every field in Salesforce has a set of metadata flags describing what you're allowed to do with it. One of those flags is IsQueryable on the field's DescribeFieldResult. When it's false, Salesforce will refuse the query — even if your user has full read access — because the field is internal, computed outside the query engine, or only exposed through the describe layer.

RecordName is the specific case most developers hit. It appears as a field on several metadata-flavoured objects (the Tooling API surface, history tables, certain relationship lookups), but you cannot SELECT RecordName directly. Salesforce expects you to ask for the underlying record's name via a relationship traversal, or to use the queryable equivalent for that object.

The three causes — and the fix for each

1. Selecting RecordName from a history or share table

History tables (AccountHistory, custom __History) and share tables expose the parent record via a polymorphic relationship, not a direct field. Selecting RecordName trips the error. Traverse the relationship instead:

apex
// Fails: field 'recordname' is not marked queryable
List<AccountHistory> bad = [SELECT Id, RecordName FROM AccountHistory];

// Works: traverse the parent relationship
List<AccountHistory> good = [
  SELECT Id, Account.Name, Field, OldValue, NewValue
  FROM AccountHistory
];

2. Tooling API queries on metadata objects

Objects like CustomField, EntityDefinition, and ApexClass expose Name only in describe results — not via SOQL. Use the queryable surrogate:

  • DeveloperName — the API name without the namespace prefix.
  • MasterLabel — the display label, regardless of user locale.
  • QualifiedApiName — fully qualified name including namespace.
apex
// Fails on Tooling API
[SELECT Id, RecordName FROM CustomField WHERE TableEnumOrId = 'Account']

// Works
[SELECT Id, DeveloperName, QualifiedApiName, TableEnumOrId
   FROM CustomField
  WHERE TableEnumOrId = 'Account']

3. Dynamic SOQL built from Schema.getGlobalDescribe()

The most insidious case. You loop over every field in a describe, build a comma-separated select list, and a single non-queryable field (compound address fields, encrypted fields, external lookup fields, formula reference fields) takes the entire query down. Filter before you concatenate:

apex
Schema.DescribeSObjectResult d = Account.SObjectType.getDescribe();
List<String> fields = new List<String>();

for (Schema.SObjectField f : d.fields.getMap().values()) {
  Schema.DescribeFieldResult info = f.getDescribe();
  // Guard every dynamic select. isAccessible() alone is not enough —
  // a field can be accessible-via-describe but not queryable.
  if (info.isQueryable() && info.isAccessible()) {
    fields.add(info.getName());
  }
}

String soql = 'SELECT ' + String.join(fields, ', ') + ' FROM Account LIMIT 10';
List<SObject> rows = Database.query(soql);

isQueryable() is what fixes this category, not isAccessible() or isFilterable(). They check different things; mixing them up is a common cause of "I gated my query but it still fails."

How to find the offending field fast

The error message in Apex doesn't always tell you which field is the problem when you're selecting many. Two strategies:

  • Binary-search the select list. Comment out half the fields, re-run, keep halving until you isolate the field. Brutal but fast.
  • Pre-flight with describe. Before running the query, log every field whose isQueryable() is false:
apex
for (Schema.SObjectField f : Account.SObjectType.getDescribe().fields.getMap().values()) {
  Schema.DescribeFieldResult info = f.getDescribe();
  if (!info.isQueryable()) {
    System.debug('Not queryable: ' + info.getName() + ' (' + info.getType() + ')');
  }
}

Reproduce and fix it locally — no org round-trip

Iterating on dynamic SOQL against a real org means a deploy-run-wait cycle for every edit. Nimbus runs the same Apex against an embedded PostgreSQL with your project's schema loaded, so you can rebuild the query, re-run the describe loop, and check isQueryable() output in under a second. The fix lands before you ever push to the org.

bash
# In your SFDX project
nimbus test "MyDynamicQueryTest.*"

# Or run anonymous Apex with the describe loop above
nimbus run-anonymous scripts/check-queryable.apex