supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
fullstory-anonymize-users
Comprehensive guide for implementing Fullstory's User Anonymization API (setIdentity with anonymous:true) for web applications. Teaches proper logout handling, session management, privacy compliance, and user switching scenarios. Includes detailed good/bad examples for logout flows, multi-user ap...
Full skill instructions
Fullstory's Anonymize Users API allows developers to release the identity of the current user and create a new anonymous session. This is essential for:
When you call FS('setIdentity', { anonymous: true }), the current session ends and a fresh anonymous session begins. The previously identified user remains in Fullstory's records, but subsequent activity is no longer linked to them.
fs_uid cookie is generated - breaking the link to all previous sessionssetIdentity callCookie Behavior: Normally, the
fs_uidfirst-party cookie (1-year expiry) links all sessions from the same browser together. Whenanonymizeis called, Fullstory generates a newfs_uidcookie, effectively creating a "new device" from Fullstory's perspective. Any futuresetIdentitycalls will only merge sessions from the new cookie, not the old one.Reference: Why Fullstory uses First-Party Cookies
┌─────────────┐ Login ┌─────────────┐ Logout ┌─────────────┐
│ Anonymous │ ───────► │ Identified │ ───────► │ New Anon │
│ Session A │ │ Session B │ │ Session C │
└─────────────┘ └─────────────┘ └─────────────┘
│
setIdentity │ setIdentity
(uid: 'xxx') │ (anonymous: true)
| Scenario | Should Anonymize? | Reason |
|---|---|---|
| User logs out | ✅ Yes | Prevents session attribution to wrong user |
| User switches accounts | ✅ Yes | Clean slate before new identification |
| User requests data deletion | ❓ Consider | Part of broader privacy implementation |
| User clears browser data | ❌ No | Fullstory handles this automatically |
| Page navigation | ❌ No | Identity persists across pages |
| Session timeout | ❓ Depends | Based on your security requirements |
FS('setIdentity', {anonymous: true})
| Parameter | Type | Required | Description |
|---|---|---|---|
anonymous | boolean | Yes | Must be true to anonymize the user |
await FS('setIdentityAsync', {anonymous: true})
// GOOD: Proper logout with Fullstory anonymization
async function handleLogout() {
try {
// 1. Call your backend logout endpoint
await fetch('/api/auth/logout', {method: 'POST'})
// 2. Clear local authentication state
clearAuthTokens()
clearUserState()
// 3. Anonymize in Fullstory BEFORE redirecting
FS('setIdentity', {anonymous: true})
// 4. Redirect to login page
window.location.href = '/login'
} catch (error) {
console.error('Logout failed:', error)
// Still anonymize even if backend fails
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
}
Why this is good:
// GOOD: React hook pattern for logout with Fullstory
import {useCallback} from 'react'
import {useAuth} from './auth-context'
import {useNavigate} from 'react-router-dom'
function useLogout() {
const {clearAuth} = useAuth()
const navigate = useNavigate()
const logout = useCallback(async () => {
// Track the logout action before anonymizing
FS('trackEvent', {
name: 'User Logged Out',
properties: {
logoutMethod: 'manual',
sessionDuration: getSessionDuration(),
},
})
// Clear application auth state
await clearAuth()
// Anonymize the Fullstory session
FS('setIdentity', {anonymous: true})
// Navigate to home/login
navigate('/login')
}, [clearAuth, navigate])
return logout
}
// Usage
function LogoutButton() {
const logout = useLogout()
return <button onClick={logout}>Sign Out</button>
}
Why this is good:
// GOOD: Clean account switching with proper session boundaries
async function switchAccount(newAccountId) {
const newUser = await fetchAccountDetails(newAccountId)
// Track the switch event under current user
FS('trackEvent', {
name: 'Account Switch Initiated',
properties: {
targetAccountId: newAccountId,
},
})
// Step 1: Anonymize current session
FS('setIdentity', {anonymous: true})
// Step 2: Set up new account context
await setupAccountContext(newUser)
// Step 3: Identify as new user
FS('setIdentity', {
uid: newUser.id,
properties: {
displayName: newUser.name,
email: newUser.email,
accountType: newUser.type,
},
})
// Refresh UI
window.location.reload()
}
Why this is good:
// GOOD: Handling session timeout with Fullstory
class SessionManager {
constructor() {
this.timeoutDuration = 30 * 60 * 1000 // 30 minutes
this.timeoutId = null
this.lastActivity = Date.now()
}
startTimeout() {
this.resetTimeout()
document.addEventListener('click', () => this.resetTimeout())
document.addEventListener('keypress', () => this.resetTimeout())
}
resetTimeout() {
this.lastActivity = Date.now()
if (this.timeoutId) clearTimeout(this.timeoutId)
this.timeoutId = setTimeout(() => {
this.handleSessionTimeout()
}, this.timeoutDuration)
}
async handleSessionTimeout() {
// Track timeout event while still identified
FS('trackEvent', {
name: 'Session Timeout',
properties: {
inactivityDuration: Date.now() - this.lastActivity,
lastPage: window.location.pathname,
},
})
// Anonymize the session
FS('setIdentity', {anonymous: true})
// Clear auth and redirect
clearAuthState()
showTimeoutModal()
}
}
Why this is good:
// GOOD: "Incognito mode" toggle for privacy-conscious users
class PrivacyManager {
constructor() {
this.isIncognitoMode = false
this.originalUserId = null
}
async enableIncognitoMode(currentUserId) {
// Store original user ID for potential re-identification
this.originalUserId = currentUserId
this.isIncognitoMode = true
// Track before anonymizing
FS('trackEvent', {
name: 'Incognito Mode Enabled',
properties: {},
})
// Anonymize - activity won't be linked to user
FS('setIdentity', {anonymous: true})
// Update UI
showIncognitoIndicator()
}
async disableIncognitoMode() {
if (!this.originalUserId) return
this.isIncognitoMode = false
// Re-identify user
const user = await getCurrentUser()
FS('setIdentity', {
uid: user.id,
properties: {
displayName: user.name,
email: user.email,
},
})
// Track re-enablement
FS('trackEvent', {
name: 'Incognito Mode Disabled',
properties: {},
})
hideIncognitoIndicator()
this.originalUserId = null
}
}
Why this is good:
// BAD: No Fullstory anonymization on logout
function handleLogout() {
clearAuthTokens()
clearUserState()
window.location.href = '/login'
// Missing FS('setIdentity', { anonymous: true })!
}
Why this is bad:
CORRECTED VERSION:
// GOOD: Include Fullstory anonymization
function handleLogout() {
clearAuthTokens()
clearUserState()
// Anonymize before redirect
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
// BAD: Anonymizing after navigation starts
function handleLogout() {
window.location.href = '/login'
// BAD: This may never execute - page is already navigating!
FS('setIdentity', {anonymous: true})
}
Why this is bad:
CORRECTED VERSION:
// GOOD: Anonymize BEFORE navigation
async function handleLogout() {
// Anonymize first
await FS('setIdentityAsync', {anonymous: true})
// Then navigate
window.location.href = '/login'
}
// BAD: Calling anonymize multiple times
function handleLogout() {
// Excessive calls
FS('setIdentity', {anonymous: true})
FS('setIdentity', {anonymous: true})
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
Why this is bad:
CORRECTED VERSION:
// GOOD: Single anonymization call
function handleLogout() {
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
// BAD: Wrong way to anonymize
FS('setIdentity', {uid: null}) // BAD: uid shouldn't be null
FS('setIdentity', {uid: 'anonymous'}) // BAD: This identifies as user "anonymous"!
FS('setIdentity', {uid: ''}) // BAD: Empty string uid
FS('setIdentity', {}) // BAD: Missing required parameters
Why this is bad:
CORRECTED VERSION:
// GOOD: Proper anonymization syntax
FS('setIdentity', {anonymous: true})
// BAD: Missing opportunity to track logout event
function handleLogout() {
// Just anonymizing without capturing useful data
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
Why this is bad:
CORRECTED VERSION:
// GOOD: Track event before anonymizing
function handleLogout() {
// Track while still identified
FS('trackEvent', {
name: 'User Logged Out',
properties: {
logoutMethod: 'user_initiated',
pageAtLogout: window.location.pathname,
},
})
// Then anonymize
FS('setIdentity', {anonymous: true})
window.location.href = '/login'
}
// BAD: Using anonymization to "hide" errors
function handleError(error) {
// Don't use anonymization to hide error attribution!
FS('setIdentity', {anonymous: true})
console.error(error)
}
Why this is bad:
CORRECTED VERSION:
// GOOD: Log errors while identified, only anonymize on logout
function handleError(error) {
// Track the error - attribution helps debugging!
FS('trackEvent', {
name: 'Application Error',
properties: {
errorMessage: error.message,
errorCode: error.code,
page: window.location.pathname,
},
})
// Show error UI without anonymizing
showErrorMessage(error)
}
// Centralized logout service
class LogoutService {
static async logout(options = {}) {
const {trackEvent = true, redirectUrl = '/login', reason = 'user_initiated'} = options
// Track logout if requested
if (trackEvent) {
FS('trackEvent', {
name: 'User Logged Out',
properties: {
reason: reason,
sessionDuration: getSessionDuration(),
},
})
}
// Backend logout
try {
await fetch('/api/logout', {method: 'POST'})
} catch (e) {
console.warn('Backend logout failed:', e)
}
// Clear client state
clearAuthTokens()
clearUserState()
clearLocalStorage()
// Anonymize Fullstory
FS('setIdentity', {anonymous: true})
// Redirect
if (redirectUrl) {
window.location.href = redirectUrl
}
}
}
// Usage
await LogoutService.logout()
await LogoutService.logout({reason: 'session_timeout', redirectUrl: '/timeout'})
// For apps with workspace/tenant switching
class TenantManager {
async switchTenant(newTenantId) {
const currentUser = getCurrentUser()
// Track switch under current context
FS('trackEvent', {
name: 'Tenant Switch',
properties: {
fromTenant: currentUser.tenantId,
toTenant: newTenantId,
},
})
// Start fresh session for new tenant context
FS('setIdentity', {anonymous: true})
// Update tenant context
await loadTenantContext(newTenantId)
// Re-identify with new tenant context
FS('setIdentity', {
uid: currentUser.id,
properties: {
displayName: currentUser.name,
email: currentUser.email,
tenantId: newTenantId,
tenantName: await getTenantName(newTenantId),
},
})
}
}
// For shared devices like kiosks
class KioskMode {
sessionTimeout = 5 * 60 * 1000 // 5 minutes
async startSession(user) {
FS('setIdentity', {
uid: user.id,
properties: {
displayName: user.name,
deviceMode: 'kiosk',
locationId: getKioskLocation(),
},
})
// Auto-logout after timeout
setTimeout(() => this.endSession(), this.sessionTimeout)
}
async endSession() {
FS('trackEvent', {
name: 'Kiosk Session Ended',
properties: {
endReason: 'timeout',
location: getKioskLocation(),
},
})
FS('setIdentity', {anonymous: true})
// Reset to welcome screen
showWelcomeScreen()
}
}
// setProperties updates user info without changing identity
FS('setProperties', {
type: 'user',
properties: {lastActiveAt: new Date().toISOString()},
})
// anonymous: true ends the session entirely
FS('setIdentity', {anonymous: true}) // New session starts
// Common pattern: switch users
FS('setIdentity', {anonymous: true}) // End session 1
// Some time passes, new user logs in
FS('setIdentity', {
// Start session 2
uid: newUser.id,
properties: {displayName: newUser.name},
})
Symptom: New user activity appears under old user
Common Causes:
Solutions:
{ anonymous: true } syntaxSymptom: User has many short, fragmented sessions
Common Causes:
Solutions:
When helping developers implement User Anonymization:
Always emphasize:
{ anonymous: true } syntax exactlyCommon mistakes to watch for:
Questions to ask developers:
Integration considerations:
This skill document was created to help Agent understand and guide developers in implementing Fullstory's User Anonymization API correctly for web applications.
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content