Compound Field Patterns
Activate when working with Salesforce compound fields — Name, Address, and Geolocation — in SOQL, Apex DML, LWC, or reports. Compound fields expose a single logical field (the compound) and N component fields (the parts). SOQL rules, DML behavior, and reporting differ in ways that trip up both humans and LLMs.
Before Starting
- Know the three compound field types. Name (FirstName/LastName/Salutation), Address (Street/City/State/PostalCode/Country/Latitude/Longitude), Geolocation (Latitude/Longitude).
- Compound in SELECT works; compound in WHERE does not. You can
SELECT MailingAddress FROM Contact but not WHERE MailingAddress = ....
- DML uses component fields.
update new Contact(Id=x, MailingCity='SF') — never assign the compound.
- The compound value exists on three surfaces only. "Compound fields are accessible only through SOAP API, REST API, and Apex. The compound versions of fields aren't accessible anywhere in the Salesforce user interface" (object_reference.txt L2913–L2914). Reports, Visualforce, formulas, Data Loader export and lookup filters all need components.
- Confirm three org flags before writing anything: State and Country/Territory Picklists (adds
-Code components), Custom Address Fields (CustomAddressFieldSettings.enableCustomAddressField, irreversible once true — api_meta.txt L113903–L113909), and Person Accounts (changes what Account.Name accepts).
Questions to Ask Before Configuring
Ask these before opening Object Manager or writing the first query; each answer changes the field names your code targets, and an assistant that skips them writes code that compiles and saves the wrong column.
| Ask | Why it matters | What a good answer adds |
|---|
| "Are State and Country/Territory Picklists enabled here — and in every sandbox on the deploy path?" | Decides whether writes target BillingState or BillingStateCode. Address.getStateCode() "returns the state code of this address if state and country/territory picklists are enabled in your organization. Otherwise, returns null" (apexrefguide.txt L199256–L199258) | The exact field list every integration, Flow, and load file must target (gotcha 1) |
| "Which surfaces read this field — Apex, a report filter, a formula, a Flow, Data Loader export?" | Only SOAP API, REST API, and Apex can see the compound value at all; every other surface needs components | The compound-vs-component decision per surface, from the matrix in references/well-architected.md (gotcha 7) |
| "Does the requirement need proximity — now, or plausibly next year?" | DISTANCE/GEOLOCATION work only against a Location-type compound. Two independent decimal fields never qualify, and converting later is a data migration plus a field-name breaking change | A Location field type chosen at creation, before any data lands |
| "Is Person Accounts enabled, and can one transaction touch both record types?" | Account.Name accepts a direct write on a business Account; on a Person Account it is "the concatenation of the FirstName, MiddleName, LastName, and Suffix of the associated person contact. You can't modify this value" (object_reference.txt L13262–L13266) | An IsPersonAccount partition in the bulk code instead of a mixed-batch failure (gotcha 3) |
| "Do we need an address on an object that has no standard one?" | Custom Address Fields is an org-wide enablement, and "Custom Address Fields can't be disabled. When enableCustomAddressField is set to true, you can't change the value to false" (api_meta.txt L113905–L113909) | A deliberate, reviewed, one-way enablement — or a decision to use five text fields instead (gotcha 8) |
| "How many custom fields are left on this object?" | "Custom geolocation fields count as three custom fields towards your organization's limits: one for latitude, one for longitude, and one for internal use" (object_reference.txt L2874–L2876) | The real field budget — see admin/data-model-documentation gotcha 7 for how to document it |
| "Is any validation rule or formula supposed to check 'the address is filled in'?" | "As of API version 20.0, validation rules can't have compound fields" (api_meta.txt L45369–L45370), and only ISBLANK, ISCHANGED, and ISNULL accept a compound at all | A component-level rule set instead of one rule that will not compile (gotcha 9) |
What a proper configuration adds over just reading and writing the field: every write lands on the column the org's flags actually make authoritative, proximity stays possible without a migration, and the rules and reports that need the address are expressed against components that exist on their surface.
Core Concepts
Name compound
Standard objects: Name is read-only compound; update FirstName, LastName, Salutation. Custom objects: Name is plain text unless defined as Person Name type.
Address compound
On Account (BillingAddress, ShippingAddress), Contact (MailingAddress, OtherAddress), Lead, User. The Address type extends Location. Components, verbatim from the Object Reference field table (object_reference.txt L2684–L2726):
| Component | Type | Contact example |
|---|
Street | textarea | MailingStreet |
City | string | MailingCity |
State | string | MailingState |
StateCode | picklist | MailingStateCode — standard addresses only when state and country/territory picklists are enabled |
Country | string | MailingCountry |
CountryCode | picklist | MailingCountryCode — always available on Custom Address Fields, picklist-gated on standard ones |
PostalCode | string | MailingPostalCode |
Latitude / Longitude | double | MailingLatitude / MailingLongitude |
Accuracy | picklist | MailingGeocodeAccuracy — standard address fields on standard objects only, populated only once geocoded |
Address compound fields are available in the SOAP and REST APIs in API version 30.0 and later.
Geolocation compound
CustomField of type Location (not "Geolocation" — that spelling is not in the FieldType enum, api_meta.txt L45760). Components are addressed by appending __Latitude__s / __Longitude__s to the field name "instead of the usual '__c'" (object_reference.txt L2915–L2917). Geolocation compound fields are available in the SOAP and REST APIs in API version 26.0 and later; Location is not an available custom field type on external objects (api_meta.txt L43233–L43245).
SOQL rules
-- Works: the compound in SELECT, through SOAP API, REST API, or Apex
SELECT Name, BillingAddress FROM Account
-- Fails: "Address fields can't be used in WHERE statements in SOQL"
SELECT Id FROM Account WHERE BillingAddress = :addr
-- Works: components
SELECT Id FROM Account WHERE BillingCity = 'SF' AND BillingStateCode = 'CA'
-- Works: the address compound used as a *location*, not as a filter value
SELECT Id, Name, BillingAddress FROM Account
WHERE DISTANCE(BillingAddress, GEOLOCATION(37.775,-122.418), 'mi') < 20
ORDER BY DISTANCE(BillingAddress, GEOLOCATION(37.775,-122.418), 'mi')
LIMIT 10
The last query is the guide's own sample (object_reference.txt L2836–L2841) and is the reason "no compound in WHERE" needs the qualifier: the compound is not filterable, but it is usable as a location operand inside DISTANCE. DescribeFieldResult.isFilterable() "erroneously returns true for address fields" (object_reference.txt L2950–L2951), so do not gate code on it.
Apex DML
// Works — components
update new Contact(Id = cid, MailingCity = 'SF');
// Fails — "Compound fields are read-only. To update field values, modify the individual field components."
update new Contact(Id = cid, MailingAddress = new Address(...));
// Reading: assign the parent to a typed variable first. Dot notation
// straight through the parent field does not compile.
Address addr = acct.BillingAddress; // System.Address
String city = addr.city; // == addr.getCity()
Location loc = acct.MyLocation__c; // System.Location
Double lat = loc.latitude;
// Writing a geolocation: the __Latitude__s / __Longitude__s components
w.Warehouse_Location__Latitude__s = 37.775;
w.Warehouse_Location__Longitude__s = -122.418;
Address and Location are also standard object names. In Apex, "always use Schema.Address instead of Address" for the object and System.Address for the compound field type; the same disambiguation applies to System.Location vs Schema.Location (apexrefguide.txt L199187–L199190, L221921–L221924).
LWC UI API
@wire(getRecord) returns compound and components; display via {v.fields.MailingAddress.displayValue} or each component individually.
Common Patterns
Pattern: Address update from form
Collect form fields, assign to component fields on a new SObject, DML. When State and Country/Territory Picklists are on, write the -Code component; see references/metadata-examples.md §5 for the load-file column list and references/examples.md Example 1 for the batch shape.
Pattern: Geolocation proximity search
DISTANCE(location1, location2, 'unit') calculates the distance between two location values; GEOLOCATION(latitude, longitude) builds a location from coordinates and must be paired with DISTANCE. DISTANCE() is supported in SELECT, WHERE, and ORDER BY clauses; GEOLOCATION() is supported in WHERE and ORDER BY only. Neither is supported in GROUP BY. Use for store locators — see gotchas.md (Gotcha 6) for the four non-obvious query constraints and examples.md Example 2 for the working Apex pattern.
Pattern: Compute distance in Apex when SOQL cannot
DISTANCE/GEOLOCATION need a query. When both points are already in memory — a user's browser coordinates against a record you already hold — System.Location exposes the same maths without a second query: Location.newInstance(lat, lon), loc1.getDistance(loc2, 'mi'), and the static Location.getDistance(a, b, unit), all documented as "an approximation of the haversine formula" (apexrefguide.txt L221955–L221962). Use these instead of hand-rolling trigonometry; use SOQL DISTANCE whenever the filtering or sorting can happen in the database.
Pattern: Serialize compound to JSON
JSON.serialize(contact.MailingAddress) returns the compound object. Consuming code should use components, not the serialized blob as a key.
Decision Guidance
| Task | Approach |
|---|
| Display full address | Select compound, render via UI API or concatenate components |
| Filter by city | Use component field (BillingCity) |
| Update name | Update FirstName/LastName, not Name |
| Proximity search | DISTANCE on Geolocation compound |
| Report with address columns | Use component columns — the compound value is not accessible in the UI at all |
| Export addresses with Data Loader | Select component fields; compound fields "cause error messages" on export |
| "Address is required" rule | Validation rule on components; compound fields are not allowed in validation rules |
| Distance rule in a workflow/approval/validation rule | DISTANCE formulas are supported there — the compound field on its own is not |
Recommended Workflow
- Fill
templates/compound-field-patterns-template.md: the compound fields in scope, the three org flags (State and Country/Territory Picklists, Custom Address Fields, Person Accounts), and every surface that reads or writes the field. Answer the Questions table above into it.
- Resolve compound-vs-component per surface against the matrix in
references/well-architected.md → Architectural Tradeoffs. Anything outside SOAP API, REST API, and Apex resolves to components.
- If a new field is needed, take the XML from
references/metadata-examples.md §1 (Location field), §2 (CustomAddressFieldSettings) or §3 (AddressSettings) — not from admin/custom-field-creation, which covers the non-compound CustomField elements.
- Write the SOQL, the Apex reads and writes, and the Data Loader column mapping from §4–§6 of the same file. Keep the location field first in every
DISTANCE call and the unit a literal.
- Run
python3 scripts/check_compound_field_patterns.py --manifest-dir force-app/main/default. ERROR findings (compound in WHERE, compound DML assignment, DISTANCE without a unit) block the deploy; WARN and INFO findings need a written reason.
- Deploy with
sf project deploy start --dry-run first, then verify with the describe call and the SOQL in §8–§9 — confirm MailingStateCode is populated, not just MailingState.
- Walk
references/gotchas.md against the change before sign-off; each gotcha names the surface it breaks and the org flag that triggers it.
Review Checklist
Salesforce-Specific Gotchas
- State & Country Picklists change components. Adds
BillingStateCode / BillingCountryCode alongside text versions; DML requires the code if picklist is enabled.
Name on standard objects cannot be DML-assigned. Only on custom objects (where it's a plain text field anyway).
- Serialized compound in JSON integrations is not round-trippable. Always map to components explicitly.
isFilterable() lies about address fields. The describe result reports true; SOQL rejects the filter. Do not build dynamic query builders on that flag.
- A custom geolocation field is three fields against the org limit, one in the UI, and absent from Schema Builder and dashboards.
The full set with reproduction and avoidance steps is in references/gotchas.md.
Output Artifacts
| Artifact | Description |
|---|
| Compound-access cheat sheet | Context × field-type matrix |
| DML update template | Component-assignment patterns |
| Geolocation query library | DISTANCE patterns |
Reference Files
| File | Read it when |
|---|
references/metadata-examples.md | You are writing deployable metadata or queries: the Location CustomField XML with scale and displayLocationInDecimal, CustomAddressFieldSettings, AddressSettings, the SOQL set (compound, components, DISTANCE, GEOLOCATION), the Apex read/write patterns, the Data Loader column mapping, package.xml, retrieve/deploy, and the verification describe |
references/gotchas.md | The field saved and something downstream is wrong — a null -Code, an empty report column, a query the parser refuses, a describe flag that lies, or a formula that will not compile. Eleven behaviours with guide line citations |
references/examples.md | You want the scenarios end to end: a 30,000-record address batch under State and Country Picklists, a store-locator proximity query, and the compound-in-a-filter anti-pattern |
references/well-architected.md | You are justifying the design — the pillar mapping, the surface-by-surface compound-vs-component matrix, the three field-shape tradeoffs, and the sources every claim rests on |
references/llm-anti-patterns.md | You are reviewing AI-generated compound-field code and need the six failure modes with detection hints |
templates/compound-field-patterns-template.md | Workflow step 1 — the org-flag and surface inventory |
scripts/check_compound_field_patterns.py | Workflow step 5, before every deploy that touches an address, name, or geolocation field |
Related Skills
admin/custom-field-creation — the non-compound CustomField elements, the FieldType enum, and general field design; it owns the CustomField XML this skill only extends
admin/data-model-documentation — how to document a compound field that counts as three against the limit (its gotcha 7)
admin/validation-rules — why ISBLANK(BillingAddress) will not save, and the component-level rules to write instead (its "Compound Fields Cannot Be Used in a Validation Rule at All")
admin/formula-fields — formula syntax and which field types are addressable
data/soql-query-optimization — selectivity and index behaviour for the component filters this skill sends you to
apex/soql-fundamentals — SOQL query patterns