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
}
{ "person": { "name": { "firstName": "Alex", "lastName": "Nguyen" }, "gender": "female" }, "personalEmail": { "address": "user@example.com" }, "homeAddress": { "country": "AU" } }

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
}
{ "_tenant": { "lifecycle": { "stage": "Active", "riskScore": 0.87 }, "loyalty": { "tier": "GOLD", "pointsBalance": 12450 }, "marketing": { "preferredChannel": "email", "emailOptIn": true } } }

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, isHighSpender, 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
}
{ "_class": "profile", "personalEmail": { "address": "user@example.com" }, "lastProductViewed": { "SKU": "ABC-123", "viewedAt": "2026-03-01T10:15:00Z" } }

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
}
// Profile record (Who they are) { "_class": "profile", "personalEmail": { "address": "user@example.com" }, "person": { "name": { "firstName": "Alex" } } } // ExperienceEvent record (What happened) { "_class": "experienceevent", "eventType": "commerce.productView", "timestamp": "2026-03-01T10:15:00Z", "productListItems": [ { "SKU": "ABC-123" } ] }

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
}
"_tenant": { "loyalty": { "tier": "GOLD", // enum: BRONZE/SILVER/GOLD/PLATINUM "pointsBalance": 12450, // integer "lifetimeSpend": 8392.45, // double "lastRedemptionDate": "2026-03-01T10:15:00Z" // ISO 8601 string }, "engagement": { "lastOpenDate": "2026-02-28T07:45:00Z", "channelPreference": "EMAIL" // enum } }

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.