---
name: fullstory-element-properties
version: v2
description: Comprehensive guide for implementing Fullstory's Element Properties API across Web, Android, and iOS platforms. Teaches proper type handling, schema construction, value formatting, and platform-specific patterns including view/cell reuse. Includes detailed good/bad examples for e-commerce, forms, and dynamic content to help developers add semantic decoration to their apps and capture business-relevant properties on interactive elements.
related_skills:
- fullstory-page-properties
- fullstory-user-properties
- fullstory-analytics-events
- fullstory-data-scoping-decoration
- fullstory-privacy-controls
- fullstory-ecommerce
- fullstory-saas
---
# Fullstory Element Properties API
## Overview
Fullstory's Element Properties API allows developers to capture custom properties on UI elements that can be used for search, filtering, grouping, and analytics. Unlike standard attributes which are only used for CSS selectors, element properties become first-class data points for analysis, similar to user properties, page properties, and event properties.
This skill covers implementation across Web (Browser), Android, and iOS platforms.
## Core Concepts
### API Defined Elements
- API Defined Elements provide a programmatic approach to defining Elements in Fullstory
- Once created, these elements can be used across Fullstory features for search and analysis
- Elements are defined using the `data-fs-element` attribute with a name
- Element names are permanently attached to their associated Element (though display name can change)
### Element Properties vs Attributes
- **Attributes**: Used for CSS selectors and element matching only
- **Element Properties**: Can be used for filtering, grouping, and analytics
- Properties inherit from parent elements in the hierarchy
- The deepest property value is used when conflicts occur
### Key Features
1. Properties are captured on element interactions (clicks, taps, etc.)
2. Properties inherit down the element hierarchy
3. Multiple `data-fs-properties-schema` can be set in the same hierarchy
4. Properties automatically connect to Named Elements
### ⭐ Critical: Property Inheritance (Parent ↔ Child)
This is one of the most powerful features of Element Properties:
**Two-way inheritance:**
- **Parent → Child**: Properties defined on a parent element are inherited by all child elements
- **Child → Parent**: When an action occurs on a parent element, it captures properties from all defined child elements
```
┌─────────────────────────────────────────────────────────────────────┐
│ FORM (data-fs-element="checkout-form") │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ SHIPPING SELECT (selectedShipping="express") │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PAYMENT SELECT (selectedPayment="credit_card") │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ GIFT WRAP CHECKBOX (giftWrap="true") │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ SUBMIT BUTTON (data-fs-element="submit-order") │ │
│ │ ─────────────────────────────────────────────────────── │ │
│ │ When clicked, captures ALL properties from siblings: │ │
│ │ • selectedShipping: "express" │ │
│ │ • selectedPayment: "credit_card" │ │
│ │ • giftWrap: true │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
**Why this matters for analytics:**
```html
```
**In Fullstory, when "submit-order" is clicked:**
- The click event captures: `selectedShipping`, `selectedPayment`, `giftWrap`
- You can now create metrics like:
- "Submit clicks WHERE selectedPayment = 'paypal'"
- "Group submit clicks BY selectedShipping" → see which shipping option is most popular
- "Funnel: visits → checkout-form viewed → submit-order clicked WHERE giftWrap = true"
> **Key Insight**: Define element properties on individual form fields, but the submit button (or any parent action element) will automatically capture the state of all child properties at the moment of interaction. This eliminates the need to manually aggregate form state.
## Platform-Specific Implementation
---
## WEB / BROWSER IMPLEMENTATION
### Basic Syntax
Element properties are defined using HTML data attributes:
- `data-fs-properties-schema`: JSON object defining which attributes to capture and their types
- `data-fs-element`: (Optional) Names the element for use in Fullstory
### Schema Structure
```json
{
"attribute-name": "type",
"attribute-name-2": {
"type": "type_value",
"name": "override_property_name"
}
}
```
### Supported Value Types
| Type | Description | Examples |
| ------- | --------------------- | -------------------------------------- |
| `str` | String value | "foo", "bar", "foo@bar" |
| `strs` | Array of strings | ["foo", "bar"] |
| `int` | Integer | 0, -123, 45 |
| `ints` | Array of integers | [0, 1, 2] |
| `real` | Float/decimal | 12.345, -0.5 |
| `reals` | Array of reals | [12.345, 1] |
| `bool` | Boolean | true, false, 1, 0, t, f |
| `bools` | Array of booleans | [true, false] |
| `date` | ISO8601 date/datetime | "2006-01-02T15:04:05Z" |
| `dates` | Array of dates | ["2006-01-02", "2006-01-02T15:04:05Z"] |
### ✅ GOOD WEB IMPLEMENTATION EXAMPLES
#### Example 1: E-commerce Product Card
```html
```
**Why this is good:**
- ✅ Proper type mapping (real for price, int for stock, bool for sale status)
- ✅ Clean property names using the `name` override
- ✅ Captures both scalar and array values appropriately
- ✅ Named element for easy reference in Fullstory
- ✅ Child button inherits all parent properties
- ✅ Uses semantic attribute names that describe the data
#### Example 2: Form with Hierarchy
```html
```
**Why this is good:**
- ✅ Hierarchical property inheritance (form properties flow to all children)
- ✅ Each element has appropriate context-specific properties
- ✅ All interactions capture both element-specific and form-level context
- ✅ Clear separation of concerns (form context vs field specifics)
#### Example 3: Dynamic Content (React/Vue Pattern)
```javascript
// GOOD: Dynamically setting properties in a React component
function ProductCard({product}) {
const schemaRef = useRef(null)
useEffect(() => {
if (schemaRef.current) {
// Set the schema as a data attribute
schemaRef.current.setAttribute(
'data-fs-properties-schema',
JSON.stringify({
'data-product-id': {type: 'str', name: 'productId'},
'data-product-name': {type: 'str', name: 'productName'},
'data-price': {type: 'real', name: 'price'},
'data-in-stock': {type: 'bool', name: 'inStock'},
'data-variant-count': {type: 'int', name: 'variantCount'},
}),
)
// Set the actual attribute values
schemaRef.current.setAttribute('data-product-id', product.id)
schemaRef.current.setAttribute('data-product-name', product.name)
schemaRef.current.setAttribute('data-price', product.price.toString())
schemaRef.current.setAttribute('data-in-stock', product.inStock.toString())
schemaRef.current.setAttribute('data-variant-count', product.variants.length.toString())
schemaRef.current.setAttribute('data-fs-element', 'Product Card')
}
}, [product])
return (
{/* Component content */}
)
}
```
**Why this is good:**
- ✅ Properly handles dynamic data
- ✅ Sets schema first, then values
- ✅ Converts values to strings as required
- ✅ Uses appropriate types for each property
- ✅ Includes element naming
### ❌ BAD WEB IMPLEMENTATION EXAMPLES
#### Example 1: Common Type Mistakes
```html
```
**Why this is bad:**
- ❌ `data-price` contains "$" currency symbol but typed as string (should be real and clean numeric: "199.99")
- ❌ `data-quantity` contains "items" text but typed as int (should be clean number: "5")
- ❌ `data-available` uses "yes" instead of boolean value (should be "true" or "1")
- ❌ Lost ability to do numeric filtering, comparisons, and aggregations
**CORRECTED VERSION:**
```html
```
#### Example 2: Over-capturing and Schema Bloat
```html
```
**Why this is bad:**
- ❌ Captures technical/debug data not useful for analytics (component version, git commit, developer)
- ❌ Captures implementation details (react-key, css-class)
- ❌ Wastes property quota (limit of 50 per interaction, 500 total)
- ❌ Creates high cardinality issues (timestamp, git commits)
- ❌ Makes actual business data harder to find
- ❌ No meaningful names provided
**CORRECTED VERSION:**
```html
```
#### Example 3: Invalid JSON and Syntax Errors
```html
```
**Why this is bad:**
- ❌ Mixed single and double quotes in JSON (invalid JSON)
- ❌ JSON will fail to parse
- ❌ Properties won't be captured at all
- ❌ Silent failure - no console errors
**CORRECTED VERSION:**
```html
```
#### Example 4: Missing Property Values
```html
```
**Why this is bad:**
- ❌ Schema references `data-product-name`, `data-price`, `data-category` but these attributes don't exist
- ❌ Only `data-product-id` will be captured
- ❌ Incomplete data reduces analytics value
- ❌ Creates confusion about what's actually being captured
**CORRECTED VERSION:**
```html
```
---
## ANDROID IMPLEMENTATION
### Basic Syntax
Element properties are set using the `FS.setAttribute()` method:
**Java:**
```java
public static void FS.setAttribute(View view, String attributeName, String attributeValue);
```
**Kotlin:**
```kotlin
fun FS.setAttribute(view: View, attributeName: String, attributeValue: String)
```
### ✅ GOOD ANDROID IMPLEMENTATION EXAMPLES
#### Example 1: Product Card with Comprehensive Properties (Java)
```java
// GOOD: Complete product card implementation with proper typing
public class ProductCardViewHolder extends RecyclerView.ViewHolder {
private View cardView;
public void bind(Product product) {
cardView = itemView.findViewById(R.id.product_card);
// Set individual attribute values
FS.setAttribute(cardView, "data-product-id", product.getId());
FS.setAttribute(cardView, "data-product-name", product.getName());
FS.setAttribute(cardView, "data-category", product.getCategory());
FS.setAttribute(cardView, "data-price", String.valueOf(product.getPrice()));
FS.setAttribute(cardView, "data-stock-level", String.valueOf(product.getStockLevel()));
FS.setAttribute(cardView, "data-rating", String.valueOf(product.getRating()));
FS.setAttribute(cardView, "data-on-sale", String.valueOf(product.isOnSale()));
FS.setAttribute(cardView, "data-launch-date", product.getLaunchDate().toString()); // ISO8601 format
// Set the properties schema - defines types for all attributes
String schema = new JSONObject()
.put("data-product-id", new JSONObject()
.put("type", "str")
.put("name", "productId"))
.put("data-product-name", new JSONObject()
.put("type", "str")
.put("name", "productName"))
.put("data-category", new JSONObject()
.put("type", "str")
.put("name", "category"))
.put("data-price", new JSONObject()
.put("type", "real")
.put("name", "price"))
.put("data-stock-level", new JSONObject()
.put("type", "int")
.put("name", "stockLevel"))
.put("data-rating", new JSONObject()
.put("type", "real")
.put("name", "rating"))
.put("data-on-sale", new JSONObject()
.put("type", "bool")
.put("name", "isOnSale"))
.put("data-launch-date", new JSONObject()
.put("type", "date")
.put("name", "launchDate"))
.toString();
FS.setAttribute(cardView, "data-fs-properties-schema", schema);
// Name the element for easy reference in Fullstory
FS.setAttribute(cardView, "data-fs-element", "Product Card");
// Configure child button - will inherit parent properties
Button addToCartBtn = itemView.findViewById(R.id.btn_add_to_cart);
FS.setAttribute(addToCartBtn, "data-fs-element", "Add to Cart Button");
}
}
```
**Why this is good:**
- ✅ Uses proper type conversions (String.valueOf for numbers/booleans)
- ✅ Provides clean property names via the `name` field
- ✅ Sets schema AFTER all attribute values are set
- ✅ Uses JSONObject for clean schema construction
- ✅ Child elements automatically inherit parent properties
- ✅ Includes element naming for both parent and child
#### Example 2: Form with Field-Level Properties (Kotlin)
```kotlin
// GOOD: Comprehensive form tracking with hierarchy
class CheckoutFormFragment : Fragment() {
private fun setupFormTracking() {
val formContainer = view?.findViewById(R.id.checkout_form)
// Set form-level properties that all fields will inherit
formContainer?.let { form ->
FS.setAttribute(form, "data-form-type", "checkout")
FS.setAttribute(form, "data-form-step", "payment")
FS.setAttribute(form, "data-user-tier", "premium")
FS.setAttribute(form, "data-total-amount", viewModel.cartTotal.toString())
val formSchema = JSONObject().apply {
put("data-form-type", JSONObject().put("type", "str").put("name", "formType"))
put("data-form-step", JSONObject().put("type", "str").put("name", "checkoutStep"))
put("data-user-tier", JSONObject().put("type", "str").put("name", "userTier"))
put("data-total-amount", JSONObject().put("type", "real").put("name", "cartTotal"))
}.toString()
FS.setAttribute(form, "data-fs-properties-schema", formSchema)
FS.setAttribute(form, "data-fs-element", "Checkout Form")
}
// Set field-specific properties
val cardNumberField = view?.findViewById(R.id.card_number_field)
cardNumberField?.let { field ->
FS.setAttribute(field, "data-field-name", "cardNumber")
FS.setAttribute(field, "data-field-required", "true")
FS.setAttribute(field, "data-field-type", "credit-card")
FS.setAttribute(field, "data-max-length", "16")
val fieldSchema = JSONObject().apply {
put("data-field-name", JSONObject().put("type", "str").put("name", "fieldName"))
put("data-field-required", JSONObject().put("type", "bool").put("name", "isRequired"))
put("data-field-type", JSONObject().put("type", "str").put("name", "fieldType"))
put("data-max-length", JSONObject().put("type", "int").put("name", "maxLength"))
}.toString()
FS.setAttribute(field, "data-fs-properties-schema", fieldSchema)
FS.setAttribute(field, "data-fs-element", "Card Number Field")
}
// Submit button inherits form properties
val submitButton = view?.findViewById