About contentsquare-flutter-sdk
A skill for AI agents integrating or upgrading the Contentsquare Flutter SDK in client applications. It covers project healthchecks, SDK setup, analytics instrumentation, and migration guidance.
Key features
- 01Project configuration healthcheck
- 02Flutter SDK installation and setup
- 03Screen and webview tracking guidance
- 04Analytics and replay API guidance
- 05Migration guidance for SDK versions
Use cases
- 01Integrating Contentsquare in a Flutter app
- 02Upgrading an existing Contentsquare SDK
- 03Diagnosing project setup and tracking issues
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ contentsquare/ agents --skill contentsquare-flutter-sdkAfter installing, type/contentsquare-flutter-sdkin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-reference.mdcommon-patterns.mdios-in-app-features.mdmigration.mdscreen-tracking.mdsession-replay.mdDetails
From the project READMEContentsquare Flutter SDK Integration
This skill helps AI agents integrate or upgrade the Contentsquare Flutter SDK in client applications.
MANDATORY FIRST STEP -- Contentsquare project healthcheck
Before ANY Contentsquare work in a conversation -- integration, debugging, configuration questions, code changes -- run this healthcheck exactly once per conversation. Do not skip it. Do not defer it to later in the task.
-
Announce it first. Tell the user what you are about to do, for example:
"Before we start, I'll run a quick healthcheck of your Contentsquare project: I'll ask for your data source ID (or read your app's bundle ids if you don't have one), call the Contentsquare healthcheck endpoint, and show you the current project configuration. Then we'll continue."
-
Ask the user for their Contentsquare data source ID(s) first. For example: "Do you have a Contentsquare data source ID? There can be one or several, for example one for Android and one for iOS. If so, tell me which platform each one is for." Use only the IDs the user gives -- never invent one.
- The user gives one or more data source IDs → check by data source ID only (
v3). Skip step 3. - The user only has an environment ID → check by bundle id only (
v2): an environment ID is not a data source ID and never works in thev3path. - The user has none, does not know, or skips the question → check by bundle id only (
v2).
- The user gives one or more data source IDs → check by data source ID only (
-
Resolve the bundle ids (only without a data source ID) -- a Flutter app has TWO, and they are frequently different strings. Resolve each one separately; never assume they match.
Platform Where to read it Android applicationIdinandroid/app/build.gradle(.kts)(fall back tonamespace, orpackageinAndroidManifest.xml)iOS PRODUCT_BUNDLE_IDENTIFIERinios/Runner.xcodeproj/project.pbxproj(orCFBundleIdentifierinios/Runner/Info.plist), resolving any$(...)variables -
Call the endpoint -- one version only, never both. The platform segment must be literally
iosorandroid. There is no Flutter platform value -- never send one.With data source IDs (
v3), once per ID:curl -s "https://mobile-production.content-square.net/healthcheck/android/config/v3/<dataSourceId>.json" curl -s "https://mobile-production.content-square.net/healthcheck/ios/config/v3/<dataSourceId>.json"Query each ID on the platform the user gave for it. If the platform is unknown, query it on each platform present in the project: a non-200 on one platform only means that data source is not set up for it.
If an ID gets no
200on any platform you queried, tell the user -- it may be an environment ID or a typo -- and ask whether you may look for the data source ID in the code yourself: it is the ID passed toStartConfig.withDatasourceId(id: ...)(follow it to its literal value if it comes from a constant or a config file). If they agree and you find a different ID, run thev3check once with it. If they decline or there is none in the code, continue without it.Without a data source ID (
v2), once per platform that exists:curl -s "https://mobile-production.content-square.net/healthcheck/android/config/v2/<androidApplicationId>.json" curl -s "https://mobile-production.content-square.net/healthcheck/ios/config/v2/<iosBundleId>.json"If only one platform is present in the project, query only that one. If you cannot resolve one of the bundle ids, ask the user which platform to check rather than guessing.
-
Report a short summary of these values, for each call that returned
200:Report JSON path Project ID cs_project_idTracking enabled project_configurations.project_config.enabledSession Replay project_configurations.project_config.session_replay--recording_rate,record_via_cellular_network,recording_quality_wifi,srm_enabled,user_identifierEnabled feature flags project_configurations.project_config.feature_flags-- only entries whereenabled == true, withnameandmin_versionFor
v3calls, also report the Environment ID (environment_id).Android and iOS may map to different Contentsquare projects. If the responses differ, call that out explicitly.
-
Then continue with the user's actual request.
Failure handling. 403 Invalid health-check path means the URL shape is wrong -- re-check the ios/android segment, the v2/v3 segment and the id, and do not alter the path structure. A non-200 proxied from upstream means that data source ID or bundle id has no Contentsquare configuration for that platform: say so, then continue anyway -- do not retry with the other version (the only retry is the code lookup in step 4). This healthcheck is informational only and must never block the user's task.
Install
dependencies:
contentsquare: ^4.5.1
Then run flutter pub get.
Already on
^4.1.x? This release contains breaking changes inAnalyticsOptions/ProductAnalyticsOptions(several fields removed) and a deprecation ofconfigureProductAnalytics(...). Read the Migration from 4.1.x to 4.5.1 section before bumping.
Already on
^3.x? The v4 release contains breaking changes including API renames,ContentsquareRootremoval, and native cleanup. Read the LegacyContentsquareAPI toCSQ(v3.x → v4.x) section before upgrading.
Quick Start
import 'package:contentsquare/csq.dart';
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
CSQ().collectFlutterError(details: details);
};
await CSQ().start();
await CSQ().optIn(); // TODO: customer to manage consent — call only after user accepts
runApp(const MyApp());
}, (error, stack) => CSQ().collectError(error: error, stackTrace: stack));
}
Avoid
runZonedGuardedonly when the app architecture makes it necessary. Alternative init without it: references/common-patterns.md.
It is very important that screens should be tracked, otherwise Session replay will not work. You can either track screens manually with trackScreenview() or use the recommended CSQNavigatorObserver for automatic tracking.
Add automatic screen tracking:
MaterialApp(
navigatorObservers: [CSQNavigatorObserver()],
);
Critical: Add the required iOS setup for iOS In-App Features.
Before wrapping the MVP, make sure you are handling webviews if they are present in the app. Webviews are not automatically tracked, and every webview must be wrapped. Check the webview section below for details.
API Surface
Always use CSQ from package:contentsquare/csq.dart. The Contentsquare class is deprecated.
Lifecycle
| Method | Purpose |
|---|---|
start({StartConfig, CSQMaskingConfig?}) | Start the SDK. Must be called before any other API. |
stop() | Shut down the SDK completely. |
pauseTracking() | Pause data collection (for sensitive screens). Avoid unless explicitly requested. |
resumeTracking() | Resume after pauseTracking(). |
StartConfig Variants
| Factory | Use Case | Onboarding priority |
|---|---|---|
StartConfig.withDatasourceId(id:, options:) | CSQ with data source ID | 1st choice for new clients -- ask for this first |
StartConfig.withEnvironmentId(id:, options:) | Product Analytics with environment ID | 2nd choice if no data source ID is available |
StartConfig.dxa({options}) | Digital Experience Analytics only (default). options is AnalyticsOptions -- new in 4.2.0 | Fallback only -- when neither ID is available or DXA-only is explicitly requested |
All three variants accept the same AnalyticsOptions object (see the AnalyticsOptions section below).
Recommended onboarding flow:
- Ask the user: "Do you have a Contentsquare data source ID?" (skip the question if they already answered it during the healthcheck). If yes, use
StartConfig.withDatasourceId(id: ...). - If not, ask: "Do you have an environment ID?" If yes, use
StartConfig.withEnvironmentId(id: ...). - Only if neither is available (or the user explicitly wants DXA-only), fall back to
StartConfig.dxa(). - Ask: "Is your Product Analytics / Unified CSQ environment hosted in the EU?" If yes, set
baseUrl: Uri.parse('https://mh.ba.contentsquare.net')inAnalyticsOptions. The default endpoint is US -- EU environments will not receive data without this override.
Do not invent an ID. Do not silently default to DXA. Do not assume US hosting -- always ask.
Privacy
| Method | Purpose |
|---|---|
optIn() | Opt the device into tracking. Starts immediately. |
optOut() | Opt out permanently. Stops until optIn() or app reinstall. |
Identity
| Method | Context | Purpose |
|---|---|---|
identify(userIdentifier:) | Product Analytics | Set user identity. Different ID triggers new session. |
resetIdentity() | Product Analytics | Clear identity. Triggers new session. |
sendUserIdentifier(userIdentifier:) | DXA | Send hashed user ID. Max 100 chars. |
Screen Tracking
| Method | Purpose |
|---|---|
trackScreenview(screenName:, customVars:) | Track a screen view with optional custom variables. |
CustomVar(index:, name:, value:) -- index (int), name (String), value (String).
Transactions
await CSQ().trackTransaction(
Transaction(price: 29.99, currency: Currency.USD, id: 'order_123'),
);
Currency is an ISO 4217 enum (e.g., Currency.USD, Currency.EUR).
Custom Events and Properties
| Method | Purpose |
|---|---|
trackEvent(eventName:, properties:) | Track a named event with optional properties. |
addDynamicVar(DynamicVar) | Add session-level dynamic variable. |
addUserProperties(properties:) | Set user-level properties. |
addEventProperties(properties:) | Set properties on all future events. |
removeEventProperty(name:) | Remove one event property. |
clearEventProperties() | Remove all event properties. |
Property values must be String, num, or bool. Other types are silently ignored.
DynamicVar factories: DynamicVar.fromString(key:, value:) and DynamicVar.fromInt(key:, value:).
Session Replay
| Method | Purpose |
|---|---|
triggerReplayForCurrentSession(name:) | Flag the session for replay capture (for segmentation/filtering). |
triggerReplayForCurrentScreen(name:) | Flag the current screen for replay capture. |
startSessionReplay() (new in 4.2.0) | Start Session Replay on-demand. Use when sessionReplayAutoStart: false was passed to start(). One-shot -- not a restart API. |
stopSessionReplay() (new in 4.2.0) | Stop Session Replay on-demand without stopping the SDK. Terminal -- SR cannot be re-started afterwards in the same SDK lifetime. |
On-demand Session Replay -- disable auto-start at SDK launch and control it manually:
await CSQ().start(
startConfig: StartConfig.dxa(
options: const AnalyticsOptions(sessionReplayAutoStart: false),
),
);
// later, when you want SR to start (e.g., entering a checkout flow):
await CSQ().startSessionReplay();
// and to stop it (final -- no restart afterwards):
await CSQ().stopSessionReplay();
startSessionReplay/stopSessionReplayare start + stop only, not pause/resume. AfterstopSessionReplay()SR stays off until the next SDK lifetime.
Session Metadata (get replay link)
Access session information including the replay URL:
// Get current session replay URL (returns Uri?)
final replayUrl = CSQ().metadata.sessionReplayUrl;
// Listen for metadata changes
final subscription = CSQ().metadata.onChange((metadata) {
print('Session Replay URL: ${metadata.sessionReplayUrl}');
});
subscription.cancel(); // Stop listening when done
Metadata fields: projectId, environmentId, userId, identity, sessionId, sessionReplayUrl (Uri?).
Error Reporting
| Method | Purpose |
|---|---|
collectFlutterError(details:, presentErrorDetails:) | Report Flutter framework errors. |
collectError(error:, stackTrace:) | Report caught errors. |
setURLMaskingPatterns(patterns:) | Mask sensitive URL paths in error tracking. Emails are masked automatically. |
For automatic HTTP request error tracking, install CSQHttpOverrides as the global HTTP overrides at app startup — see references/api-reference.md.
Debug and Metadata
| API | Purpose |
|---|---|
debug.setLogLevel(LogLevel) | Set log level (info, verbose, etc.) |
metadata | Access session metadata. metadata.onChange(handler) to listen. |
AnalyticsOptions (shared by all StartConfig variants)
All fields are nullable (null = native default behavior).
| Field | Type | Platform | Purpose |
|---|---|---|---|
enableInteractionsAutocapture | bool? | Both | Enable Flutter interaction autocapture (PA) |
disablePageviewAutocapture | bool? | Both | Disable native pageview autocapture |
enablePushNotificationAutocapture | bool? | Both | Capture push notification events |
enablePushNotificationTitleAutocapture | bool? | Both | Capture push notification title |
enablePushNotificationBodyAutocapture | bool? | Both | Capture push notification body |
baseUrl | Uri? | Both | Custom API endpoint. Required for EU-hosted Product Analytics environments: set to Uri.parse('https://mh.ba.contentsquare.net'). Also used for on-prem / custom endpoints. |
uploadInterval | Duration? | Both | How often events are uploaded |
sessionReplayAutoStart | bool? | Both | If false, SR doesn't start at SDK launch -- use startSessionReplay() |
disablePageviewTitleAutocapture | bool? | iOS only | Disable iOS page title autocapture |
disableInteractionAutocapture | bool? | iOS only | Disable native iOS interaction autocapture |
Removed in 4.2.0 (breaking):
captureVendorId,captureAdvertiserId,clearEventPropertiesOnNewUser,resumePreviousSession,disableScreenviewForwardToDXA,disableScreenviewForwardToPA,messageBatchByteLimit,messageBatchMessageLimit,pruningLookBackWindow,maximumDatabaseSize,maximumBatchCountPerUpload. See references/migration.md for replacements.
ProductAnalyticsOptionsis now atypedefalias forAnalyticsOptions(kept for back-compat). PreferAnalyticsOptionsin new code.
Session Replay Masking
Global masking (at start)
await CSQ().start(
maskingConfig: const CSQMaskingConfig(
maskTexts: true,
maskTextFields: true,
maskImages: false,
),
);
Scoped masking (per widget subtree)
CSQMask(
config: const CSQMaskingConfig(maskTexts: true, maskInteractions: true),
child: Text('Sensitive content'),
)
CSQMaskingConfig fields
| Field | What it masks |
|---|---|
maskTexts | Text, RichText |
maskTextFields | TextField, TextFormField |
maskImages | Image widgets |
maskSvgImages | SVG-rendered pictures |
maskCharts | fl_chart widgets |
maskCustomPaints | Developer CustomPaint widgets |
maskInteractions | User interactions (Product Analytics only) |
Convenience: CSQMaskingConfig.maskAll() and CSQMaskingConfig.unMaskAll().
Navigator Observer (Auto Screen Tracking)
Before adding the observer, check
pubspec.yamlfor the navigation package in use:
auto_route→ useCSQNavigatorAutoRouteObserver(see AutoRoute section below)go_router→ useCSQNavigatorObserverviaGoRouter(observers: [...])- Neither → use
CSQNavigatorObserverviaMaterialApp(navigatorObservers: [...])
Standard Flutter Navigator (MaterialApp / CupertinoApp)
import 'package:contentsquare/csq.dart';
MaterialApp(
navigatorObservers: [
CSQNavigatorObserver(
screenNameProvider: (route) {
if (route.settings.name == '/profile') return 'User Profile';
return route.settings.name;
},
customVarsProvider: (route) => [
CustomVar(index: 1, name: 'section', value: 'main'),
],
excludeRouteFromTracking: (route) =>
route.settings.name?.startsWith('/debug') ?? false,
),
],
);
All three callbacks are optional. Without them, route names are used as screen names.
Modal routes (
showDialog,showModalBottomSheet, etc.) are tracked as separate screens by default. UseexcludeRouteFromTrackingto skip them.
GoRouter
No extra package needed. Pass CSQNavigatorObserver via the observers parameter on GoRouter — not via MaterialApp.router.
GoRouter(
observers: [CSQNavigatorObserver()],
routes: [...],
)
When using
pageBuilderin aGoRoute, you must setnameon theMaterialPage— otherwise the observer receives a nameless route. See references/common-patterns.md.
AutoRoute
AutoRoute is not compatible with CSQNavigatorObserver. Use CSQNavigatorAutoRouteObserver from the csq_navigator_auto_route_observer package. It also handles tab navigation automatically (didInitTabRoute / didChangeTabRoute).
Add csq_navigator_auto_route_observer: ^1.1.0 to dependencies, then attach it to the router config:
import 'package:csq_navigator_auto_route_observer/csq_navigator_auto_route_observer.dart';
MaterialApp.router(
routerConfig: _appRouter.config(
navigatorObservers: () => [
CSQNavigatorAutoRouteObserver(),
],
),
);
Supports the same three optional callbacks as CSQNavigatorObserver (screenNameProvider, customVarsProvider, excludeRouteFromTracking). Full callback reference and modal exclusion examples: references/screen-tracking.md.
Compatibility: AutoRoute >=10.1.0+1 <12.0.0, csq_navigator_auto_route_observer ^1.1.0.
PageView and TabBar (manual tracking required)
PageView swipes and standard TabBar tab changes do not push routes, so observers are never triggered. Call trackScreenview manually:
- PageView: use the
onPageChangedcallback - TabBar: use
TabController.addListener
See references/screen-tracking.md for full code examples.
WebView Tracking
WebViews are not automatically tracked. You must wrap them with CSQWebViewWrapper and set up a JavaScript channel bridge to enable session stitching between the native app and web content.
Prerequisites:
- JavaScript must be enabled in the WebView
- The web pages loaded in the WebView must have the Contentsquare Web Tracking Tag in WebView mode
Using webview_flutter (JSChannelWebViewWrapperDelegate)
import 'package:contentsquare/csq.dart';
import 'package:webview_flutter/webview_flutter.dart';
webViewController.setJavaScriptMode(JavaScriptMode.unrestricted);
CSQWebViewWrapper(
delegate: JSChannelWebViewWrapperDelegate(
addJavaScriptChannel: (WebViewJSChannelHandler handler) {
webViewController.addJavaScriptChannel(
handler.channelName,
onMessageReceived: (jsMessage) {
handler.onMessageReceived(jsMessage.message);
},
);
},
builder: (context) {
return WebViewWidget(controller: webViewController);
},
),
);
Using flutter_inappwebview (UserScriptWebViewWrapperDelegate)
import 'dart:collection';
import 'package:contentsquare/csq.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
CSQWebViewWrapper(
delegate: UserScriptWebViewWrapperDelegate(
builder: (BuildContext context, String userScript) {
return InAppWebView(
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(javaScriptEnabled: true),
),
initialUrlRequest: URLRequest(url: Uri.parse(url)),
initialUserScripts: UnmodifiableListView([
UserScript(
source: userScript,
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
),
]),
);
},
),
);
Important:
- Always use
handler.channelNameas the channel name (do not hardcode) - Always call
handler.onMessageReceived(...)in the JavaScript channel callback - For
UserScript, always setinjectionTimetoAT_DOCUMENT_START - The underlying WebView must use
android.webkit.WebViewon Android andWKWebViewon iOS
Common Patterns
1. Full initialization with error handling
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await CSQ().start(
maskingConfig: const CSQMaskingConfig(maskTexts: true, maskTextFields: true),
);
await CSQ().optIn();
FlutterError.onError = (details) {
CSQ().collectFlutterError(details: details);
};
runZonedGuarded(
() => runApp(const MyApp()),
(error, stack) => CSQ().collectError(error: error, stackTrace: stack),
);
}
2. On-demand Session Replay (sensitive flows)
// 1) Disable SR auto-start globally
await CSQ().start(
startConfig: StartConfig.withEnvironmentId(
id: 'your-env-id',
options: const AnalyticsOptions(sessionReplayAutoStart: false),
),
);
// 2) Start SR once, when entering the flow you want recorded
await CSQ().startSessionReplay();
// 3) Stop SR when the flow is done.
// WARNING: this is terminal for the SDK lifetime -- you cannot
// restart SR after this call. Do NOT wire it to dispose() of
// short-lived widgets if you may want SR again later in the same session.
await CSQ().stopSessionReplay();
More patterns (Product Analytics setup, transactions, consent flow, custom events): references/common-patterns.md.
iOS In-App Features Setup
To use Contentsquare in-app features on iOS (screenshot capture, SDK logs, Zoning Analysis), native iOS configuration is required:
- Add URL scheme to
ios/Runner/Info.plist:cs-$(PRODUCT_BUNDLE_IDENTIFIER) - Handle deeplinks in
AppDelegate.swiftby callingCSQ.handle(url: url)
This is one of the few cases where editing native files is allowed.
Full implementation guide with code samples for AppDelegate, SceneDelegate, and SwiftUI: references/ios-in-app-features.md
Migrations
Full migration playbooks live in references/migration.md. Load that file before performing an upgrade.
| Customer's current state | Migration to read |
|---|---|
On ^3.x, uses Contentsquare() singleton, ContentsquareRoot, MaskingConfig, ContentsquareNavigatorObserver, etc. | Legacy Contentsquare API to CSQ (v3.x → v4.x) -- API renames, ContentsquareRoot removal, native AndroidManifest.xml / Info.plist cleanup, Swift Contentsquare.handle( → CSQ.handle(. |
On ^4.1.x, may use configureProductAnalytics(...) and/or removed ProductAnalyticsOptions fields (captureVendorId, resumePreviousSession, pruningLookBackWindow, etc.) | 4.1.x to 4.5.1 -- breaking: 11 ProductAnalyticsOptions fields removed; deprecation of configureProductAnalytics (removal August 2026); new startSessionReplay / stopSessionReplay and sessionReplayAutoStart option. |
Important Constraints
- Ask for IDs in this order: Before integrating, ask the user (1) for a Contentsquare data source ID (reuse the healthcheck answer) -- this is the recommended setup for new clients. (2) If they don't have one, ask for an environment ID. (3) Only if they have neither (or explicitly want DXA-only), fall back to
StartConfig.dxa(). Map the answers to:StartConfig.withDatasourceId(id:)→StartConfig.withEnvironmentId(id:)→StartConfig.dxa(). Do not invent an ID and do not silently default to DXA. - Never touch native code or native config (with exceptions): Integration is Dart-only by default. Do not edit files under
android/,ios/,macos/,windows/,linux/unless explicitly allowed. Allowed exceptions:- iOS in-app features setup: Add the
cs-$(PRODUCT_BUNDLE_IDENTIFIER)URL scheme toios/Runner/Info.plistand add the deeplink handler toAppDelegate.swift(see iOS In-App Features Setup section). - v3.x → v4.x migration cleanup: Remove the legacy
com.contentsquare.android.autostartmeta tag fromAndroidManifest.xml, theCSDisableAutostartkey fromInfo.plist, and rename SwiftContentsquare.handle(→CSQ.handle((see references/migration.md). For any native change outside these scopes, stop and ask the user instead of editing.
- iOS in-app features setup: Add the
- Always configure iOS in-app features: Apply the iOS setup on every new integration (URL scheme +
CSQ.handle(url:)inAppDelegate.swift). See iOS In-App Features Setup and references/ios-in-app-features.md for full samples. - Detect navigation package & widgets: Scan
pubspec.yamland code.auto_route→ installcsq_navigator_auto_route_observerand useCSQNavigatorAutoRouteObserveron_appRouter.config(navigatorObservers: ...);go_router→CSQNavigatorObserveronGoRouter(observers: [...])(notMaterialApp.router); neither →CSQNavigatorObserveronMaterialApp(navigatorObservers: [...]). Additionally,PageViewandTabBarwidgets do not push routes — require manualtrackScreenviewinonPageChanged/TabController.addListener. - Scan for WebViews: During integration, search the codebase for any existing WebView usage (
webview_flutter,flutter_inappwebview,WebViewWidget,InAppWebView). WebViews are not automatically tracked -- every WebView must be wrapped withCSQWebViewWrapperand the appropriate delegate. Do not skip this step. - Call
start()first: All other CSQ methods requirestart()to have been called. - CSQ, not Contentsquare: The
Contentsquareclass is deprecated. Always useCSQ(). configureProductAnalyticsis deprecated (removal August 2026). UseCSQ().start(startConfig: StartConfig.withEnvironmentId(...))instead. Never combine the two -- the deprecated API takes precedence and silently overridesstart(startConfig: ...).AnalyticsOptionsoverProductAnalyticsOptions: the latter is atypedefkept for back-compat. UseAnalyticsOptionsin new code; it now applies to DXA too.- Platform support: Android and iOS only. Web/desktop return a no-op implementation.
- Flutter version: Requires Flutter >= 3.27.0, Dart >= 3.6.0.
- Property types: Only
String,num,boolare accepted for event/user properties. - DynamicVar limits: Key max 49 chars (asserted
< 50), string value max 255 chars (asserted< 256), int value range0..2^32-1. - userIdentifier: Recommended max 100 characters for both
identify()andsendUserIdentifier(). - Privacy: Never auto-opt-in. Always implement a consent flow.
- PII: Never pass emails, passwords, or real names in screen names, event names, or properties.
- Environment IDs: Never hardcode -- use configuration or environment variables.
- iOS: Check iOS compatibility docs before integration.
References
- Migration playbooks (legacy v3 → v4 and 4.1.x → 4.5.1): references/migration.md
- Full method signatures and parameter details: references/api-reference.md
- Session Replay deep dive (masking, on-demand SR, known limitations): references/session-replay.md
- iOS in-app features setup (URL scheme, deeplink handling, debugging): references/ios-in-app-features.md
- Screen tracking setups (GoRouter, AutoRoute, PageView, TabBar, modal exclusion, naming guidelines): references/screen-tracking.md
- Common patterns (alternative init, transactions, consent, events, GoRouter pageBuilder): references/common-patterns.md
- Official Flutter documentation