3.3 Schema Design Impact on Apex

Key Takeaways

  • Apex and SOQL bind to API names; custom fields/objects use __c and relationship navigation uses __r with configured child relationship names
  • Schema changes—renames, type changes, deletes—break static Apex references at compile or runtime and can invalidate dynamic queries
  • Formula and roll-up fields are read-only in Apex DML; null handling differs for lookups, booleans, and unset fields
  • Governor-friendly schema includes selective filters, indexed External ID/Unique fields, and avoiding chatty parent-child patterns
  • Prefer stable API names and package-safe references; dynamic Schema describe APIs trade safety for flexibility
Last updated: August 2026

3.3 Schema Design Impact on Apex

Quick Answer: Apex does not use field labels—it binds to API names. Custom attributes end in __c, parent navigation uses __r, and child subqueries use the child relationship name. Schema design choices determine null behavior, whether fields are writable, how SOQL must be written, and whether code survives package installs and renames.

Platform Developer I expects you to connect declarative schema decisions to programmatic consequences. A “correct” ERD that ignores Apex will still fail in production under governors or deployments.

API Names Are the Contract

Schema elementTypical API patternApex / SOQL usage
Custom objectInvoice__cList<Invoice__c> rows = ...
Custom fieldStatus__cinv.Status__c = 'Open'
Custom lookup valueAccount__cHolds the parent Id
Parent navigationAccount__rinv.Account__r.Name
Child relationshipInvoice_Lines__rSubquery or related list API name

Labels can change for localization or clarity without breaking code. API names should be treated as permanent public contracts. Renaming Status__c to Invoice_Status__c forces updates to Apex classes, triggers, flows, validation formulas, reports, and integrations.

Standard objects and fields omit __c (Account, Name, CloseDate). Standard relationships use names like Account / Accounts depending on direction and context—learn the describe or Object Manager “Child Relationship Name” rather than guessing.

Relationship Names in SOQL and Apex

Child-to-parent (dot navigation via __r):

for (Invoice_Line__c line : [
    SELECT Id, Amount__c, Invoice__r.Name, Invoice__r.Account__r.Name
    FROM Invoice_Line__c
    WHERE Invoice__c = :invoiceId
]) {
    String acctName = line.Invoice__r.Account__r.Name;
}

Parent-to-child (subquery uses relationship name, not object API name):

Invoice__c inv = [
    SELECT Id, Name,
        (SELECT Id, Amount__c FROM Invoice_Lines__r)
    FROM Invoice__c
    WHERE Id = :invoiceId
];

If the child relationship name was set poorly (or left as a non-obvious default), every subquery breaks. Relationship design is therefore an Apex design decision, not only a UI concern.

Master-detail vs lookup does not change __c/__r syntax, but it changes what is safe to assume: detail records always have a parent Id in master-detail; lookup parents may be null.

Nulls, Defaults, and Booleans

  • Unset lookup fields are null—always null-check before parent.Name style navigation on in-memory records not loaded via SOQL relationships
  • Checkbox fields in Apex are Boolean and often false when unchecked rather than null, but code should still be explicit
  • Number/Currency unset values may be null; arithmetic without null guards causes exceptions
  • SOQL does not return fields you did not select; touching them yields null, not an automatic lazy load (no transparent ORM load)

Schema defaults (checkbox default true, formula defaults, auto-number) affect insert behavior differently in UI vs Apex—tests should set fields explicitly when behavior depends on them.

Formula and Roll-Up Fields in Apex

Formula fields and roll-up summary fields are calculated by the platform:

  • Readable in SOQL and Apex after query (and after refresh from DB)
  • Not writable via DML—assignments will not persist and can cause issues if you try to treat them as inputs
  • Formula results may be stale in-memory after related DML until re-queried
  • Some formula types are non-sortable/non-filterable or expensive; filtering on non-deterministic or cross-object formulas can hurt performance

Design implication: store source data in writable fields; expose calculations as formulas when appropriate; use Apex only when formulas/roll-ups cannot express the rule.

Governor-Friendly Schema

Schema shapes how easy it is to stay under governor limits:

  • Selective filters: Prefer indexed paths—Id, lookups, External ID, Unique, and some standard indexed fields—over leading wildcards on long text
  • Avoid unbounded parent-child explosion: Deep subquery trees and querying unnecessary relationships burn heap and rows
  • Roll-ups vs Apex aggregates: Native roll-ups push work to the platform; custom aggregates in triggers need bulkification
  • Cascade deletes and implicit updates: Master-detail cascades can fire large trigger storms—design trigger handlers for bulk
  • Record types: Branching logic on RecordTypeId should use maps of developer names from a single describe/query, not per-record SOQL

Poor schema (everything Long Text Area, no External IDs for integration, M:N faked as text) forces Apex into inefficient scans and string parsing.

Static vs Dynamic References

Static references (concrete Invoice__c, Status__c in code):

  • Compile-time checking—deploy fails if the field is missing
  • Easier to read and refactor with IDE tooling
  • Required for most application business logic

Dynamic references (Schema.SObjectType, getDescribe(), sObject.put/get, dynamic SOQL):

  • Flexible for generic libraries, package utilities, and field-set driven UI
  • Fail at runtime if names are wrong
  • Dynamic SOQL must still guard against SOQL injection (bind variables or escape single quotes)
  • Slightly more verbose and easier to get wrong under exam time pressure
Schema.DescribeFieldResult d = Invoice__c.Status__c.getDescribe();
Boolean updateable = d.isUpdateable();

Field-level security and CRUD checks use describe APIs—schema design that creates many similar custom objects may push you toward generic dynamic patterns; still enforce security explicitly (WITH SECURITY_ENFORCED, Security.stripInaccessible, or describe checks) as covered in security topics.

Packages, Unlocked Packages, and API Name Stability

In packaging contexts:

  • Namespaced components appear as namespace__Object__c / namespace__Field__c
  • Subscriber orgs cannot always rename packaged API names freely
  • Changing a packaged field type or deleting a field is a breaking change for subscriber Apex and integrations

Even outside ISV packages, treat API names as versioned public APIs. Prefer additive changes (new fields) over renames/type changes. Use External IDs for integration keys so external systems do not depend on Salesforce Ids alone.

Practical Schema → Apex Checklist

  1. Freeze API names and child relationship names early
  2. Decide master-detail vs lookup before writing triggers (nulls, cascade, roll-ups)
  3. Mark integration keys External ID + Unique for upsert-friendly Apex and loaders
  4. Keep formulas read-only in design docs so developers do not DML them
  5. Model M:N with junction objects so SOQL stays relational instead of parsing multi-selects
  6. Prefer static typed Apex for business logic; use dynamic describe for generic frameworks

Schema is not “admin work” separate from development—on the Lightning Platform, schema is part of the application’s type system.

Test Your Knowledge

In Apex, which expression correctly navigates from a custom child record to a field on its custom parent via a lookup named Account__c?

A
B
C
D
Test Your Knowledge

Why is renaming a custom field’s API name after Apex is deployed considered high risk?

A
B
C
D
Test Your Knowledge

A developer tries to set a formula field value in Apex before insert. What is the correct expectation?

A
B
C
D