--- name: fullstory-privacy-controls version: v2 description: Comprehensive guide for implementing Fullstory's Element Privacy Controls (fs-exclude, fs-mask, fs-unmask) for web applications. Teaches proper privacy class usage, understanding what data leaves the device, CSS selector rules, and Form Privacy features. Includes detailed good/bad examples for protecting sensitive elements while maintaining session replay utility. related_skills: - fullstory-privacy-strategy - fullstory-user-consent - fullstory-element-properties - fullstory-capture-control - fullstory-banking - fullstory-healthcare - fullstory-gaming - fullstory-ecommerce --- # Fullstory Privacy Controls API ## Overview Fullstory's Privacy Controls allow developers to control what data is captured and sent to Fullstory servers. This is implemented through CSS classes that define how elements and their content are treated during session recording. **Critical Understanding**: Privacy controls operate at the DOM level in the user's browser: - **Excluded content**: Never leaves the user's device at all - completely ignored - **Masked content**: The **actual text never leaves the device**. It is replaced locally (in the browser) with a wireframe approximation before anything is sent to Fullstory's servers. Fullstory only receives the wireframed placeholder, never the original text. ## Core Concepts ### The Three Privacy Modes | Mode | CSS Class | Data Leaves Device | Events Captured | Best For | | ----------- | ------------- | ------------------------------------------------------------ | --------------- | ----------------------- | | **Exclude** | `.fs-exclude` | ❌ Nothing | ❌ No | Regulated data, secrets | | **Mask** | `.fs-mask` | ⚠️ Structure only (text **never** sent - wireframed locally) | ✅ Yes | PII, names, emails | | **Unmask** | `.fs-unmask` | ✅ Everything | ✅ Yes | Public content | ### Privacy Hierarchy (Most → Least Restrictive) ``` ┌─────────────────────────────────────────────────────┐ │ EXCLUDE (.fs-exclude) │ │ - Element completely ignored │ │ - Events targeting element ignored │ │ - Gray crosshatch in replay │ │ - Nothing sent to Fullstory │ ├─────────────────────────────────────────────────────┤ │ MASK (.fs-mask) │ │ - Actual text NEVER leaves device │ │ - Replaced locally with wireframe approximation │ │ - Only wireframe sent to Fullstory servers │ │ - Element structure sent (knows what was clicked) │ │ - Events captured │ │ - Text appears as "████████" in replay │ ├─────────────────────────────────────────────────────┤ │ UNMASK (.fs-unmask) │ │ - Full text and content captured │ │ - Everything visible in replay │ │ - Default mode (unless Private by Default) │ └─────────────────────────────────────────────────────┘ ``` ### Key Technical Facts 1. **Local Processing**: All privacy filtering happens in the browser before data is sent 2. **Inheritance**: Children inherit parent's privacy class 3. **Override**: Child can unmask/mask within a masked/excluded parent (with exceptions) 4. **Strictest Wins**: When multiple rules match, the most restrictive applies 5. **CSS Selector Based**: Rules can be defined via classes or CSS selectors in Settings ### Default Exclusions Fullstory automatically excludes: - `input[type=password]` - All password fields - `[autocomplete^=cc-]` - Credit card fields (number, CVV, expiry) - `input[type=hidden]` - Hidden inputs --- ## Private by Default Mode Fullstory offers a **Private by Default** mode that inverts the default capture behavior for maximum privacy protection. ### How Private by Default Works | Mode | Default Behavior | When to Use | | ---------------------- | --------------------------------------------------- | -------------------------------------------------- | | **Standard** | Everything captured (unmask) unless excluded/masked | Low-sensitivity applications (marketing sites) | | **Private by Default** | Everything masked unless explicitly unmasked | Sensitive applications (banking, healthcare, SaaS) | ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ STANDARD MODE (Default) │ │ └── All content visible → Add fs-mask/fs-exclude to protect │ ├─────────────────────────────────────────────────────────────────────────┤ │ PRIVATE BY DEFAULT MODE │ │ └── All content masked → Add fs-unmask to reveal safe content │ │ │ │ With Private by Default enabled: │ │ • No text is captured unless explicitly unmasked │ │ • Session replay shows wireframes everywhere │ │ • Zero risk of accidentally capturing sensitive data │ │ • Selectively unmask navigation, buttons, product info │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### Enabling Private by Default Private by Default is enabled via Fullstory Support or during account setup: 1. **New accounts**: Choose "Private by Default" during onboarding wizard 2. **Existing accounts**: Contact [Fullstory Support](https://help.fullstory.com/hc/en-us/requests/new) to enable > **⚠️ Warning for existing accounts**: Enabling Private by Default may break existing segments, event funnels, or Conversions that rely on text elements. Coordinate with your analytics team before enabling. ### When to Use Private by Default | Scenario | Recommendation | | ---------------------------------- | ------------------------------------------ | | **Healthcare applications** | ✅ Highly recommended | | **Banking/financial services** | ✅ Highly recommended | | **Applications with heavy PII** | ✅ Highly recommended | | **Enterprise SaaS (multi-tenant)** | ⚠️ Recommended | | **E-commerce (product pages)** | ⚠️ Consider - may need extensive unmasking | | **Marketing/content sites** | ❌ Probably overkill | ### Unmasking Strategy for Private by Default When Private by Default is enabled, use `.fs-unmask` to reveal safe content: ```html

