10.3 Programmatic Schema Access (Describe)

Key Takeaways

  • Schema.sObjectType and describe APIs expose object and field metadata at runtime—labels, API names, type, and permission flags
  • fields.getMap() returns field API name to SObjectField tokens for dynamic validation and UI building
  • Use isAccessible, isCreateable, isUpdateable, isDeletable (object and field) before dynamic SOQL/DML in security-sensitive paths
  • Static tokens (Account.Name, Account.SObjectType) are compile-time safe; describe is for dynamic, data-driven, or multi-object code
  • Describe results can be cached in a transaction to avoid redundant describe calls and governor pressure
Last updated: August 2026

10.3 Programmatic Schema Access (Describe)

Quick Answer: Apex Schema describe APIs let you inspect objects and fields at runtime: names, types, and CRUD/FLS flags. Use static tokens when the shape is known at compile time; use describe + getMap for dynamic SOQL/UI and permission checks. Cache describe results within a transaction when called repeatedly.

Dynamic apps—metadata-driven UIs, generic utilities, managed packages that adapt to customer fields—cannot hard-code every field. Platform Developer I tests whether you can read schema safely and when describe beats static references.

Schema Entry Points

Schema.sObjectType (common, readable)

Schema.DescribeSObjectResult accountDescribe =
    Schema.SObjectType.Account;

// Or from a token:
Schema.DescribeSObjectResult d =
    Account.SObjectType.getDescribe();

Useful object-level facts:

String apiName = accountDescribe.getName();      // Account
String label = accountDescribe.getLabel();        // Account
Boolean canRead = accountDescribe.isAccessible();
Boolean canCreate = accountDescribe.isCreateable();
Boolean canUpdate = accountDescribe.isUpdateable();
Boolean canDelete = accountDescribe.isDeletable();
Boolean isCustom = accountDescribe.isCustom();

Schema.describeSObjects

When you have API names as strings (package config, custom metadata):

List<Schema.DescribeSObjectResult> describes =
    Schema.describeSObjects(new String[]{ 'Account', 'Contact', 'My_Object__c' });

for (Schema.DescribeSObjectResult r : describes) {
    System.debug(r.getName() + ' createable=' + r.isCreateable());
}

This bulk describe is preferable to many one-off describes when processing a list of object names.

From an sObject instance

Account a = new Account(Name = 'Acme');
Schema.DescribeSObjectResult d = a.getSObjectType().getDescribe();

Generic code that accepts SObject often uses getSObjectType() so one utility works for many types.

Field Describes and getMap

Map<String, Schema.SObjectField> fieldMap =
    Schema.SObjectType.Account.fields.getMap();

// Keys are field API names (case-insensitive map behavior—still use correct API names)
Schema.SObjectField revenueToken = fieldMap.get('AnnualRevenue');
Schema.DescribeFieldResult revenueDescribe = revenueToken.getDescribe();

Schema.DescribeFieldResult essentials

String fieldLabel = revenueDescribe.getLabel();
Schema.DisplayType dtype = revenueDescribe.getType(); // CURRENCY, STRING, ...
Boolean readable = revenueDescribe.isAccessible();
Boolean createable = revenueDescribe.isCreateable();
Boolean updateable = revenueDescribe.isUpdateable();
Boolean nillable = revenueDescribe.isNillable();
Boolean calculated = revenueDescribe.isCalculated(); // formula, etc.
Integer length = revenueDescribe.getLength();

Permission flags for the exam:

MethodMeaning
isAccessible()Running user can read the field (FLS read)
isCreateable()User can set the field on insert
isUpdateable()User can edit the field on update
Object isDeletable()User can delete records of that type

These checks power manual CRUD/FLS enforcement when you are not using USER_MODE / stripInaccessible.

public static void safeUpdateRevenue(Account acc, Decimal value) {
    if (!Schema.sObjectType.Account.isUpdateable()) {
        throw new AuraHandledException('Cannot update Account');
    }
    if (!Schema.sObjectType.Account.fields.AnnualRevenue.isUpdateable()) {
        throw new AuraHandledException('Cannot update Annual Revenue');
    }
    acc.AnnualRevenue = value;
    update acc;
}

Note the fluent forms Schema.sObjectType.Account.fields.AnnualRevenue.isUpdateable() versus map-based dynamic names—both appear on exams.

Dynamic SOQL with Describe

Dynamic SOQL is powerful and dangerous. Pattern: validate identifiers via describe, then bind values.

public static List<SObject> queryReadableFields(
    String objectApiName,
    List<String> requestedFields,
    String nameFilter
) {
    Map<String, Schema.SObjectType> globalDescribe = Schema.getGlobalDescribe();
    if (!globalDescribe.containsKey(objectApiName)) {
        throw new IllegalArgumentException('Unknown object');
    }

    Schema.DescribeSObjectResult objDescribe =
        globalDescribe.get(objectApiName).getDescribe();
    if (!objDescribe.isAccessible()) {
        throw new SecurityException('Object not accessible');
    }

    Map<String, Schema.SObjectField> fields = objDescribe.fields.getMap();
    List<String> safeFields = new List<String>();
    for (String f : requestedFields) {
        if (!fields.containsKey(f)) {
            continue; // or throw
        }
        Schema.DescribeFieldResult fd = fields.get(f).getDescribe();
        if (fd.isAccessible()) {
            safeFields.add(fd.getName());
        }
    }
    if (safeFields.isEmpty()) {
        return new List<SObject>();
    }

    String soql =
        'SELECT ' + String.join(safeFields, ',') +
        ' FROM ' + objDescribe.getName() +
        ' WHERE Name = :nameFilter LIMIT 50';
    return Database.query(soql);
}

