Metadata & Deduping Examples
Learn how to use metadata effectively and understand deduplication strategies for reliable event tracking.
Stable Object IDs
Good: Stable, Meaningful IDs
html
<!-- Use stable, meaningful identifiers -->
<button
data-fanfest-track="cta_click"
data-fanfest-object-id="hero-cta-2024-blackfriday"
data-fanfest-description="Hero CTA"
>
Get Started
</button>Experience Check-ins with Stable IDs
Here's how to use stable IDs for experience check-ins:
vue
<template>
<!-- Experience check-in with stable ID -->
<div
v-for="experience in experiences"
:key="experience.id"
@click="handleClick(experience)"
>
<h3>{{ experience.title }}</h3>
<button>Check In</button>
</div>
</template>
<script setup>
function handleClick(experience) {
if (experience.qrCode) {
// Show QR code modal
modalData.value = experience;
showModal.value = true;
} else {
// Track experience check-in
window.FanFestSDK.trackEvent({
action: EngagementAction.EXPERIENCE_CHECK_IN,
description: experience.title,
externalExperienceId: experience.id, // Stable ID
externalObjectId: experience.id, // Same stable ID
metadata: {},
});
}
}
</script>jsx
// React: Stable object IDs
function ProductButton({ product }) {
const handleClick = async () => {
await FanFestSDK.trackEvent({
action: "product_click",
externalObjectId: product.slug, // Use slug, not ID
description: `Product: ${product.name}`,
metadata: {
category: product.category,
price: product.price,
},
});
};
return <button onClick={handleClick}>{product.name}</button>;
}Bad: DOM-Dependent IDs
html
<!-- Avoid DOM-dependent identifiers -->
<button
data-fanfest-track="cta_click"
data-fanfest-object-id="button-1" <!-- Bad: DOM-dependent -->
data-fanfest-description="Hero CTA">
Get Started
</button>Object Type Taxonomy
Content Types
jsx
// Blog posts
function BlogPost({ post }) {
const handleView = async () => {
await FanFestSDK.trackEvent({
action: "content_view",
description: post.title,
externalObjectId: post.slug,
metadata: {
objectType: "blog_post",
author: post.author,
publishDate: post.publishDate,
readTime: post.readTime,
},
});
};
return <article onClick={handleView}>{post.title}</article>;
}jsx
// Products
function ProductCard({ product }) {
const handleView = async () => {
await FanFestSDK.trackEvent({
action: "product_view",
description: product.name,
externalObjectId: product.sku,
metadata: {
objectType: "product",
category: product.category,
price: product.price,
inStock: product.inStock,
},
});
};
return <div onClick={handleView}>{product.name}</div>;
}jsx
// CTAs
function CTAButton({ cta }) {
const handleClick = async () => {
await FanFestSDK.trackEvent({
action: "cta_click",
description: cta.text,
externalObjectId: cta.id,
metadata: {
objectType: "cta",
position: cta.position,
campaign: cta.campaign,
},
});
};
return <button onClick={handleClick}>{cta.text}</button>;
}Metadata Strategies
JSON Metadata
html
<!-- JSON metadata in attributes -->
<button
data-fanfest-track="purchase"
data-fanfest-object-id="product-123"
data-fanfest-meta-campaign="BlackFriday"
data-fanfest-meta-amount="99.99"
data-fanfest-meta-currency="USD"
>
Buy Now
</button>jsx
// React: JSON metadata
function PurchaseButton({ product, campaign }) {
const handlePurchase = async () => {
await FanFestSDK.trackEvent({
action: "purchase",
externalObjectId: product.id,
metadata: {
campaign: campaign.name,
amount: product.price,
currency: "USD",
discount: campaign.discount,
category: product.category,
},
});
};
return <button onClick={handlePurchase}>Buy Now</button>;
}Key-Value Metadata
html
<!-- Key-value metadata -->
<button
data-fanfest-track="newsletter_signup"
data-fanfest-object-id="newsletter-form"
data-fanfest-meta-source="homepage"
data-fanfest-meta-position="hero"
data-fanfest-meta-campaign="winter2024"
>
Subscribe
</button>Deduplication Strategies
Client-Side Deduplication
jsx
function DeduplicatedButton() {
const [hasTracked, setHasTracked] = useState(false);
const [lastTracked, setLastTracked] = useState(null);
const handleClick = async () => {
const now = Date.now();
// Prevent rapid-fire clicks
if (lastTracked && now - lastTracked < 1000) {
return; // Ignore clicks within 1 second
}
if (!hasTracked) {
await FanFestSDK.trackEvent({
action: "first_click",
description: "First Click",
externalObjectId: "deduplicated-button",
});
setHasTracked(true);
}
setLastTracked(now);
};
return <button onClick={handleClick}>Click Me</button>;
}Session-Based Deduplication
jsx
function SessionDeduplicatedButton() {
const [trackedInSession, setTrackedInSession] = useState(new Set());
const handleClick = async (event) => {
const objectId = event.target.dataset.objectId;
if (!trackedInSession.has(objectId)) {
await FanFestSDK.trackEvent({
action: "session_click",
description: "Session Click",
externalObjectId: objectId,
});
setTrackedInSession((prev) => new Set([...prev, objectId]));
}
};
return (
<button onClick={handleClick} data-object-id="session-button">
Session Button
</button>
);
}Uniqueness Examples
Shopping Cart Items
jsx
function CartItem({ item }) {
const handleAddToCart = async () => {
await FanFestSDK.trackEvent({
action: "add_to_cart",
description: `Add ${item.name} to cart`,
externalObjectId: `${item.id}-${item.variant}`, // Unique per variant
metadata: {
productId: item.id,
variant: item.variant,
quantity: 1,
price: item.price,
},
});
};
return <button onClick={handleAddToCart}>Add to Cart</button>;
}Article Views
jsx
function ArticleView({ article }) {
const handleView = async () => {
await FanFestSDK.trackEvent({
action: "article_view",
description: article.title,
externalObjectId: article.slug, // Use slug for uniqueness
metadata: {
author: article.author,
publishDate: article.publishDate,
category: article.category,
readTime: article.readTime,
},
});
};
return <article onClick={handleView}>{article.title}</article>;
}CTA Tracking
jsx
function CTAButton({ cta, page }) {
const handleClick = async () => {
await FanFestSDK.trackEvent({
action: "cta_click",
description: cta.text,
externalObjectId: `${page}-${cta.id}`, // Unique per page
metadata: {
page: page,
position: cta.position,
campaign: cta.campaign,
ctaType: cta.type,
},
});
};
return <button onClick={handleClick}>{cta.text}</button>;
}Advanced Metadata Patterns
User Context Metadata
jsx
function ContextualButton({ user, product }) {
const handleClick = async () => {
await FanFestSDK.trackEvent({
action: "contextual_click",
description: "Contextual Button Click",
externalObjectId: product.id,
metadata: {
userType: user.isLoggedIn ? "authenticated" : "anonymous",
userTier: user.tier,
productCategory: product.category,
priceRange: getPriceRange(product.price),
sessionDuration: getSessionDuration(),
},
});
};
return <button onClick={handleClick}>Contextual Action</button>;
}A/B Testing Metadata
jsx
function ABTestButton({ variant, testId }) {
const handleClick = async () => {
await FanFestSDK.trackEvent({
action: "ab_test_click",
description: "A/B Test Button Click",
externalObjectId: `${testId}-${variant}`,
metadata: {
testId: testId,
variant: variant,
testGroup: getTestGroup(),
conversionGoal: "button_click",
},
});
};
return <button onClick={handleClick}>Test Button</button>;
}Performance Metadata
jsx
function PerformanceTrackedButton() {
const handleClick = async () => {
const startTime = performance.now();
// Simulate some work
await performWork();
const duration = performance.now() - startTime;
await FanFestSDK.trackEvent({
action: "performance_click",
description: "Performance Tracked Click",
externalObjectId: "performance-button",
metadata: {
duration: Math.round(duration),
userAgent: navigator.userAgent,
connectionType: getConnectionType(),
memoryUsage: getMemoryUsage(),
},
});
};
return <button onClick={handleClick}>Performance Button</button>;
}Error Handling with Metadata
Retry Metadata
jsx
function RetryTrackedButton() {
const [retryCount, setRetryCount] = useState(0);
const handleClick = async () => {
try {
await FanFestSDK.trackEvent({
action: "retry_click",
description: "Retry Button Click",
externalObjectId: "retry-button",
metadata: {
retryCount: retryCount,
timestamp: new Date().toISOString(),
},
});
await performAction();
} catch (error) {
setRetryCount((prev) => prev + 1);
// Track the error
await FanFestSDK.trackEvent({
action: "retry_error",
description: "Retry Error",
externalObjectId: "retry-button",
metadata: {
error: error.message,
retryCount: retryCount + 1,
},
});
}
};
return <button onClick={handleClick}>Retry Button</button>;
}Best Practices
Metadata Guidelines
- Use consistent naming for metadata keys
- Avoid PII in metadata
- Keep metadata lightweight for performance
- Use meaningful object IDs that don't change
- Include context that helps with analysis
Deduplication Guidelines
- Client-side deduplication for rapid-fire events
- Session-based deduplication for user actions
- Server-side deduplication for data integrity
- Timestamp windows for time-based deduplication
Next Steps
- Click Tracking - User interaction tracking
- Page Views - Page navigation tracking
- Event Modifiers - Advanced event handling
