GrowthSystems By Dylan Day
Architect-Level Documentation

Designing XDM Schemas for Adobe Journey Optimizer

Before you think about Field Groups or Mixins, you need a stable foundation: the XDM class and identity strategy. In Adobe Experience Platform, classes define the lifecycle of a record (profile vs event), the rules for how it is stored, and how it is stitched into the Unified Profile. Get this wrong, and no amount of clever AJO templating will save you from slow lookups, inconsistent audiences, or conflicting customer views.

How to use this XDM schema design guide

  • If you’re starting a new AEP implementation, read the class, Profile, and ExperienceEvent sections in order before you create any schemas in production.
  • If AJO journeys are slow or returning null values, review our Handlebars Debugging Guide, your identity strategy, and Field Group/Mixin hierarchy.
  • If you’re rationalising legacy schemas, focus on the Field Group & Mixin hierarchy and the Master Design Patterns Index to spot anti‑patterns.
  • Before any enterprise journey goes live, run the 10‑point XDM Schema QA as a mandatory design gate.
DD

Technical Navigation

1. The XDM Class & Identity Layer

Before you think about Field Groups or Mixins, you need a stable foundation: the XDM class and identity strategy. In Adobe Experience Platform (AEP), classes define the lifecycle of a record (profile vs event), the rules for how it is stored, and how it is stitched into the Unified Profile. Get this wrong, and no amount of clever AJO templating will save you from slow lookups, inconsistent audiences, or conflicting customer views.

Identity namespaces and primary keys

Every schema built on the Profile or ExperienceEvent class must participate in the AEP identity graph. That means you define one or more identity namespaces (email, CRM ID, ECID, loyalty ID) and mark at least one as primary so that records can be merged into a single person. Journey Optimizer leans heavily on this graph for segment evaluation, action eligibility, and frequency capping.

The architect’s job is to decide which IDs are allowed to be primary in which schemas. A sloppy identity strategy results in fragmented Profiles, duplicated journeys, and inconsistent personalisation because AJO cannot reliably see the “whole person”.

Classes as containers for Field Groups and Mixins

Once your classes and identities are set, they become containers for reusable structure: Field Groups and Mixins. Each Field Group can only attach to compatible classes. A Profile Field Group cannot be attached to an ExperienceEvent schema, and vice versa. This is how you enforce that “profile things” go on profile schemas and “event things” go on event schemas.

As an architect, you should treat classes as the top of a hierarchy: class → domain‑specific Field Groups → low‑level Mixins shared across domains. That hierarchy keeps JSON pointer paths stable and predictable for AJO, while allowing reuse across brands, channels, and schemas.

2. Designing the XDM Individual Profile Schema

Once your XDM class and identity strategy are set, the next step is to design a coherent Profile schema. This is the backbone of AJO personalisation: every attribute you want to use in segments, journeys, and message templates must eventually resolve to a stable JSON pointer on the Profile. Poor profile design leads to brittle lookups, duplicate fields, and null values showing up in production messages.

Standard XDM attributes

Adobe’s base Field Groups for the Profile class give you opinionated locations for high‑value attributes such as name, email, phone, addresses, and demographics. Leaning on these first keeps your model portable across solutions and reduces custom work later.

ADOBE XDM SCHEMA / JSON
1
{
2
  "person": {
3
    "name": {
4
      "firstName": "Alex",
5
      "lastName": "Nguyen"
6
    },
7
    "gender": "female"
8
  },
9
  "personalEmail": {
10
    "address": "user@example.com"
11
  },
12
  "homeAddress": {
13
    "country": "AU"
14
  }
15
}

Tenant‑scoped business attributes

Anything that is unique to your organisation should live under the tenant namespace. This is where you model lifecycle stages, internal segments, loyalty constructs, and product‑specific preferences that AJO will depend on.

ADOBE XDM SCHEMA / JSON
1
{
2
  "_tenant": {
3
    "lifecycle": {
4
      "stage": "Active",
5
      "riskScore": 0.87
6
    },
7
    "loyalty": {
8
      "tier": "GOLD",
9
      "pointsBalance": 12450
10
    },
11
    "marketing": {
12
      "preferredChannel": "email",
13
      "emailOptIn": true
14
    }
15
  }
16
}

