A B2C Commerce site needs to track loyalty tier on the customer, special handling instructions on the order, and a catalog of gift card configurations independent of any specific transaction. Each piece of data belongs somewhere. The question is whether to extend an existing object with custom attributes or create a new custom object entirely.
The choice is permanent for practical purposes. Migrating data from custom attributes to custom objects (or vice versa) requires data export, schema rebuild, and re-import. On a site with millions of records, that is a multi-week project.
Sapota's Salesforce team treats custom attribute vs custom object decisions as schema architecture, not as configuration. The wrong choice produces queries that do not perform, integrations that have to flatten or unflatten data, and migrations that age into "we should probably fix this someday" tech debt.
What each one is
Custom attribute. A new field added to an existing object (Customer, Order, Product, etc.). The field has a type (string, integer, date, set-of-string, etc.) and is queryable by the standard SFCC search APIs on that object.
Customer:
- firstName (standard)
- lastName (standard)
- email (standard)
- loyaltyTier (custom attribute, string)
- lastEngagementDate (custom attribute, date)
Custom object. A new object type with its own attributes, lifecycle, and queries. Records are managed independently via the dw.object.CustomObjectMgr API.
GiftCardConfiguration (custom object):
- configCode (string, primary key)
- amount (decimal)
- validityDays (integer)
- termsAndConditions (string)
The two patterns address different needs. Custom attributes extend an existing object's record. Custom objects represent something that does not fit on any existing object.
When to use a custom attribute
Five scenarios:
1. Per-record property of an existing entity. Loyalty tier is a property of the customer. Special instructions are a property of the order. Lead time is a property of the product. Use custom attributes on the parent object.
2. Cardinality matches one-to-one with the existing object. Each customer has exactly one loyalty tier. Each order has exactly one set of shipping notes. One-to-one mapping fits a custom attribute.
3. Data is read in the same context as the parent. Every order detail page shows the order. If the page also shows the shipping notes, those notes are read alongside the order data, no separate query. Custom attribute.
4. Data lifecycle matches the parent. The custom attribute lives and dies with the parent record. When the customer is deleted, the loyalty tier goes too. When the order is archived, the shipping notes go with it.
5. Reporting and analytics rely on the parent. Customer attributes flow into customer reports, segments, and analytics naturally. Order attributes flow into order reports.
Custom attributes are the default for extending the platform. Most schema changes should be custom attributes.
When to use a custom object
Three scenarios:
1. Many-to-something cardinality. A customer can have many wishlists with names and configurations. An order can have many fulfillment events. The "many" side does not fit on a single attribute; it needs its own records. Custom object.
2. Independent lifecycle. Gift card configurations exist before any customer uses them. They are not tied to a specific customer or order. They have their own create-update-delete lifecycle managed by the merchandising team. Custom object.
3. Cross-cutting reference data. Configuration entities (country-specific tax rules, shipping carrier definitions, promotion templates) shared across customers and orders. Independent existence. Custom object.
Use custom objects when the data does not fit on any existing object's record.
Query performance
Custom attributes inherit the indexing and query performance of their parent object. A custom attribute on Order participates in order search; the platform indexes it for filterable queries.
Custom objects have their own indexing concerns. Searching by custom object attributes requires specific configuration:
- The custom object type has its own search index.
- Attributes used in queries should be marked as queryable in the object definition.
- Complex relationships across custom objects need careful query design.
For low-volume custom object types (a few thousand records), query performance is rarely an issue. For high-volume (millions of records), indexing strategy matters.
The rule: if the data needs to participate in product or order search filters, custom attribute on the parent is faster than a related custom object.
Integration implications
External systems integrating with B2C Commerce see custom attributes and custom objects differently:
Custom attributes via OCAPI/SCAPI. Included in the parent object's serialization. A GET /customers/{id} returns the customer with all attributes including custom ones. Integration is automatic.
Custom objects via OCAPI/SCAPI. Separate endpoints under /custom-objects/{type}. Integration code has to know to call them. Pagination and search are separate from the parent objects.
For ERP, OMS, and CRM integrations that primarily care about products, customers, and orders, custom attributes are simpler to consume. For integrations that manage reference data (catalogs, configurations), custom objects make sense.
Storage and migration considerations
Custom attributes:
- Stored inline with the parent record.
- Created via metadata import.
- Backfilling existing records requires running a job to set values.
- Removing or renaming an attribute requires care; references in code, jobs, and integrations all touch the metadata.
Custom objects:
- Stored as their own records.
- Created via metadata import with the object type definition.
- Data can be loaded independently of the parent records.
- Removing the object type removes all data; less invasive on the parent objects.
Migration cost:
- Adding a new custom attribute: low. New metadata, optional backfill job.
- Adding a new custom object: low. New metadata.
- Moving data from a custom attribute to a custom object: medium. Export, new schema, import.
- Moving data from a custom object to a custom attribute: medium-high. Data shape often needs flattening; not all records have the right cardinality.
Plan the choice carefully at design time. Mid-life migrations are mechanical but never free.
A practical decision guide
For each new data element:
- Is this a per-record property of an existing object? → Custom attribute on that object.
- Is the cardinality one-to-one with an existing object? → Custom attribute.
- Does the data lifecycle match the parent's? → Custom attribute.
- Is this data independent of any specific customer, order, or product? → Custom object.
- Does an entity have many of these (e.g., many wishlists per customer)? → Custom object.
- Is this reference data shared across the catalog? → Custom object.
When in doubt: custom attribute is the default. Custom object is the explicit choice for cases where attributes do not fit.
Common schema mistakes
Five patterns Sapota has seen in audits:
1. Custom object for what should be custom attribute. A team creates a "CustomerProfile" custom object to hold loyalty tier and engagement date. The data is per-customer, one-to-one. It belongs as attributes on Customer. The CustomerProfile object adds query complexity for no benefit.
2. Custom attribute for what should be custom object. A "wishlistItems" multi-value custom attribute on Customer storing serialized JSON. Each customer has a different number of items. Queries against individual wishlist items become parsing exercises. Use a custom object for WishlistItem with a customer reference.
3. Custom attribute name collisions. Two cartridges both define a custom attribute loyaltyTier on Customer with different meanings. Platform allows it but only one wins. Namespace your custom attributes with a prefix.
4. Custom object without indexing strategy. High-volume custom object queried frequently. No indexed attributes. Query performance degrades as data grows. Define indexes during object creation, not retrofitted.
5. Mid-life schema migration without rehearsal. A team decides to move loyalty data from attribute to object on production. Backfill job runs against millions of records. Half-completed when something fails. Data inconsistent for weeks. Rehearse on staging first.
What good schema discipline looks like
A B2C Commerce site with healthy custom data:
- Every custom attribute has a documented purpose.
- Custom object types have query indexing strategy documented.
- Attribute naming conventions (often
brand_ or custom_ prefix).
- Schema changes reviewed against query, integration, and migration implications.
- Metadata import files in source control, reviewed alongside code changes.
Schema decisions made well at design time prevent the long tail of migration projects that otherwise become inevitable. Sapota's Salesforce team treats custom attribute vs custom object as a design deliverable on every B2C Commerce engagement.
Designing or reviewing custom data schema in B2C Commerce? Sapota's Salesforce team, certified on B2C Commerce Developer (Comm-Dev-101), handles schema design, attribute conventions, and migration planning on production engagements. Get in touch ->
See our full platform services for the stack we cover.