All Skills
FullStory

universal-data-scoping-and-decoration

Platform APIs & Operations

Strategic meta-skill for Fullstory data semantic decoration. Teaches when to use page properties vs element properties vs user properties vs events. Provides a deterministic framework for scoping data at the right level to maximize searchability, avoid duplication, and maintain consistency. Essential foundation for proper Fullstory implementation across all API types.

E
$npx skills add fullstorydev/fs-skills --skill universal-data-scoping-and-decoration

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 0: Choose Your Decoration Approach

Before diving into specific scopes, decide how you will decorate your application with semantic data attributes. There are two primary approaches (see fullstory-stable-selectors/SKILL.md for full details):

ApproachDescriptionBest For
FrameworkBuild data attributes (data-component, data-id, data-section, data-position, data-state) into shared, reusable components. Every instance gets decorated automatically.Teams with design systems or component libraries
Individual ElementDevelopers manually add data attributes to individual elements.Any codebase, teams wanting immediate results

Agent Directive: Ask the user which approach they prefer before proceeding with element-level decoration.

For mobile applications, the same data attributes can be set using FS.setAttribute calls (see fullstory-stable-selectors/SKILL-MOBILE.md and mobile-instrumentation-orchestrator/SKILL.md). Mobile apps should also implement the Pages API for screen-level context, Events for discrete actions, and User/Page Properties for contextual data — see Reference 3 in the mobile best practices guide.

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 CharacteristicScope to UseAPI
Same for all user sessionsUser PropertiessetIdentity / setProperties(user)
Same for entire pagePage PropertiessetProperties(page)
Different per element on pageElement Propertiesdata-fs-* attributes
Discrete action/momentEventtrackEvent

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 + stable selectors
<button data-component="button" data-id="add-to-cart" 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:

ScopePrivacy ConsiderationRecommendation
User PropertiesPersists across sessions, linked to identityHash/tokenize PII; use internal IDs
Page PropertiesVisible in any session replay on this pageMask sensitive page context
Element PropertiesCaptured on interactionUse fs-exclude for sensitive inputs
EventsLogged with timestampNever 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-controls for fs-exclude/fs-mask/fs-unmask and fullstory-privacy-strategy for 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 PointScopeImplementation
User's loyalty tierUser PropertysetIdentity
Search termPage PropertysetProperties(page)
Product ID (on PDP)Page PropertysetProperties(page)
Product ID (in grid)Element Propertydata-fs-*
Cart valuePage PropertysetProperties(page)
Purchase completedEventtrackEvent

SaaS (fullstory-saas)

Data PointScopeImplementation
User roleUser PropertysetIdentity
Team/org IDUser PropertysetIdentity
Dashboard being viewedPage PropertysetProperties(page)
Report ID in listElement Propertydata-fs-*
Feature usedEventtrackEvent
Setting changedEventtrackEvent

Media & Entertainment (fullstory-media-entertainment)

Data PointScopeImplementation
Subscriber statusUser PropertysetIdentity
Content categoryPage PropertysetProperties(page)
Content ID (on detail)Page PropertysetProperties(page)
Related content IDsElement Propertydata-fs-*
Video playedEventtrackEvent
Content sharedEventtrackEvent

Banking & Finance (fullstory-banking)

Data PointScopeImplementation
Account typeUser PropertysetIdentity
Current sectionPage PropertysetProperties(page)
Transaction typePage PropertysetProperties(page)
Account selectorElement Propertydata-fs-* (ID only, mask details)
Transfer completedEventtrackEvent (no amounts)
MFA stepEventtrackEvent

Gaming (fullstory-gaming)

Data PointScopeImplementation
Player tier (VIP status)User PropertysetIdentity
Game lobby sectionPage PropertysetProperties(page)
Active game IDPage PropertysetProperties(page)
Game tile in gridElement Propertydata-fs-*
Wager placedEventtrackEvent (for compliance)
Game launchedEventtrackEvent

Healthcare (fullstory-healthcare)

Data PointScopeImplementation
User role (patient/provider)User PropertysetIdentity
Section (appointments, records)Page PropertysetProperties(page)
Flow stepPage PropertysetProperties(page)
Form field (non-PHI only)Element Propertydata-fs-* + fs-exclude
Appointment scheduledEventtrackEvent (no PHI)
Form submittedEventtrackEvent (completion only)

Travel & Hospitality (fullstory-travel)

Data PointScopeImplementation
Loyalty tierUser PropertysetIdentity
Search criteriaPage PropertysetProperties(page)
Selected flight/hotelPage PropertysetProperties(page)
Flight option in resultsElement Propertydata-fs-*
Booking completedEventtrackEvent
Ancillary addedEventtrackEvent

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

Core API Skills

APISkill Document
User Identificationfullstory-identify-users
User Propertiesfullstory-user-properties
Page Propertiesfullstory-page-properties
Element Propertiesfullstory-element-properties
Eventsfullstory-analytics-events
Privacy Controlsfullstory-privacy-controls

Industry Skills

IndustrySkill Document
Banking & Financefullstory-banking
E-commerce & Retailfullstory-ecommerce
Gamingfullstory-gaming
Healthcarefullstory-healthcare
B2B SaaSfullstory-saas
Travel & Hospitalityfullstory-travel
Media & Entertainmentfullstory-media-entertainment

Strategic Skills

TopicSkill Document
Getting Startedfullstory-getting-started
Privacy Strategyfullstory-privacy-strategy
Stable Selectorsfullstory-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:

  1. 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?
  2. 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
  3. 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
  4. Quick decision:
    • WHO = User Properties (works for anonymous users too!)
    • WHERE = Page Properties
    • WHAT (specific item) = Element Properties
    • WHAT HAPPENED = Events
  5. 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
  6. Decoration approach (ask the user!):
    • Framework approach: Build data attributes into shared components — zero effort for consuming developers, scales automatically
    • Individual Element approach: Add data attributes manually — works with any codebase, immediate results
    • Hybrid: Framework for shared components + Individual for custom elements (most common)
    • Use the Fullstory Data Attributes Guide convention: data-component, data-id, data-section, data-position, data-state
    • See fullstory-stable-selectors/SKILL.md for full guidance
  7. Mobile-specific considerations:
    • Implement the Pages API for screen-level context (equivalent to URL-based pages on web)
    • Send Events at critical places (purchases, registrations, A/B tests)
    • Set User Properties (loyalty tier, role) and Page Properties (brand, language)
    • Use FS.setAttribute for data attributes on mobile elements
    • See mobile-instrumentation-orchestrator/SKILL.md for sequencing

This meta-skill document provides strategic guidance for Fullstory data semantic decoration. Refer to individual API skill documents for detailed implementation examples.