Connect FlutterFlow to FullStory using two separate paths: a Custom Action wrapping the `fullstory_flutter` pub.dev SDK to capture sessions inside your app (using a public org ID, safe on the client), and a FlutterFlow API Call through a Firebase or Supabase proxy to read session data and replay links back via FullStory's REST API. The account-level REST API key must never ship in the app bundle.
| Fact | Value |
|---|---|
| Tool | FullStory |
| Category | Analytics |
| Method | Custom Action (Dart) |
| Difficulty | Intermediate |
| Time required | 45 minutes |
| Last updated | July 2026 |
Add Session Replay to Your Flutter App — Capture and Read Back with FullStory
FullStory does something no simple event tracker can: it records an exact replay of what every user saw and did in your app. When a user hits a bug or drops off mid-flow, you can watch the session rather than guess. For FlutterFlow apps this means two things: instrumenting the app so FullStory records sessions (capture), and pulling those recordings into an in-app panel so your support team can look up a user's recent sessions without leaving the app (read back).
Capture is the primary use case and the simpler one. FullStory publishes an official `fullstory_flutter` package on pub.dev. You initialize it once on app start with your FullStory org ID — a public identifier that scopes session data to your organization and is safe to include in the compiled Flutter build. Once initialized, FullStory automatically captures interactions. You identify users by calling the SDK's identify equivalent with your own user ID, which becomes the join key between FullStory sessions and your own database records.
Read-back requires more setup because FullStory's REST API (`https://api.fullstory.com`) uses an account-level API key that has broad access to all session and user data. Placing that key inside a FlutterFlow API Call header would embed it in the compiled app binary — anyone who downloads the app can extract it. The safe path is a thin Firebase Cloud Function or Supabase Edge Function that holds the key server-side and forwards authenticated requests to FullStory. Your FlutterFlow API Call group targets the proxy, not FullStory directly. FullStory's Business plan or higher is required for REST API access.
Session replay links (`fsUrl`) returned by the API open in the FullStory player and require a FullStory viewer license — you surface the link in your app, you don't embed the player. This is the right architecture: the FlutterFlow app becomes a quick-lookup tool your CS team uses to find the right session, then watches it in FullStory's own UI.
Integration method
FullStory requires two distinct integration paths in FlutterFlow. First, a Custom Action wraps the official `fullstory_flutter` pub.dev SDK to initialize session capture using your public FullStory org ID — this runs on the user's device and is safe to embed in the bundle. Second, a FlutterFlow API Call group hits a Firebase Cloud Function or Supabase Edge Function proxy that holds the account-level REST API key, pulling session lists and replay links back into a support or CS panel inside your app.
Prerequisites
- A FullStory account — capture SDK works on any plan; REST API access requires Business plan or higher
- Your FullStory org ID (visible in FullStory app settings or your account URL)
- A FullStory API key from Admin → Account Settings → Integrations → API (for the read-back proxy only)
- A FlutterFlow project with Custom Code enabled
- A Firebase project with Cloud Functions or a Supabase project with Edge Functions (for the read-back proxy)
Step-by-step guide
Add the fullstory_flutter Custom Action and initialize on app start
In FlutterFlow, open the left nav and click Custom Code, then click + Add and select Action. Name the action InitFullStory. In the Dependencies field, add the pub.dev package: `fullstory_flutter` (search pub.dev for the current stable version and paste the full package name; FlutterFlow will resolve the version from pub.dev). This tells FlutterFlow to include the package in the generated Flutter project without you running any terminal commands. In the Dart code editor, write the initialization call. Import the package at the top with `import 'package:fullstory_flutter/fullstory_flutter.dart';`. In the function body, call `FS.init(OrgIdOptions(orgId: 'YOUR_ORG_ID'))`. Replace YOUR_ORG_ID with the alphanumeric org ID from your FullStory account settings — this is a public identifier, not a secret, so it is safe to include directly in the Dart code. Add a `kIsWeb` guard: `if (kIsWeb) return;` at the top of the function body, because the `fullstory_flutter` package is designed for native iOS and Android. Without this guard the app will fail to compile for web targets. Import `foundation.dart` for the `kIsWeb` constant: `import 'package:flutter/foundation.dart';`. Once the Custom Action is saved, open your app's main entry point — the widget that FlutterFlow designates as the initial page or your app's root. Open its Actions panel, click + Add Action, navigate to Custom Actions, and select InitFullStory. Set the action to run On Page Load so FullStory initializes as soon as the app starts. Click Save. Note: Custom Actions do not run in FlutterFlow's web-based Test Mode or the preview canvas. You will see no FullStory recording activity until you test in Run Mode on a physical device or a real iOS/Android build. This is expected and is a known FlutterFlow limitation for any pub.dev SDK-based Custom Action.
1import 'package:flutter/foundation.dart';2import 'package:fullstory_flutter/fullstory_flutter.dart';34Future initFullStory() async {5 // FullStory Flutter SDK does not support web6 if (kIsWeb) return;78 // orgId is a public capture identifier — safe to embed9 await FS.init(OrgIdOptions(10 orgId: 'YOUR_ORG_ID',11 // Optional: set logLevel to debug during development12 // logLevel: FSLogLevel.debug,13 ));14}Pro tip: The FullStory org ID is NOT your API key. It is a public capture identifier — anyone can see it in your app binary and that is by design. The secret that must stay off the client is the REST API key used to read data back. These are two completely different credentials.
Expected result: The Custom Action appears under Custom Code in the left nav. When you build and run the app on a device, a new session appears in your FullStory dashboard within a few minutes of app launch.
Identify users so sessions link to your own user records
After the user signs in to your FlutterFlow app, you need to call FullStory's identify function to tag the session with your own user ID. This is the join key: when you later query FullStory's REST API for a user's sessions using `/v1/users/{uid}`, the uid you search with must exactly match the uid you passed to FS.identify in the app. Without this step, sessions are anonymous and you cannot look them up per user. Create a second Custom Action named IdentifyFullStoryUser. Add the same `fullstory_flutter` package dependency. The function should accept two String arguments: userId (your internal user ID — a stable, opaque identifier, never an email alone) and userEmail (optional, for display in FullStory). In the function body, add the `kIsWeb` guard first, then call `FS.identify(userId, {'email': userEmail, 'displayName': userDisplayName})`. FullStory stores whatever custom properties you pass as user variables, so include attributes that will be useful for filtering sessions in the FullStory dashboard: subscription plan, user role, account creation date. Wire this Custom Action to fire after a successful sign-in. In FlutterFlow, open your sign-in page or the page users land on after authentication. In the Actions panel for the navigation event or the success outcome of your auth action, add IdentifyFullStoryUser. Pass the signed-in user's ID from your Supabase or Firestore user record as the userId argument. This ensures every session from a logged-in user is tagged with your internal ID. Store the userId in an App State variable as well, because you will need it in Step 5 when you query the proxy for this user's sessions. Make the App State variable a String type and set it during the same sign-in action flow.
1import 'package:flutter/foundation.dart';2import 'package:fullstory_flutter/fullstory_flutter.dart';34// Arguments: userId (String), userEmail (String)5Future identifyFullStoryUser(6 String userId,7 String userEmail,8) async {9 if (kIsWeb) return;1011 // Identify the user so sessions are searchable by your internal ID12 await FS.identify(userId, {13 'email': userEmail,14 // Add any attributes useful for FullStory filtering15 // 'plan': userPlan,16 // 'createdAt': userCreatedAt,17 });18}Pro tip: Use your stable internal user ID (from Firestore or Supabase auth) as the uid — not the user's email. Emails can change; a stable ID never will. This ensures a user's sessions remain findable even if they update their email address.
Expected result: After signing in, the user's FullStory sessions in the FullStory dashboard show their display name and email instead of 'Anonymous'. The Sessions panel in FullStory shows sessions associated with the user ID you set.
Deploy a proxy Cloud Function to hold the FullStory REST API key
FullStory's REST API at `https://api.fullstory.com` requires an account-level API key sent as a Bearer token. This key has read access to all sessions and user data in your FullStory organization. If you place it in a FlutterFlow API Call header, it is compiled into the app binary and any user who downloads your app can extract it using standard tools. The solution is a thin proxy function. If you are using Firebase, open the Firebase Console, enable Cloud Functions, and write a Node.js function (Firebase Functions v2 is recommended). The function receives a request from your FlutterFlow app, adds the FullStory Bearer token from environment variables, forwards the request to `https://api.fullstory.com`, and returns the response. This way the token never touches the client. Deploy the function to Firebase using the Firebase Console's inline editor or the Firebase CLI from your development machine. Once deployed, note the function's HTTPS URL — this is the base URL you will use in your FlutterFlow API Group. If you are using Supabase instead, the same pattern applies: create a Supabase Edge Function (Deno runtime) that reads the API key from `Deno.env.get('FULLSTORY_API_KEY')`, forwards the request to FullStory, and returns the response. Store the API key in Supabase Dashboard → Settings → Edge Functions → Secrets. This proxy step is also where RapidDev can help if you'd rather have experts handle the backend plumbing — the team builds FlutterFlow integrations like this every week and offers a free scoping call at rapidevelopers.com/contact.
1// Firebase Cloud Function (Node.js) — FullStory API proxy2const { onRequest } = require('firebase-functions/v2/https');3const { defineSecret } = require('firebase-functions/params');4const axios = require('axios');56const FULLSTORY_API_KEY = defineSecret('FULLSTORY_API_KEY');78exports.fullstoryProxy = onRequest(9 { secrets: [FULLSTORY_API_KEY] },10 async (req, res) => {11 // CORS for web builds12 res.set('Access-Control-Allow-Origin', '*');13 if (req.method === 'OPTIONS') {14 res.set('Access-Control-Allow-Headers', 'Content-Type');15 return res.status(204).send('');16 }1718 const apiKey = FULLSTORY_API_KEY.value();19 const path = req.query.path || '/v2/users';2021 try {22 const response = await axios({23 method: req.method,24 url: `https://api.fullstory.com${path}`,25 headers: {26 Authorization: `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,27 'Content-Type': 'application/json',28 },29 data: req.body,30 params: req.query.params ? JSON.parse(req.query.params) : {},31 });32 return res.json(response.data);33 } catch (err) {34 return res.status(err.response?.status || 500).json({35 error: err.message,36 });37 }38 }39);Pro tip: Store the FullStory API key using Firebase's secret manager (`defineSecret`) rather than environment variables — secrets are encrypted at rest and are not visible in the Firebase console after initial creation. Set the secret value via `firebase functions:secrets:set FULLSTORY_API_KEY`.
Expected result: The Cloud Function deploys successfully and appears in the Firebase Console → Functions list with a green status. Visiting the function URL in a browser returns a FullStory API response (or a 400 if no path is passed — which confirms the function itself is running and the API key is working).
Create a FlutterFlow API Group targeting the proxy
Now that the proxy is deployed, wire it up in FlutterFlow. In the left nav, click API Calls, then click + Add and select Create API Group. Name the group FullStoryProxy. In the Base URL field, paste your deployed Firebase Cloud Function URL (or Supabase Edge Function URL) from the previous step. Do not add any Authorization headers here — authentication is handled by the proxy function, not by FlutterFlow. With the API Group created, add an API Call inside it: click + Add Call on the group. Name it GetUserSessions. Set the method to GET. In the endpoint path field, leave it empty or add a path variable if your proxy forwards the FullStory path as a query parameter — for example `/` with a query variable named `path` set to `/v2/users/{{userId}}/sessions`. Add a Variable named userId in the Variables tab and set its type to String. Click the Response & Test tab. Paste a sample FullStory sessions API response (you can get this by calling FullStory's API directly from a REST client like Postman using your API key). Click Generate JSON Paths to extract the fields you want to bind in FlutterFlow: session ID, creation time, duration, and `fsUrl` (the replay link). FlutterFlow will generate JSON Path expressions like `$.sessions[*].fsUrl` for each field. Create a custom Data Type in FlutterFlow named FullStorySession with fields: sessionId (String), createdTime (String), durationMs (Integer), fsUrl (String). Map the JSON Paths to this Data Type so FlutterFlow knows how to deserialize the response into typed objects you can bind to list widgets. Save the API Group. Test it by entering a real userId in the Test tab and verifying sessions are returned.
1{2 "group_name": "FullStoryProxy",3 "base_url": "https://YOUR_REGION-YOUR_PROJECT.cloudfunctions.net/fullstoryProxy",4 "calls": [5 {6 "name": "GetUserSessions",7 "method": "GET",8 "endpoint": "/",9 "query_params": {10 "path": "/v2/users/{{userId}}/sessions"11 },12 "variables": [13 { "name": "userId", "type": "String" }14 ],15 "json_paths": {16 "sessionId": "$.sessions[*].id",17 "createdTime": "$.sessions[*].createdTime",18 "durationMs": "$.sessions[*].durationMs",19 "fsUrl": "$.sessions[*].fsUrl"20 }21 }22 ]23}Pro tip: FullStory's v2 API endpoints differ in structure from v1 — verify the current endpoint paths in FullStory's official API documentation before building your JSON Path mappings. The response shape for v2 wraps results in a top-level key like `sessions` whereas v1 returns an array directly.
Expected result: The API Group appears in the left nav under API Calls. The Test tab returns a JSON response containing session records for the user ID you entered. JSON Paths are generated and mappable to the FullStorySession Data Type.
Build the support panel: session list with replay links
Create a new screen in FlutterFlow named SupportPanel (or add it to an existing user-profile or account screen). This screen will show the signed-in user's recent FullStory sessions and let them open replay links, or let a CS agent look up any user by ID. Add a ListView widget to the screen. Set its data source to an API Call backend query — select the FullStoryProxy → GetUserSessions API Call. Pass the userId from your App State variable as the query argument. FlutterFlow will execute the call when the page loads and populate the list with the returned FullStorySession items. Inside the ListView item template, add: a Text widget bound to `createdTime` (format it as a readable date), another Text widget bound to `durationMs` (you can use a Dart string expression to convert milliseconds to minutes), and a Button labeled 'Watch Replay' whose On Tap action is Launch URL → bound to `fsUrl` from the list item. This opens the FullStory session player in the device browser. Add a gate to prevent the panel from showing if the user is not signed in or if the API call is still loading. Use a Conditional Widget wrapping the ListView: show a CircularProgressIndicator while the API call is in the Loading state, and show an error Text when the API call returns an error state. Role-gate the entire screen: only accounts with a support or admin role should be able to navigate to SupportPanel. Configure this in FlutterFlow's navigation action by checking the user's role from your Firestore or Supabase user document before navigating. Add a note in the UI that session replay links require a FullStory viewer license to open — users without a FullStory account will see a login page when they tap the link.
Pro tip: Session replay links (fsUrl) open in the FullStory player which requires authentication. Do not try to embed them in a FlutterFlow WebView — FullStory's player does not support iframe embedding without special enterprise configuration. Surface the link as a Launch URL button and let FullStory handle the viewing experience in the browser.
Expected result: The SupportPanel screen loads and displays the signed-in user's recent sessions. Each list item shows the session date, approximate duration, and a button. Tapping 'Watch Replay' opens the session in the device browser, prompting the user to log in to FullStory if they are not already authenticated.
Add consent gating and test on a real device
FullStory records user behavior which is personally identifiable information — session recordings capture what users tap, type, and see. You need a consent flow before initializing FullStory, both for GDPR (EU users) and for good product practice. In FlutterFlow, the simplest approach is to store a consent flag in your Supabase or Firestore user document and only call the InitFullStory Custom Action if consent is granted. Open your app's consent or privacy screen. Add a toggle or checkbox labeled 'Allow session recording to help us improve the app.' When the user taps Accept, set a `sessionRecordingConsent` boolean field to true in their user document, then fire the InitFullStory Custom Action. When the user declines or revokes consent, call FS.shutdown() (add a ShutdownFullStory Custom Action) and update the field to false. Note that iOS 14.5+ App Tracking Transparency (ATT) applies when you track users for cross-app attribution. For first-party product analytics within your own app, ATT is generally not required — FullStory's session recording is considered first-party analytics. However, consult your legal team for your specific jurisdiction and use case. For testing: Custom Actions do not execute in FlutterFlow's browser-based Test Mode. To verify FullStory is capturing sessions, use Run Mode which builds and runs the app on a real device or emulator. Check the FullStory dashboard → Live Sessions to confirm sessions appear. If you see no sessions after running on a device, verify that the org ID in the Custom Action matches your FullStory account and that the function is not returning an error in the device logs.
1import 'package:flutter/foundation.dart';2import 'package:fullstory_flutter/fullstory_flutter.dart';34// Custom Action: ShutdownFullStory5// Call this when user revokes consent6Future shutdownFullStory() async {7 if (kIsWeb) return;8 await FS.shutdown();9}Pro tip: When testing on a device, FullStory sessions may take 1-2 minutes to appear in the Live Sessions dashboard after the app starts. If you see nothing after 5 minutes, check the device console logs for SDK initialization errors — a common cause is a typo in the org ID.
Expected result: The consent flow correctly gates FullStory initialization. Sessions appear in the FullStory dashboard only after the user has granted consent. Revoking consent stops new sessions from being recorded. In the FullStory Live Sessions panel, you can see your device's session in real time.
Common use cases
SaaS app that surfaces session replays for the in-app support panel
Build a FlutterFlow app where users can tap a 'Contact Support' button that also shows the support agent (or an in-app CS portal) the user's three most recent FullStory sessions and their replay links. The proxy fetches `/v1/users/{uid}/sessions` and returns replay URLs the agent can open in FullStory. This eliminates the 'what were you doing when it happened?' back-and-forth.
Add a CS panel screen that looks up the signed-in user's FullStory sessions via a proxy API call and shows a list with session date, duration, and a button that opens the replay link in a WebView or external browser.
Copy this prompt to try it in FlutterFlow
Onboarding flow with session capture to diagnose drop-off
Instrument a multi-step onboarding flow in FlutterFlow with FullStory session capture. Initialize FullStory on app launch and identify each user so sessions are tagged to their account. The product team reviews replays in the FullStory dashboard to see exactly where users get confused or abandon the flow, enabling targeted UI fixes without relying on user surveys.
Initialize the fullstory_flutter SDK on app launch, call identify with the user's ID and email after sign-in, and make sure the onboarding steps are navigated as separate screens so FullStory captures each step as a distinct page view in the session replay.
Copy this prompt to try it in FlutterFlow
Frustration signal monitoring for a mobile e-commerce checkout
Add FullStory session capture to a FlutterFlow e-commerce checkout flow so rage clicks and dead clicks during checkout are captured and visible in the FullStory dashboard. When the conversion rate drops, the team pulls up sessions from the affected day and immediately sees which button or form field triggered frustration — no guesswork.
Integrate the fullstory_flutter Custom Action so sessions are recorded during checkout. Add a custom event call after a successful purchase and after an abandoned cart so FullStory can segment sessions by outcome.
Copy this prompt to try it in FlutterFlow
Troubleshooting
No sessions appear in FullStory dashboard after running on a device
Cause: The Custom Action may not have initialized correctly, or the org ID is wrong. Custom Actions do not run in FlutterFlow web Test Mode — only on real builds or Run Mode on a device.
Solution: Verify you are testing in Run Mode or a real device build, not the browser preview. Double-check that the org ID in InitFullStory matches the one in your FullStory account settings exactly. Check device logs for SDK errors. Confirm the InitFullStory action is wired to On Page Load of your root widget.
401 Unauthorized on all proxy requests to FullStory's REST API
Cause: You are likely using the FullStory org ID (the capture identifier) as the REST API Bearer token. These are two completely different credentials. The org ID is a public capture token; the REST API key is a separate account-level secret found under Admin → Account Settings → Integrations → API.
Solution: Get a separate API key from FullStory Admin → Account Settings → Integrations → API. Store this key in your Firebase Cloud Function using `defineSecret` or in Supabase Edge Function secrets. Update the proxy function to use this key, not the org ID. Also verify that your FullStory plan includes REST API access — this requires Business plan or higher.
XMLHttpRequest error or CORS error when calling the proxy from FlutterFlow web build
Cause: The Firebase Cloud Function or Supabase Edge Function is not returning the correct CORS headers, so browser-based web builds are blocked. Native iOS and Android builds do not enforce CORS, so this only appears on web.
Solution: Add CORS headers to your proxy function response: `Access-Control-Allow-Origin: *` and handle OPTIONS preflight requests with a 204 response. The code example in Step 3 includes this pattern. Redeploy the function after adding the CORS headers.
fsUrl field is null or empty in the session list from the API
Cause: The replay link may not be available yet for very recent sessions that FullStory is still processing, or the JSON Path mapping may not match the actual field name in the API response version you are using.
Solution: Check which API version your proxy is calling (v1 vs v2) — the field name for the replay URL differs between versions. In v2 the field may be `replayUrl` or `url` rather than `fsUrl`. Open the Response & Test tab on your FlutterFlow API Call, paste a fresh real response, and regenerate JSON Paths. For sessions recorded in the last few minutes, wait and retry — FullStory processes recordings asynchronously.
Best practices
- Never place the FullStory REST API key in a FlutterFlow API Call header — always route read-back requests through a Firebase Cloud Function or Supabase Edge Function proxy.
- Gate FullStory initialization behind user consent and store the consent flag in your user database so it persists across sessions and app reinstalls.
- Use a stable, opaque internal user ID (not email) as the FS.identify uid so sessions remain searchable even if the user changes their email.
- Add a kIsWeb guard in every FullStory Custom Action — the fullstory_flutter SDK is native-only and will cause compilation errors on web targets without it.
- Test FullStory session capture exclusively in Run Mode on a real device — Custom Actions do not execute in FlutterFlow's browser-based Test Mode preview.
- Surface replay links as Launch URL buttons rather than attempting to embed the FullStory player in a WebView — FullStory does not support iframe embedding without enterprise configuration.
- Cache the proxy response in Firestore or Supabase so the support panel does not re-query FullStory's API every time it opens — session lists change slowly.
- Role-gate the session replay panel so only CS or support roles can access it — FullStory recordings contain sensitive PII and should not be visible to all users of the app.
Alternatives
Hotjar offers session recording and heatmaps primarily for web apps via a JavaScript snippet, while FullStory has a native Flutter SDK for mobile session capture with stronger frustration signal detection.
Mixpanel tracks discrete user events and funnels and is better for quantitative product analytics, while FullStory records full session replays and frustration signals for qualitative UX investigation.
Amplitude excels at cohort-based retention and conversion analysis across large user populations, while FullStory is better suited for understanding individual session recordings and diagnosing specific support issues.
Frequently asked questions
Can I watch FullStory session recordings inside my FlutterFlow app?
No — you cannot embed the FullStory player inside a FlutterFlow WebView. FullStory's player requires authentication to FullStory's own domain and does not support iframe embedding without a special enterprise configuration. The right pattern is to surface the session replay link (fsUrl) as a button in your app. Tapping it opens the recording in the device browser, where the viewer logs in to FullStory and watches the session. Your CS team needs FullStory viewer licenses to watch recordings.
Does the fullstory_flutter SDK work in FlutterFlow web builds?
No — the fullstory_flutter pub.dev package is native-only (iOS and Android). For web targets you would need to inject FullStory's JavaScript snippet into your web app's index.html, which FlutterFlow allows for web builds. Add a kIsWeb guard in your Custom Action so the native SDK initialization is skipped on web. Session recording on web via the JS snippet and on mobile via the SDK operates independently — you will see two separate session types in your FullStory dashboard.
What plan do I need for FullStory REST API access?
The capture SDK (`fullstory_flutter`) works on all FullStory plans, including free trials. The REST API — which you need to read sessions and replay links back into your app — requires a Business plan or higher. If you do not see an API Keys section in your FullStory Admin → Account Settings, you will need to upgrade. Contact FullStory support to enable API access for evaluation if you need it before upgrading.
Why does the FullStory Custom Action not run in FlutterFlow's test mode?
FlutterFlow's browser-based Test Mode (the preview canvas and Run Mode in-browser) does not execute Custom Actions — Custom Actions are Dart code that only runs in a compiled Flutter build on a real device or emulator. This is a FlutterFlow platform limitation, not a FullStory issue. Use Run Mode connected to a real iOS or Android device to test FullStory session capture, and check the FullStory dashboard's Live Sessions panel to confirm sessions are being recorded.
Is the FullStory org ID safe to include in the compiled Flutter app?
Yes — the org ID is a public capture identifier by design. It scopes session recordings to your FullStory organization, but having the org ID does not grant read access to your session data. Someone who extracts your org ID from the app binary could theoretically send synthetic sessions to your FullStory account, which is why proxying is the RapidDev-recommended default for production. However, the org ID is architecturally different from the REST API key, which must never appear in the client bundle.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation