# How to Integrate Lovable with FullStory

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: March 2026

## TL;DR

Add FullStory session replay and heatmaps to a Lovable app by injecting the FullStory JavaScript snippet in your index.html and calling FS.identify() after user login to link recordings to authenticated users. For server-side event tracking and Data Export API access, create a Supabase Edge Function with your FullStory API key stored in Cloud → Secrets.

## Adding FullStory Session Replay to Your Lovable App

FullStory answers the question that behavioral analytics cannot: 'What exactly did the user do that led to this drop-off?' While tools like Amplitude tell you that 40% of users abandon your onboarding flow at step 3, FullStory shows you a recording of exactly what happened — the rage clicks on a button that does not work, the scroll loops suggesting confusing content, the form submission that silently fails. For Lovable app builders, FullStory is the fastest way to find UX bugs and friction points that would otherwise require user interviews to diagnose.

FullStory's integration with Lovable is primarily client-side: a JavaScript snippet loads from FullStory's CDN and begins recording the user's session, capturing DOM mutations, mouse movements, clicks, scrolls, and network errors in a compressed format. The recording is not a video — it is a structured replay of DOM events that FullStory reconstructs into a video-like playback. This means recordings are fast to load and searchable by any action (for example: 'show me all sessions where the user clicked the Export button but the loading spinner never disappeared').

FullStory automatically detects frustration signals without any custom tracking code: rage clicks (multiple rapid clicks indicating frustration), dead clicks (clicks on elements that do not respond), thrash (back-and-forth mouse movement suggesting confusion), and error clicks (clicks on JavaScript error-triggering elements). These signals are searchable in FullStory's session index, allowing you to find users experiencing specific UX problems in seconds. Pairing FullStory session replay with quantitative tools like Amplitude creates a complete picture — find the behavioral anomaly in Amplitude, then watch the FullStory recording to understand why it happens. Lovable's security infrastructure blocks approximately 1,200 hardcoded API keys daily and holds SOC 2 Type II certification — your FullStory API key for the Data Export API must stay in Cloud → Secrets.

## Before you start

- A Lovable account with an existing project that has Supabase authentication configured
- A FullStory account — free Business trial at fullstory.com, or a paid plan for production use
- Your FullStory Org ID from Settings → General → Organization ID (a short alphanumeric string)
- For Data Export API access: a FullStory API key from Settings → Integrations → API Keys (available on Business and Enterprise plans)
- Basic familiarity with browser developer tools to verify the FullStory snippet is loading and recording sessions

## Step-by-step guide

### 1. Get your FullStory Org ID and add the recording snippet to index.html

FullStory's session recording requires a JavaScript snippet in your HTML head that loads the recording agent from FullStory's CDN. The snippet is initialized with your unique Org ID, which identifies which FullStory organization receives the recorded sessions. Log in to your FullStory account at app.fullstory.com. Click your profile icon in the top-right corner and select Settings. Under the General section, find the 'Organization ID' — a string formatted like 'ABCDE' or '123XYZ'. This is your public Org ID, safe to include in the snippet in your HTML.

To get the snippet, go to Settings → Integrations → FullStory Snippet. FullStory shows the complete script tag to add to your HTML head. The snippet is an obfuscated loader script that asynchronously loads the FullStory recording agent without blocking page rendering. It sets up the global FS object on the window, which you will use for user identification and custom event tracking.

In your Lovable project, ask Lovable to add the FullStory snippet to your index.html file. The snippet belongs in the HTML head section, after the meta tags but before the closing head tag. Lovable will open the index.html file in the project editor and insert the script tag. The snippet already contains your Org ID inline — there is no environment variable needed for the snippet itself, as the Org ID is publicly visible in the HTML source and is intentionally public (like a GA4 measurement ID).

For the VITE_FULLSTORY_ORG_ID environment variable — add it if you want to reference the Org ID from React components for custom event calls, but the snippet initialization itself uses the hardcoded value from FullStory's generated snippet. After adding the snippet, verify it is working by opening your app in the browser, navigating a few pages, then checking FullStory → Sessions to see if a new session appears within 30-60 seconds.