Attribute placement and normalisation

The architect’s job is to decide where each attribute should live and how it should be encoded. Spreading similar concepts across multiple branches (for example, loyalty data repeated in different Field Groups for each brand) makes AJO templates fragile and slows audience evaluation because segments must check several paths to answer simple questions like “is this customer high value?”.

Favour normalised structures with well‑named objects over ad‑hoc Boolean flags. Instead of half a dozen one‑off fields such as isVip, isVipisHighSpender, and isChurnRisk, design a single lifecycle object with enumerated status and supporting scores. This makes it easier to maintain, version, and expose in AJO for both targeting and personalisation.

Class selection: Anti-pattern vs. Architected Design

✕ Anti-Pattern: Overloaded Profile
ADOBE XDM SCHEMA / JSON
1
{
2
  "_class": "profile",
3
  "personalEmail": { "address": "user@example.com" },
4
  "lastProductViewed": {
5
    "SKU": "ABC-123",
6
    "viewedAt": "2026-03-01T10:15:00Z"
7
  }
8
}

The Problem: Storing interaction data (like product views) on the Profile causes high "profile churn." Every update forces a re-merge of the Unified Profile, slowing down audiences and bloating storage.

✓ Architected Solution: Profile + ExperienceEvent
ADOBE XDM SCHEMA / JSON
1
// Profile record (Who they are)
2
{
3
  "_class": "profile",
4
  "personalEmail": { "address": "user@example.com" },
5
  "person": { "name": { "firstName": "Alex" } }
6
}
7

8
// ExperienceEvent record (What happened)
9
{
10
  "_class": "experienceevent",
11
  "eventType": "commerce.productView",
12
  "timestamp": "2026-03-01T10:15:00Z",
13
  "productListItems": [ { "SKU": "ABC-123" } ]
14
}

The Fix: Keep the Profile for stable traits. Use ExperienceEvents for behavior. This allows AJO to trigger journeys in real-time based on the event without waiting for a Profile update to propagate.

Data types that won’t sabotage personalisation

Data types matter for performance, comparison logic, and Handlebars behaviour. Fields used in audiences, eligibility rules, or score‑based personalisation should almost always be stored as numeric types (integer or double) or constrained strings with enumerated values. Storing everything as free‑text strings prevents AJO from doing efficient comparisons and increases the risk of subtle bugs.

A robust pattern is to use numeric fields for quantities, balances, and scores; enumerations for status/type fields; and ISO 8601 strings for timestamps. When you follow that pattern consistently, AJO conditions and Handlebars checks become straightforward and you avoid edge cases around “string vs number” comparisons.

Recommended data type patterns for Profile

The snippet below illustrates a realistic Profile fragment: numbers where you will calculate or compare, timestamps for recency, and short enumerated strings for categories.

ADOBE XDM SCHEMA / JSON
1
"_tenant": {
2
  "loyalty": {
3
    "tier": "GOLD",                // enum: BRONZE/SILVER/GOLD/PLATINUM
4
    "pointsBalance": 12450,         // integer
5
    "lifetimeSpend": 8392.45,       // double
6
    "lastRedemptionDate": "2026-03-01T10:15:00Z"  // ISO 8601 string
7
  },
8
  "engagement": {
9
    "lastOpenDate": "2026-02-28T07:45:00Z",
10
    "channelPreference": "EMAIL"  // enum
11
  }
12
}

Architect note: This is where Field Groups and Mixins start to matter. You want loyalty and engagement objects defined once in reusable components, not re‑implemented slightly differently in every schema.

3. Designing XDM ExperienceEvent for AJO Triggers

If the Profile schema describes who the customer is, ExperienceEvent describes what they did and why you are talking to them right now. For Adobe Journey Optimizer, this class powers real-time triggers, behavioural segments, and decisioning. A good event design captures the minimum complete story for a journey: identity, event type, key business metrics, and the context AJO needs to personalise the response.

ExperienceEvent schemas should be small, sharp, and purpose-built. Avoid the temptation to create a single “mega event” for every interaction. Instead, model a handful of atomic behaviour types (for example, commerce.productView, commerce.checkoutStart, support.caseCreated) with consistent structures that are easy to query and re-use.

