Object & data model
How the standard objects, record types, and custom fields fit together, plus the grain rules and relationship choices that decide whether reporting ever ties out.
The worst object decision I ever inherited was a team that modeled “renewals” as a custom object hanging off Account instead of as an Opportunity with a type. It felt clean at the time: renewals are different, so give them their own home. Two years later the forecast could not see them, the pipeline report double-counted the ones a rep had also logged as an Opp, and the CRO’s ARR number reconciled to nothing because half the recurring revenue lived on an object the forecast engine never queried. Rebuilding it meant migrating three years of history and rewriting nineteen reports. The object model is the one architectural choice where a shortcut in month one costs a quarter in year two.
Most teams treat the object model as something the CRM ships with and you just fill in. It is closer to a load-bearing wall. Every automation, every rollup, every report reads the grain you chose, and the grain is nearly impossible to change once records and integrations depend on it. Get the objects and relationships right and the rest of the system has somewhere solid to stand. Get them wrong and you spend years papering over it with flows and formula fields that each add their own drift.
Standard objects first, custom objects only when forced
The instinct to build a custom object is almost always premature. Salesforce ships Lead, Account, Contact, Opportunity, and the junction objects (Opportunity Contact Role, Opportunity Line Item) that carry the vast majority of go-to-market process. Before you create anything custom, prove that no standard object fits, because the platform gives standard objects things custom objects have to earn: forecast integration, the price book relationship, native activity timelines, and a decade of reports built to expect them.
A custom object earns its place when it has a genuinely different grain and lifecycle. Contract line items with their own start and end dates that do not map to an Opportunity. Product usage snapshots that arrive daily per account. A partner-deal-registration record that a partner owns and a rep does not. Those are real objects. “Renewals,” “upsells,” and “expansion” are not: they are Opportunity types, and forcing them onto a custom object is how you amputate them from the forecast.
The grain rule: one meaning per object
Every object answers exactly one question about the world, and that question is its grain. Account answers “what do we know about this company.” Contact answers “what do we know about this person.” Opportunity answers “what is the state of this deal.” A field belongs on the object whose grain it describes. Company revenue and employee count describe the company, so they live on Account. Deal amount and close date describe the deal, so Opportunity. The test when you are stuck: if this value changes, how many rows should change? One row means you found the object.
The failure mode is mixing grains on one object to save a join. Stamping account_industry onto every Opportunity so a pipeline report is one field shorter feels efficient until the industry gets re-enriched on the Account and the 40 stale copies on old Opps drift away from the truth inside a quarter. Store it once at its true grain and read it through the relationship. If a report genuinely needs it on the child, use a formula field or a rollup, never a static copy.
| Field | Grain | Object | How it is populated |
|---|---|---|---|
| Employee count, Industry | Company | Account | Enrichment integration |
| Amount, Close date, Stage | Deal | Opportunity | Rep, user-entered |
| Product, Quantity, List price | Line item | Opportunity Line Item | Price book + rep |
| Persona, Seniority, Title | Person | Contact | Enrichment or user |
| Buying role | Person-on-deal | Opportunity Contact Role | Rep, user-entered |
| Account ARR | Company | Account | Rollup, no direct writer |
Master-detail or lookup: the relationship choice you cannot easily undo
Two objects can relate as a lookup (loose, independent lifecycles, optional) or master-detail (tight, cascade-delete, roll-up-summary enabled, child cannot exist without parent). The choice is not cosmetic. Master-detail gives you roll-up summary fields for free, which is often the whole reason to reach for it, but it also means deleting the parent deletes every child and reparenting is restricted. Lookups are forgiving and keep the objects independent, at the cost of writing your own rollups (via flow, Apex, or a tool like Rollup Helper) when you need aggregation.
| Master-detail | Lookup | |
|---|---|---|
| Child without parent | Not allowed; child is orphaned on parent delete | Allowed; child survives independently |
| Roll-up summary fields | Native, no code | Not available; build your own |
| Delete behavior | Cascade: parent delete removes children | Independent: child remains |
| Reparenting | Restricted by default | Free |
| Best when | Line items, tightly-bound children needing aggregation | Cross-references between independently-owned records |
The trap I see most is defaulting every relationship to lookup because it feels safer, then discovering six months in that you need Account-level ARR rolled up from child records and lookups will not give it to you natively. Decide up front whether the parent needs to aggregate the children. If it does, and the child genuinely cannot exist without the parent, master-detail is the honest model.
View as table
| Stage | Value |
|---|---|
| Custom objects built | 34 |
| Map to a standard object | 19 |
| Could be a record type | 8 |
| Genuinely new grain | 7 |
Record types partition process without multiplying objects
When the same object needs different page layouts, picklist values, or processes (a New Business Opportunity versus a Renewal, a Partner Account versus a Direct Account), the answer is record types, not a new object. Record types keep the grain unified so the forecast and the pipeline report still see everything, while giving each variant its own layout and its own subset of picklist values. This is the single most under-used lever for taming a sprawling model: most “we need a new object” requests are really “we need a record type.”
- 1
1. Can a standard object hold it?
Lead, Account, Contact, Opportunity, or a junction. Standard objects get forecast integration and native reports; custom objects have to earn them.
- 2
2. Is this a new grain or a variant?
Different meaning about the world means a new object. Different process on the same meaning means a record type.
- 3
3. Name the single grain in one sentence
If you cannot say what one row means in a sentence, the object is mixing grains and reporting will not tie out.
- 4
4. Choose the relationship deliberately
Master-detail if the parent must aggregate the children and the child cannot exist alone. Lookup for independent records.
- 5
5. Place every field at its true grain
Company data on Account, deal data on Opportunity. Expose to children via formula or rollup, never a static copy.
- 6
6. Document what one record means
Write the object's definition and its lifecycle so the next builder does not model a renewal as a custom object.
The object model is the one part of the system you build once and live with for years, so it deserves more thought than any single field or flow. Prove a standard object cannot hold it before you build custom. Use record types to partition process instead of multiplying objects. Keep one grain per object so reporting ties out. Choose master-detail versus lookup for the lifecycle, not the convenience. Get those four right and every automation, rollup, and report downstream has solid ground; get them wrong and you spend years building workarounds on a foundation that was pouring itself crooked from day one.
Keep reading
All guides →CPQ & quote-to-cash
How quotes, price books, approvals, and billing connect into one revenue chain, plus the config choices that decide whether a signed deal actually invoices correctly.
ArchitectAutomation architecture
How flows, triggers, and validation rules coexist without fighting, plus the order-of-execution and consolidation rules that keep automation from eating itself.
ArchitectIntegrations & data flow
How systems sync without corrupting each other, plus the ownership, idempotency, and error-handling rules that keep a two-way integration from overwriting the truth every fifteen minutes.