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
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:
| Method | Meaning |
|---|---|
| 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:
- Object exists (global describe / whitelist)
- Object isAccessible
- Each field exists and isAccessible
- Values use binds (
:nameFilter), not concatenation
Never concatenate free-form object or field names from the UI without describe validation.
Static Tokens vs Describe
| Approach | Example | Strengths | When |
|---|---|---|---|
| Static field/object tokens | Account.Name, Account.SObjectType | Compile-time validation; rename-safe in many IDEs; clear | Known schema in first-party code |
| Describe / getMap | fields.getMap().get(apiName) | Works with strings, custom metadata, multi-tenant variability | Dynamic UIs, generic libraries, optional fields |
| USER_MODE SOQL | WITH USER_MODE | Platform enforces FLS/CRUD | Prefer 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
- Assuming describe alone runs the query securely — you must use isAccessible results before query/DML.
- Dynamic SOQL with concatenated user input — describe the field list, still bind filter values.
- Confusing object isCreateable with field isCreateable — both can matter for an insert of a populated field.
- Using without sharing to “fix” FLS — sharing ≠ FLS.
- 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.
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?
Why is Schema.SObjectType.Account.fields.getMap() commonly used in dynamic Apex?
When should a developer prefer static tokens such as Account.Name over dynamic describe-based field access?