Example: product view event optimised for AJO

This example shows a minimal, AJO‑ready ExperienceEvent for a product view. It carries a clear eventType, the right identities, and just enough commerce context for journeys, offers, and personalisation templates to do useful work without additional lookups.

ADOBE XDM SCHEMA / JSON
1
{
2
  "_class": "experienceevent",
3
  "_id": "evt-20260301-0001",
4
  "eventType": "commerce.productView",
5
  "timestamp": "2026-03-01T10:15:00Z",
6
  "identityMap": {
7
    "email": [
8
      { "id": "user@example.com", "primary": true }
9
    ],
10
    "ecid": [
11
      { "id": "12345678901234567890123456789012345" }
12
    ]
13
  },
14
  "productListItems": [
15
    {
16
      "SKU": "ABC-123",
17
      "name": "Signature Hoodie",
18
      "currencyCode": "AUD",
19
      "priceTotal": 89.00
20
    }
21
  ],
22
  "web": {
23
    "webPageDetails": {
24
      "URL": "https://www.example.com/product/ABC-123",
25
      "name": "Signature Hoodie"
26
    }
27
  },
28
  "_tenant": {
29
    "experience": {
30
      "sessionId": "sess-789",
31
      "channel": "WEB",
32
      "brand": "PRIMARY"
33
    }
34
  }
35
}

Architect note: keep event payloads lean enough that AJO can resolve them quickly, but rich enough that you don’t have to re‑query external systems inside every journey.

Event granularity and journey design

A frequent failure pattern is overloading a single event with multiple behaviours. For example, sending commerce.cartEvent with “viewed_cart”, “updated_cart”, and “abandoned_cart” embedded in a single actionType field. This makes segments brittle and journeys significantly harder to troubleshoot.

A cleaner approach is to define a small set of atomic event types that map directly to meaningful customer moments: “viewed product”, “started checkout”, “placed order”, and “cancelled order”. AJO journeys and offers can then subscribe to exactly the moments that matter, using simple eventType filters and a few additional conditions.

Event granularity: anti‑patterns vs architected design

Compare a multi‑purpose “cartEvent” with an atomic design where each interaction has its own eventType. The latter is easier to segment on, more readable, and less likely to create logic bugs in AJO condition builders.

ADOBE XDM SCHEMA / JSON
1
// Anti-pattern: overloaded cart event
2
"eventType": "commerce.cartEvent",
3
"_tenant": {
4
  "cart": {
5
    "actionType": "VIEWED|UPDATED|ABANDONED",
6
    "itemCount": 3,
7
    "cartValue": 249.00
8
  }
9
}
10

11
// Better: atomic events
12
// 1) cart viewed
13
"eventType": "commerce.cartViewed"
14

15
// 2) cart updated
16
"eventType": "commerce.cartUpdated"
17

18
// 3) cart abandoned
19
"eventType": "commerce.cartAbandoned"

Architect note: when a business stakeholder describes a “moment we care about”, that is usually your hint that it deserves its own eventType and possibly its own Field Group.

Streaming latency, profile projection, and AJO

Journey Optimizer can react to streaming ExperienceEvents in near real time, but those events do not become part of the merged Unified Profile instantly. Depending on your implementation, there can be a short delay before new events contribute to segments or calculated attributes. That means you should design events so that all journey-critical data is present on the event itself, not only on the Profile.

In practice, this means including key decision inputs—like cart value, product category, channel, and brand—directly in the event payload under either standard commerce structures or tenant objects. AJO can then read these from the trigger context without waiting for profile updates to complete.

Architect’s alert: event‑first decision inputs

When building real‑time abandonment or next‑best‑offer journeys, assume that AJO will read from the current ExperienceEvent first and only fall back to the Profile for slow‑changing traits. Anything that affects the decision for this moment should exist in the event payload.

