Connect FlutterFlow to Optimizely Feature Experimentation using a Custom Action (Dart) wrapping the official `optimizely_flutter_sdk` pub.dev package. You initialize the SDK with your public SDK Key on app start, then call `createUserContext(userId).decide(flagKey)` inside a second Custom Action to gate features or run A/B test variants. The SDK Key is public — safe to ship in the app — but test only on a real device, not in FlutterFlow's web preview.
| Fact | Value |
|---|---|
| Tool | Optimizely |
| Category | Marketing |
| Method | Custom Action (Dart) |
| Difficulty | Advanced |
| Time required | 45 minutes |
| Last updated | July 2026 |
Run Feature Flags and A/B Tests Inside Your FlutterFlow App with Optimizely
Optimizely Feature Experimentation is unlike the other marketing integrations in this series. Rather than calling a REST API to push or pull data, Optimizely ships an official Flutter SDK (`optimizely_flutter_sdk` on pub.dev) that runs entirely on the device. The SDK downloads a JSON datafile from Optimizely's CDN, keeps it updated via polling, and evaluates flag decisions locally — fast, no per-decision API call, and works even with intermittent connectivity. For a FlutterFlow app, this means you can gate a screen behind a feature flag, roll out a new checkout flow to 20% of users, or run a real A/B test on button copy — all controlled from the Optimizely console without a new app release.
The auth model differs fundamentally from every other tool here: the SDK Key is a public, per-environment key found in Optimizely under Settings → Environments. It is designed to ship inside client apps and does not grant write access. Do not confuse it with Optimizely's REST API personal access token (used for admin operations at `api.optimizely.com`) — that one must stay server-side.
Implementation uses two FlutterFlow Custom Actions and the `optimizely_flutter_sdk` pub.dev dependency. The first Custom Action initializes the SDK on app start. The second evaluates a flag and writes the result to App State so any widget on any screen can react to it with conditional visibility. Critical constraint: Custom Actions with native Flutter plugins do not run in FlutterFlow's web-based Test Mode — you must test on a real device or via Local Run. Optimizely Feature Experimentation is on paid/enterprise plans; verify current pricing at optimizely.com/pricing.
Integration method
The integration uses two Dart Custom Actions in FlutterFlow, powered by the official `optimizely_flutter_sdk` package added as a pub.dev dependency. The first Custom Action initializes the Optimizely client with your SDK Key on app start (the SDK handles datafile polling automatically). The second Custom Action creates a user context and calls `decide(flagKey)` to return a flag-enabled boolean and optional variation key, which is stored in App State so any screen can react to it with conditional visibility.
Prerequisites
- An Optimizely Feature Experimentation account with at least one project and one feature flag created (check optimizely.com/pricing for current plan options)
- Your SDK Key copied from Optimizely → Settings → Environments (Development or Production)
- A FlutterFlow project open in the browser, with App State variables ready to store flag results
- Access to a real iOS or Android device (or FlutterFlow Local Run) for testing — Custom Actions do not run in web Test Mode
- Basic familiarity with FlutterFlow's Custom Code panel and Action Flow Editor
Step-by-step guide
Create a flag in Optimizely and copy your SDK Key
Log into app.optimizely.com and select your project (or create one). In the left navigation, go to 'Feature Flags' and click 'Create New Feature Flag'. Give it a key like `new_onboarding_v2` or `checkout_button_test` — this key is what you will pass to `decide()` in your Dart code, so note it exactly. The flag key is case-sensitive. If you want to run an A/B test rather than a simple on/off flag, add variations to the flag: click 'Variables & Variations' and add a string or boolean variable that each variation sets differently (e.g., `button_text` = 'Get Started' in control, 'Start Free Trial' in challenger). To get the SDK Key, navigate to Settings → Environments. You will see at least two environments: Development and Production. Each environment has its own SDK Key. Copy the Development SDK Key for testing — you will use the Production key when you are ready to publish. The SDK Key is a long alphanumeric string and is safe to include in client code. Before leaving the Optimizely console, activate the flag (toggle it 'On') and if you have an audience or traffic allocation rule, configure it. For testing, set traffic to 100% and audience to 'Everyone'. Save your changes.
Pro tip: Copy the flag key exactly as shown — lowercase, underscores, no spaces. A typo in the flag key causes `decide()` to return the default off state silently, which looks like the SDK is not working when the flag key is just misspelled.
Expected result: You have a feature flag with a key (e.g., `new_onboarding_v2`) created and activated in Optimizely, and you have the Development SDK Key copied.
Add `optimizely_flutter_sdk` as a FlutterFlow pub dependency
In your FlutterFlow project, click 'Custom Code' in the left navigation bar. In the Custom Code panel, look for the 'Dependencies' or 'Pub Dependencies' section. Click '+ Add Dependency' and type `optimizely_flutter_sdk`. FlutterFlow will look up the package on pub.dev — select the latest stable version from the dropdown. Do not add a version constraint manually; let FlutterFlow handle version resolution. Click 'Save'. FlutterFlow automatically adds this package to the `pubspec.yaml` of the generated Flutter project. You do not need to run `flutter pub get` — FlutterFlow handles this when you build or test the app. This is a native Flutter plugin, which is why it will not function in FlutterFlow's web-based Test Mode (the 'Run' button in the browser). The plugin requires platform code that only works on compiled iOS and Android builds or FlutterFlow Local Run. Verify the dependency was accepted: refresh the Custom Code panel and confirm `optimizely_flutter_sdk` appears in the list of dependencies without an error badge. If it shows an error, check that the package name is spelled correctly — the exact name on pub.dev is `optimizely_flutter_sdk` (all lowercase, underscores).
Pro tip: If FlutterFlow shows a version conflict warning after adding the package, check pub.dev for the latest stable version of `optimizely_flutter_sdk` and try pinning to that version explicitly in the FlutterFlow Dependencies field.
Expected result: The `optimizely_flutter_sdk` dependency appears in FlutterFlow's Custom Code → Dependencies list without an error badge.
Write the Initialize Optimizely Client Custom Action
In the Custom Code panel, click '+ Add' and choose 'Action'. Name it `initOptimizely`. Set the return type to void (this action has no return value — it initializes a shared client). In the arguments section, add one argument: `sdkKey` (String). This is where you will pass your SDK Key from FlutterFlow's App Values or directly from an action parameter. Paste the Dart code below into the code editor. The action initializes the Optimizely Flutter client using the provided SDK Key, waits for the datafile to be downloaded, and stores the initialized client in a static variable so the decide action can reference it later. The `OptimizelyDecideOption` and `OptimizelyClientConfig` classes come from the `optimizely_flutter_sdk` package. After pasting the code, click 'Save'. If FlutterFlow shows any import errors, confirm the dependency is correctly added in the Dependencies tab. Then go to your app's initial screen (usually the main screen or a splash screen) and add this action in the 'On Page Load' action chain: Action Flow Editor → '+ Add Action' → 'Custom Actions' → 'initOptimizely' → pass your SDK Key string. For the SDK Key value, either type it directly or store it in FlutterFlow's App Values (left nav → App Values → + Add Constant) and reference it there. Note: this action is async and the Optimizely client takes a moment to download the datafile. Guard your decide calls to run only after initialization completes. A simple way is to use a Boolean App State variable `optimizelyReady` that the init action sets to true on completion.
1// Custom Action: initOptimizely2// Argument: sdkKey (String)3// Return: void45import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart';67// Static holder so decideFlag can access the same client8class OptimizelyHolder {9 static OptimizelyFlutterSdk? client;10}1112Future<void> initOptimizely(String sdkKey) async {13 // Create the client14 final client = OptimizelyFlutterSdk(sdkKey);1516 // Start the client and wait for the datafile to be fetched17 final response = await client.start();1819 if (response.success) {20 OptimizelyHolder.client = client;21 // The SDK now has the datafile and is ready for decide() calls22 } else {23 // Log or handle initialization failure24 // response.reason contains the error message25 debugPrint('Optimizely init failed: ${response.reason}');26 }27}28Pro tip: Add a Boolean App State variable called `optimizelyReady` and set it to `true` inside the `if (response.success)` block by updating App State from within the Custom Action. Then use conditional visibility on screens that depend on flag decisions — only show them when `optimizelyReady` is true.
Expected result: The `initOptimizely` Custom Action is saved in FlutterFlow without errors and is wired to run on the app's initial screen load.
Write the Decide Flag Custom Action and bind results to App State
In the Custom Code panel, click '+ Add' and choose 'Action'. Name it `decideFlagAction`. Add two arguments: `userId` (String — a unique ID for the current user, typically the user's UID from Firebase Auth or a device ID) and `flagKey` (String — the flag key from Optimizely, e.g., `new_onboarding_v2`). Set the return type to Boolean (or JSON if you need the full decision object including variation key). Paste the Dart code below. The action retrieves the shared client from `OptimizelyHolder.client`, creates a user context with the provided userId, calls `decide(flagKey)`, and returns the `enabled` boolean. If the client is null (init has not completed yet), it returns false as the safe default. After saving, go to the App State panel in FlutterFlow (left nav → App State → + Add Field) and add a Boolean field called `flagEnabled` (default: false). Optionally add a String field `flagVariation` to store the variation key. To use the decide action: in the Action Flow Editor of the relevant screen or widget, add '+ Add Action' → 'Custom Actions' → 'decideFlagAction'. Pass `userId` from your authentication state (e.g., Firebase Auth user UID, or `FFAppState().userId` if stored). Pass the flag key as a string literal (`new_onboarding_v2`). Set the action's 'Return Value' output to update the `flagEnabled` App State variable. After the action, add conditional navigation or use FlutterFlow's conditional visibility settings to show different UI based on `flagEnabled`.
1// Custom Action: decideFlagAction2// Arguments: userId (String), flagKey (String)3// Return: bool45import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart';67Future<bool> decideFlagAction(8 String userId,9 String flagKey,10) async {11 final client = OptimizelyHolder.client;1213 // Guard: if init hasn't completed, return safe default (off)14 if (client == null) {15 debugPrint('Optimizely client not initialized — returning false for $flagKey');16 return false;17 }1819 // Create a user context for this user20 final userContext = await client.createUserContext(userId: userId);2122 if (userContext == null) {23 debugPrint('Failed to create Optimizely user context for $userId');24 return false;25 }2627 // Decide the flag28 final decision = await userContext.decide(flagKey);2930 // decision.enabled is the main result31 // decision.variationKey is the variation name for A/B tests32 debugPrint(33 'Flag $flagKey: enabled=${decision.enabled}, variation=${decision.variationKey}'34 );3536 return decision.enabled;37}38Pro tip: If you need the variation key (for A/B tests where you want to know which variant the user saw, not just enabled/disabled), change the return type of the Custom Action to String and return `decision.variationKey ?? 'control'`. Store this in a String App State variable and use it in conditional expressions for button text, colours, or layout choices.
Expected result: The `decideFlagAction` Custom Action is saved and wired to update the `flagEnabled` App State variable. When triggered, FlutterFlow widgets using conditional visibility on `flagEnabled` respond correctly.
Test on a real device and add conditional UI based on flag state
Custom Actions that use native Flutter plugins — including `optimizely_flutter_sdk` — do not run in FlutterFlow's browser-based Test Mode (the 'Run' button). This is a hard limitation: the web preview uses a JavaScript renderer that does not execute native Dart plugin code. When you tap the preview in the browser and nothing happens, the Custom Action code is not running — this is expected, not a bug in your code. To test, you have two options. First, FlutterFlow Local Run: install the FlutterFlow companion app on a physical iOS or Android device, connect via Local Run from the FlutterFlow toolbar, and your app runs natively on the device with live reload. Second, download the Flutter project from FlutterFlow (via the Download Code button) and run it locally with `flutter run` — but Local Run is the easier option that stays in the FlutterFlow workflow. Once running on device, observe the debug console for the `debugPrint` messages from the init and decide actions. You should see `Optimizely init failed` or confirmation of the initialized client, then the decide output showing the flag key, enabled status, and variation key. For the conditional UI: on the screen where you want to gate a feature, select the widget (Container, Screen, or Button) you want to show/hide. In its properties panel, find 'Conditional Visibility' and set the condition to 'App State → flagEnabled == true'. If you are running an A/B test, use a conditional value on a Text widget's content property: if `App State → flagVariation == 'challenger'` then 'Start Free Trial', else 'Get Started'. If you would rather skip writing the Custom Actions yourself, RapidDev's team builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.
Pro tip: In Optimizely, you can force a specific user ID to a specific variation in the Optimizely UI (under Audiences → Forced Decisions or the flag's traffic allocation settings). Use a static test user ID during development so you can predictably trigger each variant without random traffic allocation.
Expected result: On a real device, the `initOptimizely` action runs on startup, `decideFlagAction` returns the correct enabled/variation state, and conditional UI renders the right variant for the flag state configured in the Optimizely console.
Common use cases
Feature flag controlling a new onboarding flow
A team is rebuilding their onboarding flow in FlutterFlow and wants to test it with 10% of new users before a full rollout. They create a flag `new_onboarding_v2` in Optimizely, initialize the SDK on app start, and use the decide result to conditionally show the new flow or the existing one — controlled entirely from the Optimizely console.
When the app launches, initialize Optimizely and check the flag 'new_onboarding_v2' for this user. If enabled, navigate to the new onboarding screen; otherwise show the existing one.
Copy this prompt to try it in FlutterFlow
A/B test on subscription page button copy and colour
A SaaS founder wants to test whether 'Start Free Trial' or 'Get Started' converts better on their paywall screen. They create an Optimizely flag with two variations, write the variation key to App State when the user reaches the screen, and bind the button text and colour to conditional values based on the variation. Optimizely tracks which variation the user saw alongside conversion events.
On the subscription screen, check the 'paywall_button_test' Optimizely flag. If variation is 'control' show a green button with 'Get Started'. If variation is 'challenger' show a blue button with 'Start Free Trial'.
Copy this prompt to try it in FlutterFlow
Progressive rollout of a new AI-powered search screen
A marketplace app is adding an AI search screen but wants to roll it out to 5% of users first, then gradually increase. The Optimizely flag controls visibility of the search icon in the navigation bar — no code change, no app store release needed to adjust the rollout percentage.
Check the 'ai_search_enabled' flag on app start. Store the result in App State. Show the AI search navigation icon only when the flag is enabled for this user.
Copy this prompt to try it in FlutterFlow
Troubleshooting
Optimizely decide always returns `false` (flag disabled) even though the flag is enabled in the console
Cause: Either the SDK Key is wrong (points to the wrong environment), the flag key string has a typo, or the Optimizely client is not yet initialized when decide is called.
Solution: Check the debug console for the initOptimizely debugPrint message — if you see 'Optimizely init failed', the SDK Key is likely wrong. Copy the SDK Key again from Optimizely → Settings → Environments and ensure you are using the right environment (Development vs Production). If init succeeds but decide still returns false, log `decision.variationKey` and `decision.reasons` from the decide response — `decision.reasons` lists why a flag is disabled (e.g., flag is paused, user does not match audience, traffic is 0%).
1// Log full decision reasons for debugging2final decision = await userContext.decide(flagKey);3debugPrint('Decide reasons: ${decision.reasons}');4Nothing happens when triggering Custom Actions in FlutterFlow's web Test Mode (browser preview)
Cause: Native Flutter plugin Custom Actions do not run in FlutterFlow's web-based Test Mode. The browser preview uses a JavaScript renderer and cannot execute native Dart plugin code.
Solution: This is expected behavior, not a bug. Test the integration using FlutterFlow Local Run (connect a physical device via the Local Run option in the FlutterFlow toolbar) or download the Flutter code and run it with `flutter run` on a device. Your web-built FlutterFlow UI will work fine — only native plugin Custom Actions require device testing.
`OptimizelyHolder.client` is null when `decideFlagAction` runs
Cause: The decide action was called before the initialize action completed, or the initialize action ran on a different action thread and the static holder was not populated.
Solution: Ensure `initOptimizely` is called at the very beginning of the app (in the 'On Page Load' of your initial/splash screen) and that it completes before any decide calls. Add an App State Boolean `optimizelyReady` that is set to true inside the `if (response.success)` block in the init action, and wrap all decide calls in a conditional that checks `optimizelyReady` first.
FlutterFlow shows a dependency conflict error after adding `optimizely_flutter_sdk`
Cause: The package version conflicts with another Flutter dependency in the project (FlutterFlow manages its own set of packages).
Solution: Check pub.dev for the latest stable version of `optimizely_flutter_sdk` and try pinning to an earlier version that resolves the conflict. In FlutterFlow's Dependencies field, enter the version as `^X.Y.Z` (e.g., `^2.0.0`). If the conflict persists, check the Optimizely Flutter SDK GitHub repository for known compatibility issues with recent FlutterFlow Flutter versions.
Best practices
- Initialize the Optimizely client once on app start (in the initial screen's On Page Load action) and store the result in App State — calling initialize on every screen wastes datafile downloads and adds latency.
- Use a static Dart class (like `OptimizelyHolder`) to share the initialized client between Custom Actions, since FlutterFlow Custom Actions cannot directly pass Dart objects through App State variables.
- Always guard decide calls with a null check on the client and return false (the safe default) if initialization has not completed — this prevents a blank or broken UI state during startup.
- Use the Development SDK Key for testing and the Production SDK Key only in your production build — they control separate traffic allocations and you do not want test users affecting production experiment results.
- Do not confuse the Optimizely SDK Key (public, safe to ship in client code) with the REST API personal access token (secret, admin-level, must stay server-side) — they are different credentials with very different security implications.
- Test on a real device or via FlutterFlow Local Run — Custom Actions using native Flutter plugins will never run in the web-based Test Mode, and this is expected, not a bug.
- Use Optimizely's forced decisions feature during development to pin specific test user IDs to a specific variant, so you can test each code path without relying on random traffic allocation.
Alternatives
Choose HubSpot if your goal is CRM contact management and marketing automation triggered by app events rather than feature flags and A/B testing — HubSpot uses a standard REST API Call pattern, not a native SDK Custom Action.
Choose Ubersuggest if you want to surface keyword and SEO metrics inside a marketing-tools app rather than experiment on your app's own UI — Ubersuggest is a straightforward API Call integration with no native SDK needed.
Frequently asked questions
Is the Optimizely SDK Key safe to include in the compiled Flutter app?
Yes. The SDK Key is a public, client-side key that Optimizely designed to be shipped in mobile and web apps. It identifies your project and environment but does not grant write access to your Optimizely account. Do not confuse it with the Optimizely REST API personal access token — that is a secret admin credential that must stay server-side.
Why does nothing happen when I test my Custom Actions in FlutterFlow's web preview?
FlutterFlow's web-based Test Mode (the 'Run' button in the browser) uses a JavaScript renderer that cannot execute native Dart plugin code. The `optimizely_flutter_sdk` is a native Flutter plugin, so it will not fire in web preview. This is expected behavior. Use FlutterFlow Local Run (connect a real device via the Local Run option in the toolbar) to test your Custom Actions with the Optimizely SDK.
Can I use Optimizely to run an A/B test on two completely different screens in FlutterFlow?
Yes. The `decideFlagAction` returns both an `enabled` boolean and a `variationKey` string. Store the variation key in App State, then use FlutterFlow's conditional navigation in your Action Flow to navigate to Screen A when the variation is 'control' and Screen B when it is 'challenger'. You can also use conditional visibility to show or hide entire Containers on a single screen.
How does Optimizely know which user is which across app sessions?
You pass a `userId` string to `createUserContext()`. This ID is the bucket key — Optimizely uses it to consistently assign the same user to the same variation across sessions. Use a stable, unique identifier like the user's Firebase Auth UID, Supabase user ID, or a UUID stored in shared preferences on first launch. Do not use a random ID that changes per session, as that would put the user in a different variation on every open.
What Optimizely plan do I need to use the Flutter SDK?
Optimizely Feature Experimentation is available on paid and enterprise plans. There is no free tier for Feature Experimentation (the free Rollouts product was discontinued). Verify current pricing and plan availability at optimizely.com/pricing before building, as plans and pricing change.
Can I use Optimizely Feature Experimentation on a Flutter web build from FlutterFlow?
The `optimizely_flutter_sdk` package targets native iOS and Android. For Flutter web builds, the native plugin code will not compile. If you need feature flagging for a web-only FlutterFlow app, you would need to use Optimizely's JavaScript SDK through a Custom Action that uses a `dart:js` interop bridge — a significantly more complex setup that requires advanced Dart and Flutter web knowledge.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation