Universal Data Scoping and Decoration: The Unified Model (v4.0)
A deterministic strategy for applying high-signal data attributes across web interfaces. Separate user context from page context from element context to maximize efficiency, searchability, and consistency.
Overview
This meta-skill provides the strategic framework for deciding where to capture data in Fullstory. Before implementing any Fullstory API, use this guide to determine the appropriate scope for your data.
The Data Hierarchy
┌─────────────────────────────────────────────────────────────┐
│ USER PROPERTIES (FS setIdentity / setProperties) │
│ Scope: Across all sessions for this user │
│ Examples: plan, role, company, signup_date │
├─────────────────────────────────────────────────────────────┤
│ PAGE PROPERTIES (FS setProperties type: 'page') │
│ Scope: Current page until URL path changes │
│ Examples: pageName, searchTerm, filters, productId │
├─────────────────────────────────────────────────────────────┤
│ ELEMENT PROPERTIES (data-fs-properties-schema) │
│ Scope: Individual element interactions │
│ Examples: itemId, position, variant, price │
├─────────────────────────────────────────────────────────────┤
│ EVENT PROPERTIES (FS trackEvent) │
│ Scope: Single discrete action │
│ Examples: orderId, revenue, source, action │
└─────────────────────────────────────────────────────────────┘
I. Core Principles: Data Scope and Responsibility
Rule 1: Capture Data at the Highest Relevant Scope
Why?
- Reduces duplication
- Improves searchability
- Makes data inheritance work correctly
- Simplifies semantic decoration
Decision Matrix:
| Data Characteristic | Scope to Use | API |
|---------------------|--------------|-----|
| Same for all user sessions | User Properties | setIdentity / setProperties(user) |
| Same for entire page | Page Properties | setProperties(page) |
| Different per element on page | Element Properties | data-fs-* attributes |
| Discrete action/moment | Event | trackEvent |
Rule 2: Never Duplicate Across Scopes
❌ BAD: Product ID on page AND on every element
// Page properties
FS('setProperties', { type: 'page', properties: { productId: 'SKU-123' }});
// Also on element (REDUNDANT!)
<button data-product-id="SKU-123">Add to Cart</button>
✅ GOOD: Product ID at page level only
// Page properties (single source of truth)
FS('setProperties', { type: 'page', properties: { productId: 'SKU-123' }});
// Element just has element-specific data
<button data-fs-element="Add to Cart Button">Add to Cart</button>
Rule 3: Let Inheritance Work
Fullstory's property inheritance:
- User properties → Available on all sessions for that user
- Page properties → Available on all elements/events on that page
- Element properties → Inherited by child elements, AND child properties bubble up to parent on interaction
Important: Child → Parent Inheritance
When a user interacts with a parent element (e.g., clicks a form submit button), Fullstory captures properties from ALL child elements that have been interacted with:
<form data-fs-element="CheckoutForm">
<select data-fs-element="ShippingMethod"
data-fs-properties-schema='{"value": {"type": "str", "name": "shipping"}}'>
<option value="standard">Standard</option>
<option value="express">Express</option>
</select>
<button type="submit">Place Order</button>
</form>
<!-- When form is submitted, Fullstory captures shipping selection from child -->
Rule 4: Consider Privacy at Each Scope
Different scopes have different privacy implications:
| Scope | Privacy Consideration | Recommendation |
|-------|----------------------|----------------|
| User Properties | Persists across sessions, linked to identity | Hash/tokenize PII; use internal IDs |
| Page Properties | Visible in any session replay on this page | Mask sensitive page context |
| Element Properties | Captured on interaction | Use fs-exclude for sensitive inputs |
| Events | Logged with timestamp | Never include PII in event properties |
// ✅ GOOD: Privacy-conscious scoping
FS('setIdentity', {
uid: 'usr_abc123', // Internal ID, not email
properties: {
plan: 'enterprise',
account_age_days: 365
// NO: email, name, phone
}
});
FS('setProperties', {
type: 'page',
properties: {
pageName: 'Account Settings',
section: 'billing'
// NO: account balance, card details
}
});
Reference: See
fullstory-privacy-controlsfor fs-exclude/fs-mask/fs-unmask andfullstory-privacy-strategyfor comprehensive privacy guidance.
Rule 5: Anonymous Users Can Have Properties
User properties work for anonymous users (before setIdentity is called). Properties persist via the fs_uid first-party cookie and transfer when the user later identifies:
// Anonymous user lands on site
FS('setProperties', {
type: 'user',
properties: {
landing_page: '/pricing',
referral_source: 'google_ads',
campaign: 'spring_promo'
}
});
// Later, user signs up - properties transfer automatically
FS('setIdentity', {
uid: 'usr_newuser123',
properties: {
signup_date: '2024-01-15'
}
});
// Now user has: landing_page, referral_source, campaign, AND signup_date
II. Scope Selection by Scenario
Scenario A: Single-Entity Detail Pages (1:1)
Definition: A page dedicated to one unique entity (product detail, flight itinerary, policy document).
Strategy: Entity attributes become Page Properties
// ✅ CORRECT: Entity data at page level
FS('setProperties', {
type: 'page',
properties: {
pageName: 'Product Detail',
productId: 'SKU-123',
productName: 'Wireless Headphones',
category: 'Electronics',
price: 199.99,
inStock: true
}
});
// ✅ CORRECT: Elements just have element-specific data
<button data-fs-element="Add to Cart">Add to Cart</button>
<button data-fs-element="Buy Now">Buy Now</button>
// ❌ WRONG: Entity data duplicated on elements
<button
data-fs-element="Add to Cart"
data-product-id="SKU-123" // REDUNDANT - already at page level
data-product-name="Wireless..." // REDUNDANT
>Add to Cart</button>
Why This Works:
- All interactions inherit product context
- Search by "clicks on Product Detail page where price > 100" works
- No duplication, cleaner implementation
Scenario B: Multi-Entity Listing Pages (1:Many)
Definition: Pages showing multiple distinct entities (search results, product grid, job listings).
Strategy:
- Search/filter context → Page Properties
- Individual item context → Element Properties
// ✅ CORRECT: Search context at page level
FS('setProperties', {
type: 'page',
properties: {
pageName: 'Search Results',
searchTerm: 'wireless headphones',
resultsCount: 50,
sortBy: 'relevance',
activeFilters: ['Electronics', 'In Stock']
}
});
<!-- ✅ CORRECT: Item-specific data on elements -->
<div
data-product-id="SKU-123"
data-product-name="Wireless Headphones"
data-price="199.99"
data-position="1"
data-fs-properties-schema='{
"data-product-id": {"type": "str", "name": "productId"},
"data-product-name": {"type": "str", "name": "productName"},
"data-price": {"type": "real", "name": "price"},
"data-position": {"type": "int", "name": "position"}
}'
data-fs-element="Product Card"
>
...
</div>
<!-- ❌ WRONG: Search context duplicated on elements -->
<div
data-product-id="SKU-123"
data-search-term="wireless headphones" <!-- REDUNDANT - page level -->
data-results-count="50" <!-- REDUNDANT - page level -->
>
Scenario C: User Attributes
Definition: Data about WHO the user is (not what they're doing).
Strategy: Use User Properties via setIdentity or setProperties(user)
// ✅ CORRECT: User attributes at user level
FS('setIdentity', {
uid: user.id,
properties: {
displayName: user.name,
email: user.email,
plan: 'enterprise',
role: 'admin',
companyName: user.company,
signupDate: user.createdAt
}
});
// ❌ WRONG: User attributes in page/element properties
FS('setProperties', {
type: 'page',
properties: {
userPlan: 'enterprise', // WRONG SCOPE - use user properties
userRole: 'admin' // WRONG SCOPE
}
});
Scenario D: Discrete Actions (Events)
Definition: Something that happened at a point in time.
Strategy: Use trackEvent with action-specific properties
// ✅ CORRECT: Action captured as event
FS('trackEvent', {
name: 'Product Added to Cart',
properties: {
quantity: 2, // Action-specific
addedFrom: 'quick-view', // Action-specific
// productId inherited from page properties
// userId inherited from user properties
}
});
// ❌ WRONG: Trying to track actions via properties
FS('setProperties', {
type: 'user',
properties: {
lastAddedProduct: 'SKU-123', // Events shouldn't be properties
lastAddedQuantity: 2,
lastAddedTime: Date.now()
}
});
III. Decision Flowchart
START: You have data to capture
│
▼
Is this data about WHO the user is?
(plan, role, company, signup date)
│
YES ──┴── NO
│ │
▼ ▼
USER Is this a discrete action/moment?
PROPS (purchase, signup, feature used)
│
YES ──┴── NO
│ │
▼ ▼
EVENT Is this data the same for the entire page?
(search term, product on detail page)
│
YES ──┴── NO
│ │
▼ ▼
PAGE ELEMENT
PROPS PROPS
IV. Common Patterns by Industry
Detailed guidance: See industry-specific skills for comprehensive implementation patterns.
E-commerce (fullstory-ecommerce)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| User's loyalty tier | User Property | setIdentity |
| Search term | Page Property | setProperties(page) |
| Product ID (on PDP) | Page Property | setProperties(page) |
| Product ID (in grid) | Element Property | data-fs-* |
| Cart value | Page Property | setProperties(page) |
| Purchase completed | Event | trackEvent |
SaaS (fullstory-saas)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| User role | User Property | setIdentity |
| Team/org ID | User Property | setIdentity |
| Dashboard being viewed | Page Property | setProperties(page) |
| Report ID in list | Element Property | data-fs-* |
| Feature used | Event | trackEvent |
| Setting changed | Event | trackEvent |
Media & Entertainment (fullstory-media-entertainment)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| Subscriber status | User Property | setIdentity |
| Content category | Page Property | setProperties(page) |
| Content ID (on detail) | Page Property | setProperties(page) |
| Related content IDs | Element Property | data-fs-* |
| Video played | Event | trackEvent |
| Content shared | Event | trackEvent |
Banking & Finance (fullstory-banking)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| Account type | User Property | setIdentity |
| Current section | Page Property | setProperties(page) |
| Transaction type | Page Property | setProperties(page) |
| Account selector | Element Property | data-fs-* (ID only, mask details) |
| Transfer completed | Event | trackEvent (no amounts) |
| MFA step | Event | trackEvent |
Gaming (fullstory-gaming)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| Player tier (VIP status) | User Property | setIdentity |
| Game lobby section | Page Property | setProperties(page) |
| Active game ID | Page Property | setProperties(page) |
| Game tile in grid | Element Property | data-fs-* |
| Wager placed | Event | trackEvent (for compliance) |
| Game launched | Event | trackEvent |
Healthcare (fullstory-healthcare)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| User role (patient/provider) | User Property | setIdentity |
| Section (appointments, records) | Page Property | setProperties(page) |
| Flow step | Page Property | setProperties(page) |
| Form field (non-PHI only) | Element Property | data-fs-* + fs-exclude |
| Appointment scheduled | Event | trackEvent (no PHI) |
| Form submitted | Event | trackEvent (completion only) |
Travel & Hospitality (fullstory-travel)
| Data Point | Scope | Implementation |
|------------|-------|----------------|
| Loyalty tier | User Property | setIdentity |
| Search criteria | Page Property | setProperties(page) |
| Selected flight/hotel | Page Property | setProperties(page) |
| Flight option in results | Element Property | data-fs-* |
| Booking completed | Event | trackEvent |
| Ancillary added | Event | trackEvent |
V. Anti-Patterns to Avoid
Anti-Pattern 1: Everything as User Properties
// ❌ BAD: Transient state as user property
FS('setProperties', {
type: 'user',
properties: {
currentPage: '/checkout', // Should be page property
cartItems: 5, // Should be page property
lastClickedButton: 'submit' // Should be event
}
});
Anti-Pattern 2: Everything as Events
// ❌ BAD: State as events
FS('trackEvent', { name: 'User Is Premium', properties: {} }); // User property
FS('trackEvent', { name: 'Page Has 5 Results', properties: {} }); // Page property
Anti-Pattern 3: Duplicating Hierarchy
// ❌ BAD: Same data at multiple levels
FS('setIdentity', { uid: '123', properties: { plan: 'premium' }});
FS('setProperties', { type: 'page', properties: { userPlan: 'premium' }}); // DUP
FS('trackEvent', { name: 'Click', properties: { userPlan: 'premium' }}); // DUP
Anti-Pattern 4: Over-Granular Element Properties
// ❌ BAD: Too much data on elements when page context would work
<button
data-page-name="Checkout" // Should be page property
data-user-id="123" // Should be user property
data-cart-total="99.99" // Should be page property
data-fs-element="Submit">
VI. Implementation Checklist
Before implementing, answer these questions:
-
[ ] Who is this data about?
- The user → User Properties
- The page/context → Page Properties
- A specific element → Element Properties
- An action → Event
-
[ ] How long is this data relevant?
- Entire user lifetime → User Properties
- This page view → Page Properties
- This interaction → Element/Event Properties
-
[ ] Is this data already available at a higher scope?
- If yes → Don't duplicate, let inheritance work
- If no → Add at appropriate scope
-
[ ] Can this data be searched/segmented?
- User properties → Segment users
- Page properties → Find sessions with this page context
- Element properties → Find clicks on elements with this data
- Events → Funnel analysis, conversion tracking
VII. Related Skills
Core API Skills
| API | Skill Document |
|-----|----------------|
| User Identification | fullstory-identify-users |
| User Properties | fullstory-user-properties |
| Page Properties | fullstory-page-properties |
| Element Properties | fullstory-element-properties |
| Events | fullstory-analytics-events |
| Privacy Controls | fullstory-privacy-controls |
Industry Skills
| Industry | Skill Document |
|----------|----------------|
| Banking & Finance | fullstory-banking |
| E-commerce & Retail | fullstory-ecommerce |
| Gaming | fullstory-gaming |
| Healthcare | fullstory-healthcare |
| B2B SaaS | fullstory-saas |
| Travel & Hospitality | fullstory-travel |
| Media & Entertainment | fullstory-media-entertainment |
Strategic Skills
| Topic | Skill Document |
|-------|----------------|
| Getting Started | fullstory-getting-started |
| Privacy Strategy | fullstory-privacy-strategy |
| Stable Selectors | fullstory-stable-selectors |
VIII. Version History
-
4.0 (Current)
- Added YAML front matter for skill metadata
- Integrated with core skill documents
- Added decision flowchart
- Expanded industry-specific patterns
- Added explicit anti-patterns section
- Aligned format with element-properties skill
-
3.0
- Generalized naming and examples
- Added explicit Good/Bad implementation guides
- Renamed properties for universal application
-
2.0
- Merged Scoping and Element Naming into one unified document
Key Takeaways for Agent
When helping developers with data scoping:
-
Always ask first:
- What data are you trying to capture?
- Is this about the user, the page, an element, or an action?
- Will this data be the same across multiple elements?
- Is this data sensitive? What privacy level is needed?
-
Common mistakes to watch for:
- User data in page properties
- Page-level data duplicated on elements
- Actions captured as properties instead of events
- Same data at multiple scopes
- PII in user properties without hashing
- Forgetting that anonymous users can have properties
-
Golden rules:
- Capture at the HIGHEST relevant scope
- Let inheritance do the work (both parent→child AND child→parent)
- User → Page → Element → Event (hierarchy)
- Don't duplicate across scopes
- Consider privacy at every scope
-
Quick decision:
- WHO = User Properties (works for anonymous users too!)
- WHERE = Page Properties
- WHAT (specific item) = Element Properties
- WHAT HAPPENED = Events
-
Privacy quick reference:
- Sensitive user data → Hash/tokenize or use internal IDs
- Sensitive page context → Consider masking
- Sensitive elements → Use
fs-exclude - Sensitive events → Never include PII in properties
This meta-skill document provides strategic guidance for Fullstory data semantic decoration. Refer to individual API skill documents for detailed implementation examples.