Product Name

$99.99

Name: John Smith

Email: john@example.com

``` ### Using CSS Selectors for Bulk Unmasking Instead of adding classes to every element, use CSS selector rules in Settings: ```css /* Unmask all navigation links */ nav a /* Unmask all product titles */ .product-card h2, .product-card h3 /* Unmask all prices */ .price, [data-price], .product-price /* Unmask all buttons */ button, .btn, [role="button"] /* Unmask error messages */ .error-message, .alert, [role="alert"] ``` > **Reference**: [Fullstory Private by Default](https://help.fullstory.com/hc/en-us/articles/360044349073-Fullstory-Private-by-Default) --- ## API Reference ### CSS Classes ```html
...
...
...
``` ### Legacy Classes (Still Supported) ```html
...
``` ### ⚠️ BUILD TOOL WARNING Modern CSS build tools may remove "unused" classes, breaking privacy controls: ```javascript // DANGER: CSS purge tools might remove fs-* classes! // In tailwind.config.js, postcss.config.js, etc., safelist these classes: module.exports = { safelist: [ 'fs-exclude', 'fs-mask', 'fs-unmask', 'fs-block', // legacy ], } ``` **Always verify** privacy classes survive your build process by: 1. Inspecting production HTML for `fs-*` classes 2. Testing in Fullstory to confirm masking/exclusion works 3. Using CSS selector rules in Settings as backup --- ## ✅ GOOD IMPLEMENTATION EXAMPLES ### Example 1: Protecting Password Fields ```html
``` **Why this is good:** - ✅ Explicit exclusion (doesn't rely on auto-detection) - ✅ Password never leaves device - ✅ Login button clicks still captured - ✅ Email field still visible for debugging ### Example 2: Masking User Profile Information ```html
User avatar

John Smith

john.smith@company.com

Saved Payment Methods

**** **** **** 4242
``` **Why this is good:** - ✅ Name and email masked (visible structure, no text) - ✅ Action buttons fully visible for UX analysis - ✅ Payment info completely excluded (not even structure) - ✅ Avatar images still visible for context ### Example 3: Healthcare Form with Mixed Privacy ```html
Medical History
Appointment Preferences
``` **Why this is good:** - ✅ PHI (medications, conditions) completely excluded - ✅ HIPAA compliance - no health data leaves device - ✅ Non-PHI preferences masked (structure visible) - ✅ Navigation buttons visible for funnel analysis ### Example 4: E-commerce Checkout with Granular Privacy ```html

Your Order

Premium Widget x 2 $99.98
Total: $109.98

Shipping To

John Smith

123 Main Street

San Francisco, CA 94102

Payment Method

``` **Why this is good:** - ✅ Order details visible (useful for conversion analysis) - ✅ Shipping PII masked but structure shows flow - ✅ Payment card data completely excluded (PCI compliance) - ✅ Actions visible for funnel analysis ### Example 5: Search Results with Selective Masking ```html
Results for: "wireless headphones" 50 results
Wireless Headphones

Premium Wireless Headphones

$199.99
Sarah J. Great product! ★★★★★
``` **Why this is good:** - ✅ Search terms visible for search analytics - ✅ Product info visible for conversion analysis - ✅ Reviewer names/content masked (privacy) - ✅ Ratings still visible (useful for UX) ### Example 6: Component Library with Built-in Privacy ```javascript // GOOD: React component library with privacy classes built-in import React from 'react'; // Text Input - automatically masked export function TextInput({ label, sensitive = false, ...props }) { const privacyClass = sensitive ? 'fs-exclude' : 'fs-mask'; return (
); } // Password Input - always excluded export function PasswordInput({ label, ...props }) { return (
); } // Credit Card Input - always excluded export function CreditCardInput({ label, ...props }) { return (
); } // Public content - explicitly unmasked export function PublicContent({ children }) { return (
{children}
); } // Usage function CheckoutForm() { return (
{/* Masked */} {/* Masked */} {/* Excluded */} {/* Excluded */} {/* Visible */} ); } ``` **Why this is good:** - ✅ Privacy built into component library - ✅ Consistent application across app - ✅ Sensitive prop for high-risk fields - ✅ Developers don't need to remember privacy classes --- ## ❌ BAD IMPLEMENTATION EXAMPLES ### Example 1: Over-Excluding (Losing Analytics Value) ```html