ADOBE XDM SCHEMA / JSON
1
// Example: checkout started event with decision inputs on the event
2
{
3
  "_class": "experienceevent",
4
  "eventType": "commerce.checkoutStart",
5
  "timestamp": "2026-03-01T10:20:00Z",
6
  "identityMap": {
7
    "email": [
8
      { "id": "user@example.com", "primary": true }
9
    ]
10
  },
11
  "commerce": {
12
    "order": {
13
      "purchaseOrderNumber": "CHK-987",
14
      "priceTotal": 249.00,
15
      "currencyCode": "AUD"
16
    }
17
  },
18
  "productListItems": [
19
    {
20
      "SKU": "ABC-123",
21
      "categories": ["APPAREL", "HOODIES"],
22
      "quantity": 1
23
    },
24
    {
25
      "SKU": "XYZ-999",
26
      "categories": ["ACCESSORIES"],
27
      "quantity": 2
28
    }
29
  ],
30
  "_tenant": {
31
    "experience": {
32
      "channel": "WEB",
33
      "brand": "PRIMARY",
34
      "abandonmentWindowMinutes": 45
35
    }
36
  }
37
}

Architect note: this pattern pairs naturally with AJO’s event‑scoped Handlebars usage. Your later Field Groups and Mixins should reflect this design by encapsulating “checkout experience” attributes into reusable components.

ExperienceEvent as a container for reusable Field Groups

Just like the Profile class, the ExperienceEvent class is a container for Field Groups and Mixins. Commerce, web, mobile, and support data should each have their own domain‑specific Field Groups that can be selectively attached to event schemas. This avoids copy‑pasting the same structures (for example productListItems) into multiple tenant Field Groups.

In the next section we’ll formalise this into a Field Group and Mixin hierarchy that spans both Profile and ExperienceEvent, so that AJO can rely on consistent JSON pointer paths regardless of which schema a particular journey uses.

4. Field Groups & Mixin Hierarchy for AJO

With classes, Profile, and ExperienceEvent patterns in place, the real architecture work begins: designing a reusable hierarchy of Field Groups and Mixins. This is where you decide which attributes belong together, how they are namespaced, and how to keep JSON pointer paths stable as your implementation grows. For Adobe Journey Optimizer, a well‑designed hierarchy means you can add new journeys and channels without refactoring every schema or Handlebars template.

Think of Field Groups as domain packages (loyalty, lifecycle, commerce, support) and Mixins as low‑level building blocks (identity, consent, geo, device). The hierarchy is: class → Field Groups → Mixins, with tenant‑scoped Field Groups acting as the main contract AJO relies on.

What Field Groups and Mixins actually do

In AEP, a Field Group is a reusable set of attributes attached to a specific class. It defines both the structure and semantics of a part of your schema—for example, a Loyalty Profile Field Group on the Profile class, or a “Commerce Checkout” Field Group on ExperienceEvent. A Mixin is a more granular, often cross‑domain set of fields you want to share inside those Field Groups, such as an address or device descriptor.

Architects should minimise the number of tenant Field Groups but maximise their clarity. Each Field Group should have a single responsibility and a clear owner. Mixins then provide re‑usable structures (like “geo”, “offer exposure”, or “consent”) that can be pulled into multiple Field Groups without duplicating definitions.

A simple hierarchy blueprint

Below is a conceptual view of how you might structure Field Groups and Mixins across Profile and ExperienceEvent for an AJO implementation. The exact names will vary, but the pattern—shared Mixins, domain Field Groups, and a consistent tenant namespace—should remain.

ADOBE XDM SCHEMA / JSON
1
// Profile class
2
XDM Individual Profile
3
  |_ Field Group: GS Profile Identity
4
  |_ Field Group: GS Lifecycle Profile
5
  |_ Field Group: GS Loyalty Profile
6
  |_ Field Group: GS Marketing Preferences
7
        |_ Mixin: GS Standard Identity
8
        |_ Mixin: GS Contact Details
9
        |_ Mixin: GS Consent Flags
10

11
// ExperienceEvent class
12
XDM ExperienceEvent
13
  |_ Field Group: GS Commerce Events
14
  |_ Field Group: GS Web Experience
15
  |_ Field Group: GS Support Interactions
16
        |_ Mixin: GS Device Context
17
        |_ Mixin: GS Geo Context
18
        |_ Mixin: GS Offer Exposure

Architect note: the “GS” prefix here stands for “Growth Systems” as a tenant identifier. In your implementation, use a consistent, short prefix across all tenant Field Groups and Mixins.