Security stack in this pattern:

  1. Object exists (global describe / whitelist)
  2. Object isAccessible
  3. Each field exists and isAccessible
  4. Values use binds (:nameFilter), not concatenation

Never concatenate free-form object or field names from the UI without describe validation.

Static Tokens vs Describe

ApproachExampleStrengthsWhen
Static field/object tokensAccount.Name, Account.SObjectTypeCompile-time validation; rename-safe in many IDEs; clearKnown schema in first-party code
Describe / getMapfields.getMap().get(apiName)Works with strings, custom metadata, multi-tenant variabilityDynamic UIs, generic libraries, optional fields
USER_MODE SOQLWITH USER_MODEPlatform enforces FLS/CRUDPrefer for straightforward queries when dynamic field lists are not required
// Static — preferred when you know the field
Decimal rev = (Decimal)a.get(Account.AnnualRevenue);

// Dynamic — when field API name arrives as data
String fieldName = 'AnnualRevenue';
a.get(fieldName);

If the exam asks for the safest approach for fixed Account.Name access, static references (or ordinary static SOQL) win. If the exam asks how to support admin-configured field sets, describe + field sets / getMap is correct.

Field sets (related idea)

Field sets are a declarative way to group fields; Apex can iterate field set members and still describe each for permissions. You do not need full field-set syntax memorized, but know describe underpins dynamic field iteration.

Caching Describe Results

Describe calls are lighter than they once were, but repeated global describes or field-map retrieval in tight loops still waste CPU and can contribute to limit pressure in bulk contexts.

Transaction-level caching pattern:

public class SchemaCache {
    private static Map<String, Schema.DescribeSObjectResult> objectCache =
        new Map<String, Schema.DescribeSObjectResult>();

    public static Schema.DescribeSObjectResult getObjectDescribe(String apiName) {
        if (!objectCache.containsKey(apiName)) {
            Schema.SObjectType t = Schema.getGlobalDescribe().get(apiName);
            if (t == null) {
                return null;
            }
            objectCache.put(apiName, t.getDescribe());
        }
        return objectCache.get(apiName);
    }
}

Considerations:

  • Static caches last for the transaction (one request / one async execution)—good for bulk triggers that check the same object many times
  • Do not assume static caches survive across separate transactions or hold forever like a custom object
  • Prefer caching only what you need (specific objects) rather than always calling getGlobalDescribe() if a tighter API suffices
  • getGlobalDescribe() returns a large map—cache thoughtfully when used repeatedly

Modern Apex also offers describe options (for example, deferred/token styles in newer APIs) that reduce cost—exam focus remains: don’t re-describe blindly in loops, do check permissions, do prefer static tokens when schema is fixed.

Combining Describe with stripInaccessible and USER_MODE

Describe checks are one enforcement style. Alternatives:

// Platform-enforced read
List<Account> rows = [
    SELECT Id, Name, AnnualRevenue FROM Account WITH USER_MODE LIMIT 10
];

// Or strip after system-mode query
SObjectAccessDecision d = Security.stripInaccessible(
    AccessType.READABLE,
    rows
);

Use describe when you need branching logic (hide a component, choose a different field, build a dynamic SELECT list). Use USER_MODE/stripInaccessible when you simply need the platform to enforce access on a known operation.

Common Exam Traps

  1. Assuming describe alone runs the query securely — you must use isAccessible results before query/DML.
  2. Dynamic SOQL with concatenated user input — describe the field list, still bind filter values.
  3. Confusing object isCreateable with field isCreateable — both can matter for an insert of a populated field.
  4. Using without sharing to “fix” FLS — sharing ≠ FLS.
  5. Calling getGlobalDescribe in a per-record loop — cache or restructure.

Practical Mini-Patterns

Guard a dynamic update map:

public static void applyUpdates(SObject record, Map<String, Object> values) {
    Schema.DescribeSObjectResult od = record.getSObjectType().getDescribe();
    if (!od.isUpdateable()) {
        return;
    }
    Map<String, Schema.SObjectField> fmap = od.fields.getMap();
    for (String key : values.keySet()) {
        if (fmap.containsKey(key) && fmap.get(key).getDescribe().isUpdateable()) {
            record.put(key, values.get(key));
        }
    }
    update record;
}

Discover custom fields only:

for (Schema.SObjectField f : Schema.SObjectType.Account.fields.getMap().values()) {
    Schema.DescribeFieldResult fd = f.getDescribe();
    if (fd.isCustom() && fd.isAccessible()) {
        System.debug(fd.getName());
    }
}

Summary for Platform Developer I

Schema describe is the reflection layer of Salesforce data: SObjectType, describeSObjects, fields.getMap, and DescribeFieldResult expose structure and isAccessible / isCreateable / isUpdateable / isDeletable. Pair describe with bind variables for safe dynamic SOQL, prefer static tokens when the model is fixed, and cache describe results across bulk iterations. Together with sharing keywords and USER_MODE/stripInaccessible, describe completes the security and metadata toolkit tested on the exam.

Test Your Knowledge

A utility must accept an object API name as a String and return whether the running user can create records of that type. Which approach fits?

A
B
C
D
Test Your Knowledge

Why is Schema.SObjectType.Account.fields.getMap() commonly used in dynamic Apex?

A
B
C
D
Test Your Knowledge

When should a developer prefer static tokens such as Account.Name over dynamic describe-based field access?

A
B
C
D