Checkout

...
...
``` **Why this is bad:** - ❌ No visibility into checkout flow - ❌ Can't analyze conversion funnel - ❌ Button clicks not captured - ❌ Overkill - only payment needs exclusion **CORRECTED VERSION:** ```html

Checkout

...
...
``` ### Example 2: Relying Only on Auto-Detection ```html
``` **Why this is bad:** - ❌ SSN field not auto-detected (no `type=password`) - ❌ Credit card not auto-detected (no `autocomplete=cc-number`) - ❌ PIN code sent in clear text - ❌ Relying on convention without verification **CORRECTED VERSION:** ```html
``` ### Example 3: Masking When Should Exclude ```html ``` **Why this is bad:** - ❌ Mask only hides text, structure is still sent - ❌ Financial data should be excluded entirely - ❌ Even wireframe could reveal sensitive info format - ❌ Regulatory risk (PCI, etc.) **CORRECTED VERSION:** ```html ``` ### Example 4: Forgetting Child Elements ```html

Account Details

Name: John Smith

Email: john@example.com

Card: **** **** **** 4242

Expiry: 12/25

``` **Why this is bad:** - ❌ Payment info only masked, not excluded - ❌ Card structure visible - ❌ Could infer card type from structure - ❌ PCI compliance issue **CORRECTED VERSION:** ```html

Account Details

Name: John Smith

Email: john@example.com

Card: **** **** **** 4242

Expiry: 12/25

``` ### Example 5: Dynamic Content Without Privacy Classes ```javascript // BAD: Dynamically added content without privacy consideration function showUserDetails(user) { const html = `

Name: ${user.name}

Email: ${user.email}

Phone: ${user.phone}

SSN: ${user.ssn}

` // No privacy classes! All content visible document.body.insertAdjacentHTML('beforeend', html) } ``` **Why this is bad:** - ❌ Dynamically created content has no privacy - ❌ SSN exposed in plain text - ❌ All PII visible in replay **CORRECTED VERSION:** ```javascript // GOOD: Apply privacy classes to dynamic content function showUserDetails(user) { const html = `

Name: ${user.name}

Email: ${user.email}

Phone: ${user.phone}

SSN: ${user.ssn}