Naming, namespacing, and JSON pointer stability

AJO templates and conditions don’t care about Field Group names—they care about paths. Once a path like _tenant.loyalty.pointsBalance is used in production, it becomes a contract. Renaming the Field Group is safe; restructuring the path is not. That’s why your hierarchy should be designed to survive several years of evolution without breaking JSON pointers.

A durable pattern is to keep all custom data under a single _tenant object per class, and treat the next level as domain objects (for example loyalty, lifecycle, engagement, experience). Field Groups then contribute to these objects rather than creating new top‑level branches for every use case.

Designing paths that won’t break AJO

Compare a “Field Group per campaign” style, where every project adds new branches, with a durable domain‑based model. The second approach keeps AJO references stable even as you add more journeys and attributes.

ADOBE XDM SCHEMA / JSON
1
// Fragile: every initiative creates a new branch
2
"_tenant": {
3
  "summerSale2026": { "discountBucket": "HIGH" },
4
  "loyaltyCampaign": { "pointsAwarded": 500 },
5
  "vipProgram": { "vipFlag": true }
6
}
7

8
// Durable: one loyalty domain, many attributes
9
"_tenant": {
10
  "loyalty": {
11
    "tier": "GOLD",
12
    "pointsBalance": 12450,
13
    "lastBonusCampaign": "SUMMER_2026",
14
    "lastBonusPoints": 500
15
  }
16
}

Architect note: the more journey‑independent your JSON pointers are, the less refactoring you have to do when marketing invents a new campaign type.

Sharing Mixins across Profile and ExperienceEvent

Some concepts—like geo, device, or consent—appear on both Profile and ExperienceEvent. Instead of redefining them in each Field Group, encapsulate them in Mixins and attach those Mixins where needed. This keeps field names, descriptions, and data types aligned, which in turn prevents subtle discrepancies when AJO reads similar concepts from different schemas.

For example, a “GS Geo Context” Mixin might define country, region, and city. On Profile it can represent the customer’s primary location; on ExperienceEvent it captures the location of a specific interaction.

Example: shared “Geo Context” Mixin

This example shows how one Mixin definition can be reused in a Profile Field Group and an ExperienceEvent Field Group while keeping the JSON pointer shape consistent for AJO.

ADOBE XDM SCHEMA / JSON
1
// Mixin: GS Geo Context (definition simplified)
2
"_tenant": {
3
  "geo": {
4
    "country": "AU",
5
    "region": "NSW",
6
    "city": "Sydney"
7
  }
8
}
9

10
// On Profile: primary geo
11
"_class": "profile",
12
"_tenant": {
13
  "geo": {
14
    "country": "AU",
15
    "region": "NSW",
16
    "city": "Sydney"
17
  }
18
}
19

20
// On ExperienceEvent: interaction geo
21
"_class": "experienceevent",
22
"_tenant": {
23
  "geo": {
24
    "country": "AU",
25
    "region": "NSW",
26
    "city": "Sydney"
27
  }
28
}

Architect note: from AJO’s perspective, _tenant.geo.country is familiar regardless of whether it’s read from profile or context.journey.events, which simplifies template logic.

Governance: who is allowed to create new Field Groups?

Without centralized governance, every project team will create its own Field Groups and Mixins, often duplicating existing structures with slightly different names. This is how organisations end up with five different “loyalty” objects and a dozen ways to express “preferred channel”. AJO pays the price through inconsistent segments and fragmented templates.

A practical governance model is to restrict Field Group and Mixin creation to a small schema council (typically solution architects and data model owners). Delivery teams request new fields through a lightweight process, and the council decides whether to extend an existing Field Group or create a new one. This preserves the long-term integrity of JSON pointers.

Governance checklist for Field Groups & Mixins

Use this quick checklist whenever someone proposes a new Field Group or Mixin:

  • Does an existing Field Group already cover this domain with minor extensions?
  • Can the requested fields fit under an existing domain object (for example _tenant.loyalty) without breaking current pointers?
  • Is there a concept here (geo, device, consent, offer exposure) that should be abstracted as a Mixin and reused across multiple Field Groups?
  • Has the team provided AJO use cases (real-time triggers, journeys, templates) that justify the new fields?
  • Have you documented the new Field Group/Mixin in your internal schema catalogue so that future teams can reuse it?