```
// src/types/fullstory.d.ts
declare global {
  interface Window {
    FS: {
      identify: (uid: string, userVars?: Record<string, unknown>) => void;
      event: (eventName: string, eventProperties?: Record<string, unknown>) => void;
      setUserVars: (userVars: Record<string, unknown>) => void;
      getCurrentSessionURL: (now?: boolean) => string;
      shutdown: () => void;
      restart: () => void;
      log: (level: string, msg: string) => void;
    } | undefined;
    _fs_org?: string;
  }
}

export {};
```

**Expected result:** The FullStory snippet is injected in index.html. Opening the app in a browser and navigating several pages results in a new session appearing in FullStory's Sessions list within a minute. The FS global object is available in the browser console. TypeScript code can reference window.FS without type errors.

### 2. Identify authenticated users in FullStory sessions

FullStory records sessions for anonymous users by default, assigning each visitor a random FullStory-generated ID. To make sessions searchable by your users' identities — so you can find recordings for specific customers when debugging support tickets — call FS.identify() after authentication with the user's Supabase user ID.

The FS.identify() function takes two arguments: a unique user ID string (use the Supabase user UUID for consistency with your other analytics tools) and an optional object of user variables. User variables are key-value pairs that appear in FullStory's session search interface and on user profiles. Include at minimum: displayName (the user's email or name for human-readable identification in the FullStory dashboard), email (for direct email-based search), and any custom attributes relevant to your debugging needs — user plan, account age, feature flags, and any other segments you want to filter sessions by.

User variable names in FullStory follow a typing convention: append _str for strings, _int for integers, _bool for booleans, and _real for floating-point numbers. For example: plan_str, days_since_signup_int, is_paying_bool. Variables without a type suffix are treated as strings. Using the typed suffix convention ensures correct sorting, filtering, and aggregation in FullStory's search interface.