` document.body.insertAdjacentHTML('beforeend', html) } ``` ### Example 6: Console Logging Sensitive Data ```javascript // BAD: Logging sensitive data to console (captured by FS) function processPayment(cardNumber, cvv) { console.log('Processing payment:', cardNumber, cvv) // BAD! console.log('User SSN:', user.ssn) // BAD! // Process payment... } ``` **Why this is bad:** - ❌ Console logs are captured by Fullstory - ❌ Card number and CVV in console - ❌ SSN in console - ❌ Privacy classes don't affect console **CORRECTED VERSION:** ```javascript // GOOD: Never log sensitive data, or disable console capture function processPayment(cardNumber, cvv) { console.log('Processing payment for card ending:', cardNumber.slice(-4)) // Or use FS.log which you control: FS('log', { level: 'info', msg: 'Payment processing initiated', }) // Process payment... } // Or disable console capture in Fullstory settings ``` --- ## PRIVACY MODE COMPARISON ### What Gets Captured? | Content Type | Exclude | Mask | Unmask | | ---------------------------- | ------- | -------------------------------- | ------ | | Element exists | ❌ | ✅ | ✅ | | Element position | ❌ | ✅ | ✅ | | Element size | ❌ | ✅ | ✅ | | Element type (button, input) | ❌ | ✅ | ✅ | | Text content | ❌ | ❌ (never sent - wireframe only) | ✅ | | Input values | ❌ | ❌ (never sent - wireframe only) | ✅ | | Images | ❌ | ✅ | ✅ | | Click events | ❌ | ✅ | ✅ | | Form change events | ❌ | ✅ | ✅ | | CSS classes | ❌ | ✅ | ✅ | ### When to Use Each Mode | Use Case | Recommended Mode | Reason | | -------------------- | ---------------- | ------------------------------ | | Passwords | **Exclude** | Never capture credentials | | Credit card numbers | **Exclude** | PCI compliance | | SSN / Tax IDs | **Exclude** | Regulatory (cannot be masked) | | Bank account numbers | **Exclude** | Financial data | | Medical conditions | **Exclude** | HIPAA - even structure reveals | | User names | Mask | PII but structure useful | | Email addresses | Mask | PII but interaction useful | | Phone numbers | Mask | PII but structure useful | | Street addresses | Mask | PII but form flow useful | | Search terms | Unmask | Analytics value, not PII | | Product names | Unmask | Business data, not PII | | Navigation/buttons | Unmask | UX analysis | | Error messages | Unmask | Debugging | --- ## CSS SELECTOR RULES (Settings UI) In addition to CSS classes, you can define rules via Settings > Data Capture and Privacy > Privacy. ### Supported Selectors ```css /* Tag name */ input /* Class */ .sensitive-field /* ID */ #credit-card-input /* Attribute */ [data-sensitive="true"] [autocomplete^="cc-"] /* Descendant */ .checkout-form input /* Child */ .payment > input /* Multiple classes */ .form-field.sensitive ``` ### Unsupported Selectors ```css /* NOT supported in Fullstory privacy rules */ :hover :focus :nth-child() ::before ::after :not() ``` ### Example Rules in Settings | Selector | Rule Type | Purpose | | ----------------------------- | --------- | ------------------- | | `input[type=password]` | Exclude | All password fields | | `.pii-field` | Mask | Custom PII class | | `.payment-section input` | Exclude | All payment inputs | | `[data-fs-privacy="exclude"]` | Exclude | Attribute-based | | `.public-content` | Unmask | Public areas | --- ## FORM PRIVACY FEATURE Fullstory's Form Privacy feature (accounts created after Nov 10, 2021) provides additional automatic protection: ### Automatically Protected - Input fields without explicit unmask - Textarea content - Select/dropdown selected values - Content editable elements ### Form Privacy Modes | Mode | Behavior | | -------- | --------------------------------- | | Strict | All form inputs masked by default | | Standard | Common sensitive fields masked | | Off | Rely on manual privacy classes | --- ## TROUBLESHOOTING ### Content Still Visible After Excluding **Symptom**: Added `.fs-exclude` but content still appears **Common Causes**: 1. ❌ CSS specificity issue 2. ❌ Class added after page load 3. ❌ Rule not propagating to children 4. ❌ Conflicting unmask rule in Settings **Solutions**: - ✅ Check browser DevTools for class - ✅ Add class before Fullstory initializes - ✅ Check Settings for conflicting rules - ✅ Verify no `fs-unmask` on children ### Clicks Not Captured on Masked Elements **Symptom**: Masking works but clicks missing **Common Causes**: 1. ❌ Used `.fs-exclude` instead of `.fs-mask` 2. ❌ Parent is excluded **Solutions**: - ✅ Use `.fs-mask` to keep click tracking - ✅ Only use `.fs-exclude` when events shouldn't be tracked ### Dynamic Content Not Protected **Symptom**: AJAX-loaded content visible in replay **Common Causes**: 1. ❌ No privacy class on dynamic content 2. ❌ Content rendered before class applied **Solutions**: - ✅ Include privacy classes in template - ✅ Use CSS selector rules as backup - ✅ Apply classes in component library --- ## KEY TAKEAWAYS FOR AGENT When helping developers with Privacy Controls: 1. **Always emphasize**: - Exclude = nothing leaves device, no events - Mask = structure sent, **actual text never leaves device** (replaced with wireframe locally), events captured - Unmask = everything captured - When in doubt, use the more restrictive option 2. **Critical data that MUST be excluded**: - Passwords (any credential) - Credit/debit card numbers - CVV/security codes - SSN/Tax IDs - Bank account/routing numbers - Medical conditions (HIPAA) - Anything regulated (PCI, HIPAA, GLBA, FERPA) 3. **Questions to ask developers**: - What regulations apply (PCI, HIPAA, GDPR)? - What data types are in this field? - Do you need to track clicks on this element? - Is this dynamic content? - Have you checked console logs for leaks? 4. **Best practices**: - Build privacy into component library - Use explicit classes, don't rely on auto-detection - Test in Fullstory to verify masking/exclusion - Also disable console capture if logging sensitive data --- ## REFERENCE LINKS - **Privacy Protection**: https://help.fullstory.com/hc/en-us/articles/360020623574 - **Privacy Settings**: https://help.fullstory.com/hc/en-us/articles/360020622814 - **Private by Default**: https://help.fullstory.com/hc/en-us/articles/360044349073 - **Form Privacy**: https://help.fullstory.com/hc/en-us/articles/4411898245271 --- _This skill document was created to help Agent understand and guide developers in implementing Fullstory's Privacy Controls correctly for compliant session recording._