With a clear Field Group and Mixin hierarchy in place, your XDM model becomes predictable for both engineers and marketers. The final pieces are pattern catalogues and a 10‑point QA checklist, so new journeys are evaluated against architectural standards before they go live in AJO.

5. Master XDM Design Patterns Index

Use this table as a fast lookup during design reviews and incident response. Identify the design smell you are seeing in AEP or AJO, match the architectural root cause, and then apply the recommended schema pattern. Treat this as your “do not ship” list for XDM implementations powering Journey Optimizer.

Design Smell Architectural Root Cause Recommended Schema Pattern
Duplicated Attributes: e.g., multiple loyalty objects or point balances. No single domain Field Group; each project team created their own tenant branch. Consolidate into one _tenant.loyalty object. Apply strict merge policies to prevent data collisions.
Path Proliferation: Templates referencing multiple paths for "preferred channel". Concept was modelled separately for each channel instead of centrally on the Profile. Move channel-agnostic preferences into _tenant.marketing on Profile. Implement standardized onboarding paths.
Thin Events: Journeys requiring complex API joins for simple decisions. Journey-critical fields were never promoted into XDM; event payloads are too thin. Promote key variables into ExperienceEvent Field Groups. Use Custom Actions only for external orchestration.
Boolean Bloat: Too many one-off flags (isVip, isChurnRisk). No normalised lifecycle model; every use case creates a new flag. Introduce a _tenant.lifecycle object. Use AEP Calculated Attributes to automate status updates based on event history.
Generic cartEvent: Overloaded ExperienceEvents using actionType strings. Event granularity not aligned to moments; one schema trying to serve all behaviours. Split into atomic events. Reference our Abandoned Cart blueprints for optimized commerce schema structures.
Identity Collisions: Fragmented Profiles across different brands. Inconsistent primary identity namespaces across person-based schemas. Standardise on a primary namespace (crmId or email). Implement the Identity Map Protocol for cross-device stitching.
Slow Segments: Audiences failing to evaluate or timing out at scale. Segments traversing deeply nested structures or string-encoded numbers. Flatten high-use attributes. Migrate from batch to Streaming Segmentation for real-time eligibility.
Logic Instability: Blank personalization for valid users. Lack of defensive coding or null-guards for optional schema objects. Implement Defensive Coding Patterns and audit paths via the AJO Debugging Guide.

6. The 10‑Point Architect XDM Schema QA Checklist

Before publishing any enterprise‑grade AJO journey or exposing a new schema to marketers, run this checklist as a mandatory schema QA gate. It is designed for marketing engineers, solution architects, and senior marketing ops and should be applied in both lower environments and production cut‑over.

1. Identity strategy

Is the primary identity namespace consistent with your global stitching standard? Are all required alternative IDs present in identityMap for AJO targeting?

2. Class alignment

Does your Profile schema contain only long‑lived traits? Ensure your ingestion governance prevents behavioral data from bloating the UPS.

3. Tenant namespacing

Are all custom fields scoped under the correct _tenant object? Have you avoided campaign-specific branches that break over time?

4. Field Group & Mixin reuse

Have you reused existing domain structures? If new ones were created, are they registered in your internal schema catalogue with clear ownership?

5. Data type discipline

Are points and balances numeric? Proper typing is required for Handlebars arithmetic helpers to function without errors.

6. JSON pointer stability

Have you validated character-exact paths? Restructuring paths after a journey is live will break every message template using that attribute.

7. Event decision inputs

Are decision-critical fields (SKUs, URLs) present on the event payload? Referencing live event context is essential to bypass Profile ingestion latency.

8. Documentation & semantics

Does every custom field have a clear label and description? This is the only way to scale schema usage across multiple marketing teams.

9. Backwards compatibility

Have you checked for breaking changes via XDM versioning? Deprecated fields must be handled gracefully to avoid journey crashes.

10. AJO usage validation

Have you tested the logic using AEP Test Profiles? Previewing with realistic JSON payloads is the only way to guarantee send-time accuracy.