Create a React hook or utility function that calls FS.identify() when the Supabase auth state changes to SIGNED_IN. Call FS.shutdown() when the user logs out to stop recording the session (preventing accidental recording of the next user's activity if the same browser is reused). For React apps, put the identification call in the same auth state change handler used for other analytics tools to keep all user identification logic in one place.

FS.getCurrentSessionURL() is a powerful debugging tool — call it from any component to get the URL of the current FullStory recording at the exact current moment. Store this URL in your Supabase database alongside support tickets, error logs, or user-reported bugs to enable one-click access to the relevant session recording.

```
// src/hooks/useFullStory.ts
import { useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';

export function useFullStory() {
  useEffect(() => {
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        if (event === 'SIGNED_IN' && session?.user) {
          const user = session.user;
          // Fetch user plan from profiles table
          const { data: profile } = await supabase
            .from('profiles')
            .select('plan, created_at')
            .eq('id', user.id)
            .single();

          const daysSinceSignup = profile?.created_at
            ? Math.floor((Date.now() - new Date(profile.created_at).getTime()) / 86400000)
            : 0;

          window.FS?.identify(user.id, {
            displayName: user.email,
            email: user.email,
            plan_str: profile?.plan ?? 'free',
            days_since_signup_int: daysSinceSignup,
          });
        }
        if (event === 'SIGNED_OUT') {
          window.FS?.shutdown();
        }
      }
    );
    return () => subscription.unsubscribe();
  }, []);
}

export function trackFullStoryEvent(
  eventName: string,
  properties?: Record<string, unknown>
) {
  window.FS?.event(eventName, properties);
}

export function getFullStorySessionURL(): string | null {
  return window.FS?.getCurrentSessionURL(true) ?? null;
}
```

**Expected result:** After logging in, sessions in FullStory show the user's email and ID in the user information panel. Searching for sessions by email in FullStory's search bar returns recordings for that specific user. Logging out stops the current recording session.

### 3. Add custom events for searchable session segmentation

FullStory's session replay becomes exponentially more valuable when you can search for sessions by what users did, not just who they are. Custom events allow you to mark specific moments in a user's session — completing onboarding, hitting an error, starting a checkout flow — making those sessions searchable and groupable in FullStory's analysis tools.

Custom FullStory events use the FS.event() function with an event name string and an optional properties object. Event names in FullStory use title case by convention (like 'Checkout Started' or 'Error Encountered'). Property values support the same type suffix convention as user variables: _str, _int, _bool, _real. For example, FS.event('Feature Used', { feature_name_str: 'Export', item_count_int: 25, success_bool: true }).

The most valuable custom events to add are: error states (when a user encounters a JavaScript error, API failure, or validation error — include the error message as a property), frustration-prone flows (the steps in your app where you suspect friction exists — add events at entry, progress, and exit so you can find incomplete sessions), and conversion milestones (signup completion, subscription upgrade, first key action — so you can compare the session recordings of users who convert versus users who abandon).

FS.setUserVars() updates the current user's profile variables at any point during the session without re-identifying them. Use this to update the user's plan after a subscription upgrade — call FS.setUserVars({ plan_str: 'pro' }) immediately after the upgrade completes so subsequent sessions show the correct plan.

For error tracking, add a global error handler that sends error information to FullStory as a custom event so all JavaScript errors generate searchable FullStory events alongside the session recording context. This connects the session replay directly to the error occurrence.

```
// Add to src/App.tsx useEffect for global error tracking
useEffect(() => {
  function handleUnhandledRejection(event: PromiseRejectionEvent) {
    const sessionUrl = window.FS?.getCurrentSessionURL(true);
    console.error('Unhandled rejection:', event.reason, 'Session:', sessionUrl);
    window.FS?.event('Unhandled Error', {
      error_type_str: 'UnhandledPromiseRejection',
      error_message_str: String(event.reason),
      page_url_str: window.location.pathname,
    });
  }
  window.addEventListener('unhandledrejection', handleUnhandledRejection);
  return () => window.removeEventListener('unhandledrejection', handleUnhandledRejection);
}, []);
```

**Expected result:** Custom FullStory events appear in session recordings at the correct moments. Searching for sessions by event name in FullStory's search interface returns the relevant sessions. Error events appear in the session timeline as searchable events with their error details as searchable properties.

### 4. Create a Supabase Edge Function for FullStory Data Export API

FullStory's Data Export API provides programmatic access to session data, user data, and event data for building dashboards outside of FullStory's own interface. This API requires an API key (different from the Org ID) and is available on Business and Enterprise plans. The API key must be stored in Cloud → Secrets and used only from the Edge Function — never in client-side code.

To get your FullStory API key, go to Settings → Integrations → API Keys in your FullStory account. Click 'Create API Key', give it a name like 'Lovable Integration', and copy the generated key. Store it as FULLSTORY_API_KEY in Lovable Cloud Secrets.

FullStory's Data Export API uses GraphQL for flexible querying. The GraphQL endpoint is https://api.fullstory.com/graphql/v2. Authentication uses the Authorization header with your API key as 'Basic [base64(api_key:)]'. The colon after the key with an empty password is required — FullStory uses HTTP Basic auth format with the API key as the username.

Useful GraphQL queries for a UX dashboard: sessionsV2 (query sessions by time range, user segment, or event criteria), eventCountsV2 (aggregate event counts for rage clicks, dead clicks, and custom events over time), and usersV2 (query user profiles by custom variable values). The Data Export API is designed for data warehouse integration and bulk exports, so it has different rate limits than the session recording API — check FullStory's documentation for current limits on your plan.

For most Lovable dashboard use cases, the most practical approach is to use FullStory's Data Export to populate a Supabase table with aggregated metrics on a schedule, then query the Supabase table for dashboard rendering rather than calling the FullStory API on every page load. RapidDev's team can help design the optimal data export and caching architecture for complex FullStory-powered dashboards in your Lovable app.

```
// supabase/functions/fullstory-data/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
  if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });

  try {
    const apiKey = Deno.env.get('FULLSTORY_API_KEY');
    if (!apiKey) {
      return new Response(JSON.stringify({ error: 'FULLSTORY_API_KEY not configured' }), {
        status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      });
    }

    // FullStory uses Basic auth with API key as username and empty password
    const credentials = btoa(`${apiKey}:`);
    const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();

    const query = `
      query GetSessionStats($start: String!) {
        sessionsV2(filters: { createdTime: { greaterThanOrEqual: $start } }) {
          totalCount
        }
      }
    `;

    const response = await fetch('https://api.fullstory.com/graphql/v2', {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${credentials}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ query, variables: { start: sevenDaysAgo } }),
    });

    const data = await response.json();
    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: String(error) }), {
      status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
});
```

**Expected result:** The fullstory-data Edge Function is deployed and returns session statistics from the FullStory Data Export API. Cloud Logs show the GraphQL query executing successfully. The response includes session counts for the past 7 days.

## Best practices

- Use the Supabase user UUID as the FS.identify() uid to ensure consistent session-to-user linking across all your analytics tools — using the same identifier in FullStory, Amplitude, and your database makes cross-tool debugging straightforward.
- Call FS.getCurrentSessionURL(true) in error handlers and store the URL in your Supabase error logs or support ticket system — this enables one-click access to the exact moment in the session recording when an error occurred.
- Use typed user variable suffixes (_str, _int, _bool, _real) consistently — without the correct type suffix, FullStory treats all values as strings, preventing numeric sorting and range filtering in session search.
- Add FS.shutdown() on user logout and FS.restart() if the user logs back in in the same browser session — this prevents one user's post-logout activity from being attributed to the previous user's session.
- Store the FullStory Org ID as VITE_FULLSTORY_ORG_ID for reference in React components, but keep the actual snippet initialization in index.html rather than attempting to dynamically inject it — dynamic injection can cause race conditions where the FS object is not ready when components mount.
- Regularly review FullStory's automatically detected frustration signals (rage clicks, dead clicks, thrash) rather than only looking at sessions for specific users — the aggregated frustration heatmaps often reveal systemic UX problems faster than individual session review.
- Consider FullStory's privacy controls carefully — users in regions with strict privacy laws (GDPR, CCPA) may require consent before recording. Use FullStory's consent framework or conditionally initialize the snippet based on the user's consent status.

## Use cases

### Identify UX friction causing onboarding drop-off

Add FullStory recording to the onboarding flow and use custom events to mark when each onboarding step is entered and exited. When your analytics show a high drop-off at a specific step, use FullStory to search for sessions that abandoned at that step and watch the recordings. Frustration signals like rage clicks often reveal the root cause immediately — a button that appears clickable but is not, a form validation message that is not visible, or a loading state that never resolves.

Prompt example:

```
Add FullStory custom events to the onboarding flow. Call FS.event('Onboarding Step Viewed', { step: 1, step_name: 'Profile Setup' }) when each onboarding step is displayed. Call FS.event('Onboarding Step Completed', { step: 1 }) when a step is successfully completed. Call FS.event('Onboarding Abandoned', { step: 2, time_on_step_seconds: 45 }) when a user navigates away without completing a step. Import FullStory via the global FS object injected by the snippet in index.html.
```

### Link session recordings to support tickets for faster debugging

When a user reports a bug, find their FullStory session using their email or Supabase user ID to watch exactly what they did before the issue occurred. FullStory's search lets you filter sessions by user identity, specific events, error conditions, and time windows. Identify the user in FullStory sessions using their Supabase user ID so support tickets can be linked directly to recordings.

Prompt example:

```
After successful login, call window._fs_identify() with the user's Supabase user ID as the uid and include displayName (email) and email as user variables. This enables searching FullStory sessions by email when debugging reported issues. Also set a plan_str user variable so sessions can be filtered by subscription tier when diagnosing plan-specific bugs.
```

### Build a UX issue dashboard from FullStory Data Export API

Use FullStory's Data Export API via a Supabase Edge Function to programmatically access frustration signal counts, error rates, and session metrics. Build an in-app dashboard showing which pages have the most rage clicks this week, which user segments experience the most JavaScript errors, and which flows have the highest abandonment rates — all from FullStory data displayed inside your Lovable app.

Prompt example:

```
Create a Supabase Edge Function called 'fullstory-data' that calls the FullStory Data Export API using FULLSTORY_API_KEY from Deno secrets. Fetch session counts, rage click events, and error events for the past 7 days. Return aggregated data as JSON. Create a UXHealthDashboard React component that calls this Edge Function and displays: total sessions this week, rage click rate, error session percentage, and top 5 pages by frustration signals as a sortable table.
```

## Troubleshooting

### FullStory snippet is in index.html but no sessions appear in the FullStory dashboard

Cause: The FullStory Org ID in the snippet is incorrect, the snippet is being blocked by a Content Security Policy that does not allow scripts from fullstory.com, or FullStory has been disabled for the current session (FullStory automatically excludes sessions from users with FullStory cookies set to opt-out).

Solution: Open the browser console and look for FullStory-related messages. Check the Network tab for requests to fullstory.com — if requests are blocked, check for CSP errors in the console. Verify the Org ID in the snippet matches your FullStory organization's Org ID from Settings → General. Try opening the app in an incognito window to rule out FullStory's own opt-out cookie interfering with session recording.

### FullStory sessions are recording but user identity is not showing — sessions appear as 'Anonymous User'

Cause: FS.identify() is being called before the FullStory snippet has loaded and the FS global is available, or the identify call is not reaching the code path that executes after a successful login.

Solution: Wrap the FS.identify() call in a check: window.FS?.identify(). The optional chaining operator prevents errors when FS is not yet defined. Verify the auth state change handler is firing after login by adding a console.log before the identify call. Check that the hook calling FS.identify() is mounted in the App component so it runs throughout the session.

```
// Safe FS.identify call with availability check:
if (typeof window !== 'undefined' && window.FS) {
  window.FS.identify(userId, userVars);
} else {
  // FS not yet loaded — retry after a short delay
  setTimeout(() => window.FS?.identify(userId, userVars), 2000);
}
```

### Data Export API Edge Function returns 401 or 'Authentication failed'

Cause: The FullStory API key is not stored correctly in Cloud Secrets, or the Basic auth encoding format is wrong. FullStory's API requires Basic auth with the API key as the username and an empty password — the format must be base64('api_key:') with the colon and empty password included.

Solution: Verify FULLSTORY_API_KEY is in Cloud → Secrets with the correct value. The auth header must be formatted as: Authorization: Basic [base64(key_value + ':')] — the colon after the key is mandatory. Test the encoding: in Node.js, Buffer.from('your_key:').toString('base64') gives the correct value. Do not include 'Bearer' in the header — FullStory uses 'Basic', not 'Bearer' authentication.

```
// Correct FullStory Basic auth encoding:
const apiKey = Deno.env.get('FULLSTORY_API_KEY');
const credentials = btoa(`${apiKey}:`); // Colon is required
const authHeader = `Basic ${credentials}`;
```

### FullStory records sessions in development but not in the production deployment

Cause: FullStory has privacy settings that may block recording on domains not included in the allowed domains list, or the snippet Org ID differs between development and production builds.

Solution: In your FullStory account, go to Settings → Privacy → Allowed Domains and verify your production domain is listed. If the snippet was added with a hardcoded Org ID, ensure it matches your production FullStory organization. FullStory also automatically excludes sessions from localhost and development ports in some plan configurations.

## Frequently asked questions

### Does FullStory require a paid plan for use in a production Lovable app?

FullStory offers a free Business trial that includes session recording with a limited number of sessions. For production use, paid plans start at pricing based on monthly session volume. Check fullstory.com/pricing for current rates. The Data Export API feature described in Step 4 is only available on Business and Enterprise plans.

### Does FullStory recording impact the performance of my Lovable app?

FullStory's snippet loads asynchronously and does not block page rendering. The recording agent adds a minimal overhead — typically 5-10KB of additional JavaScript and minor CPU usage for DOM mutation tracking. FullStory compresses the session data before transmission. For most Lovable apps, the performance impact is imperceptible to users. If you are concerned about performance on lower-end devices, FullStory supports sampling (recording only a percentage of sessions) to reduce overhead.

### How does FullStory handle user privacy and GDPR compliance?

FullStory supports privacy compliance through several mechanisms: input field masking (text inputs are replaced with asterisks by default for privacy), element exclusion (add fs-exclude class to any element to prevent it from being recorded), and consent-based initialization (only call the snippet after users provide consent). FullStory is GDPR-compliant and offers data processing agreements. Review FullStory's privacy documentation and your jurisdiction's requirements before enabling recording for EU users.

### What is the difference between FullStory session replay and heatmaps?

Session replay records individual user sessions as a complete playback of everything a specific user did — you watch their actual navigation, clicks, scrolls, and interactions. Heatmaps aggregate click and scroll data across all users to show where users tend to click and how far they scroll on average. FullStory provides both: individual session replay for debugging specific user issues and aggregate click/scroll heatmaps for understanding general user behavior patterns.

### Can I share a FullStory session recording link with my team or with a user for support purposes?

Yes. Every FullStory session has a permanent URL that can be shared with team members who have FullStory account access. Use FS.getCurrentSessionURL() from your app code to get the current session's URL and store it alongside support tickets or error reports. Team members can then click the link to watch the session without searching through FullStory's session list.

---

Source: https://www.rapidevelopers.com/lovable-integration/fullstory
© RapidDev — https://www.rapidevelopers.com/lovable-integration/fullstory
