This specification defines UJM (User Journey Mapping), a data model and serialization for describing user journeys as graphs of Steps connected by Transitions, optionally grouped into Phases and Sessions. It provides a JSON-LD context for semantic interoperability and an illustrative JSON Schema.
This Editor’s Draft incorporates corrections to JSON-LD keyword aliasing, clarifies Channel/Touchpoint semantics, and introduces an optional CallStep mechanism for nested journeys without cross-graph transitions.
The key words MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in .
Data Model
Core classes
ujm:Journey
Property | Cardinality | Notes |
---|---|---|
id | 1 | IRI of the journey. |
type | 1..* | MUST include ujm:Journey . |
title | 1 | Human-readable. |
goal | 0..1 | Free text. |
created , modified | 0..1 each | ISO 8601 date-time. |
personas | 0..* | Array of Persona. |
tags | 0..* | Array of strings. |
steps | 1..* | Array of Step (and optionally CallStep). |
transitions | 0..* | Array of Transitions (intra-journey only). |
phases , sessions | 0..* | Grouping via StepRef. |
start , end | 0..1 each | IRI of Step within this Journey. |
ujm:Step
Property | Cardinality | Notes |
---|---|---|
id , type , name | 1, 1..*, 1 | type includes ujm:Step . |
description | 0..1 | Free text. |
touchpoint | 0..1 | IRI to a Touchpoint node or an external URL. |
channel | 0..1 | Controlled term via @vocab (e.g., ujm:Web ). |
persona | 0..1 | IRI of a Persona. |
emotions | 0..* | Array of Emotion. |
metrics | 0..* | Array of Metric. |
preconditions , postconditions | 0..* | Model assumptions/outcomes. |
timestamp , duration | 0..1 each | ISO date-time; ISO 8601 duration. |
ujm:Transition
Connects two Steps within the same Journey. MUST resolve to Step IRIs defined in
steps
of the same Journey.
Property | Cardinality | Notes |
---|---|---|
from , to | 1 each | IRI of Steps in this Journey. |
label | 0..1 | Outcome label (e.g., success , retry ). |
probability | 0..1 | 0..1. |
ujm:Phase, ujm:Session, and ujm:StepRef
Phases and Sessions group Steps using members
of type StepRef.
ujm:Channel
Controlled term (e.g., ujm:Web
, ujm:MobileApp
, ujm:Phone
,
ujm:InPerson
, ujm:Email
, ujm:SMS
). MAY be extended.
In JSON-LD, channel
values SHOULD be terms resolved via @vocab
(e.g., "Web"
→ https://www.w3.org/ns/ujm#Web
).
ujm:Touchpoint
Properties: id
(MUST), name
(MUST),
uri
(MAY), location
(MAY),
owner
(MAY). A Step’s touchpoint
MAY reference a Touchpoint node by IRI or an external URL; in JSON-LD, coerce
touchpoint
to @id
.
ujm:Persona, ujm:Emotion, ujm:Metric
Emotion includes optional evidence
. Metric includes
name
, value
, optional unit
, method
, and
sampleSize
.
JSON-LD
Minimal Example
Adding Phases and Sessions without duplicating Steps
Nested Journeys (Proposed)
This specification defines an optional mechanism to reuse one Journey from within another without cross-graph Transitions. Nesting is modeled as a special kind of Step that calls another Journey and returns a result.
ujm:CallStep
A CallStep is a Step with additional properties:
Property | Cardinality | Constraints |
---|---|---|
calls (Journey IRI) | 1 | MUST reference an existing Journey. JSON-LD: coerce to @id . |
entry (Step IRI) | 0..1 | Overrides callee start if provided; otherwise MUST use callee start . |
exitMap | 0..* | Array mapping callee end labels to parent transition labels (e.g., {"calleeEnd":"success","label":"success"} ). If absent, a single unlabeled return is assumed. |
parameters | 0..1 | Arbitrary object passed as runtime hints to the callee (e.g., channel, experiment). |
returns | 0..1 | Arbitrary object for callee outputs (informative; consumers SHOULD ignore unknown fields). |
CallStep inherits all Step properties and participates in Phases/Sessions via StepRef.
Constraints
- Transitions in the parent Journey MUST connect to the CallStep (not directly into the callee). Cross-Journey Transitions remain disallowed.
- The callee Journey MUST define
start
andend
. If multiple ends exist, they SHOULD carry labels that the parent references viaexitMap
. - Nesting MAY be recursive but MUST be acyclic at runtime.
Processing Model Additions
- When encountering a CallStep, processors MAY inline the callee Journey for visualization/analysis using the specified
entry
andend
steps. - Aggregate metrics (duration, completion probability) from callee to the CallStep when feasible; provenance of aggregation SHOULD be recorded.
JSON-LD
Schema Additions (Illustrative)
{ "$defs": { "CallStep": { "type": "object", "allOf": [{ "$ref": "#/$defs/Step" }], "properties": { "type": { "oneOf": [ { "const": "CallStep" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "CallStep" } } ] }, "calls": { "type": "string", "format": "uri" }, "entry": { "type": "string", "format": "uri" }, "exitMap": { "type": "array", "items": { "type": "object", "properties": { "calleeEnd": { "type": "string" }, "label": { "type": "string" } }, "required": ["calleeEnd","label"] } }, "parameters": { "type": "object" }, "returns": { "type": "object" } }, "required": ["calls"] } } }
Profiles
Profiles add constraints beyond the base model. For example, the Minimal Profile requires ≥ 1 Transition even though the base model allows zero.
Appendix A: JSON Schema (Illustrative)
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://www.w3.org/ns/ujm/schema.json", "title": "User Journey Mapping 1.0", "type": "object", "required": ["id", "type", "title", "steps"], "properties": { "id": { "type": "string", "format": "uri" }, "type": { "oneOf": [ { "const": "Journey" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "Journey" } } ] }, "title": { "type": "string" }, "goal": { "type": "string" }, "created": { "type": "string", "format": "date-time" }, "modified": { "type": "string", "format": "date-time" }, "personas": { "type": "array", "items": { "$ref": "#/$defs/Persona" } }, "tags": { "type": "array", "items": { "type": "string" } }, "steps": { "type": "array", "items": { "oneOf": [ { "$ref": "#/$defs/Step" }, { "$ref": "#/$defs/CallStep" } ] }, "minItems": 1 }, "transitions": { "type": "array", "items": { "$ref": "#/$defs/Transition" } }, "phases": { "type": "array", "items": { "$ref": "#/$defs/Phase" } }, "sessions": { "type": "array", "items": { "$ref": "#/$defs/Session" } }, "start": { "type": "string", "format": "uri", "description": "IRI of a Step in this Journey." }, "end": { "type": "string", "format": "uri", "description": "IRI of a Step in this Journey." } }, "$defs": { "Step": { "type": "object", "required": ["id", "type", "name"], "properties": { "id": { "type": "string", "format": "uri" }, "type": { "oneOf": [ { "const": "Step" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "Step" } } ] }, "name": { "type": "string" }, "description": { "type": "string" }, "touchpoint": { "type": "string", "format": "uri", "description": "IRI to a Touchpoint node or external URL." }, "channel": { "type": "string", "description": "Controlled term; commonly 'Web','MobileApp','Phone','InPerson','Email','SMS'." }, "persona": { "type": "string", "format": "uri" }, "emotions": { "type": "array", "items": { "$ref": "#/$defs/Emotion" } }, "metrics": { "type": "array", "items": { "$ref": "#/$defs/Metric" } }, "preconditions": { "type": "array", "items": { "type": "string" } }, "postconditions": { "type": "array", "items": { "type": "string" } }, "timestamp": { "type": "string", "format": "date-time" }, "duration": { "type": "string", "description": "ISO 8601 duration (e.g., 'PT30S')." } } }, "Transition": { "type": "object", "required": ["from", "to"], "properties": { "from": { "type": "string", "format": "uri", "description": "IRI of Step in same Journey." }, "to": { "type": "string", "format": "uri", "description": "IRI of Step in same Journey." }, "label": { "type": "string" }, "probability": { "type": "number", "minimum": 0, "maximum": 1 } } }, "StepRef": { "type": "object", "required": ["step"], "properties": { "step": { "type": "string", "format": "uri" }, "position": { "oneOf": [ { "type": "number" }, { "type": "string" } ] }, "required": { "type": "boolean" }, "visibilityRule": {}, "validFrom": { "type": "string", "format": "date-time" }, "validTo": { "type": "string", "format": "date-time" }, "variant": { "type": "string" } } }, "Phase": { "type": "object", "required": ["id", "type", "name"], "properties": { "id": { "type": "string", "format": "uri" }, "type": { "oneOf": [ { "const": "Phase" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "Phase" } } ] }, "name": { "type": "string" }, "description": { "type": "string" }, "order": { "oneOf": [ { "type": "number" }, { "type": "string" } ] }, "members": { "type": "array", "items": { "$ref": "#/$defs/StepRef" } } } }, "Session": { "type": "object", "required": ["id", "type", "name"], "properties": { "id": { "type": "string", "format": "uri" }, "type": { "oneOf": [ { "const": "Session" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "Session" } } ] }, "name": { "type": "string" }, "description": { "type": "string" }, "environment": { "type": "object" }, "members": { "type": "array", "items": { "$ref": "#/$defs/StepRef" } } } }, "Persona": { "type": "object", "required": ["id", "name"], "properties": { "id": { "type": "string", "format": "uri" }, "name": { "type": "string" }, "description": { "type": "string" }, "attributes": { "type": "object" } } }, "Touchpoint": { "type": "object", "required": ["id", "name"], "properties": { "id": { "type": "string", "format": "uri" }, "name": { "type": "string" }, "uri": { "type": "string", "format": "uri" }, "location": {}, "owner": { "type": "string" } } }, "Emotion": { "type": "object", "required": ["term"], "properties": { "term": { "type": "string" }, "intensity": { "type": "number", "minimum": 0, "maximum": 1 }, "evidence": {} } }, "Metric": { "type": "object", "required": ["name", "value"], "properties": { "name": { "type": "string" }, "value": {}, "unit": { "type": "string" }, "method": { "type": "string" }, "sampleSize": { "type": "number", "minimum": 0 } } }, "CallStep": { "type": "object", "allOf": [ { "$ref": "#/$defs/Step" } ], "properties": { "type": { "oneOf": [ { "const": "CallStep" }, { "type": "array", "items": { "type": "string" }, "contains": { "const": "CallStep" } } ] }, "calls": { "type": "string", "format": "uri" }, "entry": { "type": "string", "format": "uri" }, "exitMap": { "type": "array", "items": { "type": "object", "properties": { "calleeEnd": { "type": "string" }, "label": { "type": "string" } }, "required": ["calleeEnd","label"] } }, "parameters": { "type": "object" }, "returns": { "type": "object" } }, "required": ["calls"] } } }