[{"data":1,"prerenderedAt":1525},["ShallowReactive",2],{"navigation_docs":3,"-migration-guides-firebase-ab-testing":339,"-migration-guides-firebase-ab-testing-surround":1522},[4,10,71,97,249,320],{"title":5,"path":6,"stem":7,"icon":8,"exact":9},"Introduction","\u002F","0.index","i-lucide-book-open",true,{"title":11,"icon":12,"modifiedAt":13,"path":14,"stem":15,"children":16,"page":12},"Getting Started",false,"2026-04-30T19:15:33.000Z","\u002Fgetting-started","1.getting-started",[17,22,27,32,37,42,47,61,66],{"title":18,"path":19,"stem":20,"icon":21},"Quickstart","\u002Fgetting-started\u002Fquickstart","1.getting-started\u002F1.quickstart","i-lucide-fast-forward",{"title":23,"path":24,"stem":25,"icon":26},"Core Concepts","\u002Fgetting-started\u002Fcore-concepts","1.getting-started\u002F2.core-concepts","i-lucide-workflow",{"title":28,"path":29,"stem":30,"icon":31},"Configs Anatomy","\u002Fgetting-started\u002Fconfigs","1.getting-started\u002F3.configs","i-lucide-file-cog",{"title":33,"path":34,"stem":35,"icon":36},"Architecture","\u002Fgetting-started\u002Farchitecture","1.getting-started\u002F4.architecture","i-lucide-layers",{"title":38,"path":39,"stem":40,"icon":41},"JSON Schema Validation","\u002Fgetting-started\u002Fjson-schema-validation","1.getting-started\u002F5.json-schema-validation","i-lucide-braces",{"title":43,"path":44,"stem":45,"icon":46},"Testing Rules","\u002Fgetting-started\u002Ftesting-targeting-rules","1.getting-started\u002F6.testing-targeting-rules","i-lucide-flask-conical",{"title":48,"modifiedAt":49,"icon":50,"defaultOpen":12,"path":51,"stem":52,"children":53},"Experiments","2026-11-10T01:16:18.000Z","i-lucide-flask-round","\u002Fgetting-started\u002Fexperiments","1.getting-started\u002F7.experiments\u002Findex",[54,56],{"title":55,"path":51,"stem":52,"icon":8},"Overview",{"title":57,"path":58,"stem":59,"icon":60},"How results are computed","\u002Fgetting-started\u002Fexperiments\u002Fhow-results-are-computed","1.getting-started\u002F7.experiments\u002F1.how-results-are-computed","i-lucide-sigma",{"title":62,"path":63,"stem":64,"icon":65},"Segments","\u002Fgetting-started\u002Fsegments","1.getting-started\u002F8.segments","i-lucide-squares-intersect",{"title":67,"path":68,"stem":69,"icon":70},"Pricing & Billing","\u002Fgetting-started\u002Fpricing-and-billing","1.getting-started\u002F9.pricing-and-billing","i-lucide-credit-card",{"title":72,"modifiedAt":73,"defaultOpen":9,"path":74,"stem":75,"children":76,"page":12},"Monitoring","2026-09-24T23:33:20.000Z","\u002Fmonitoring","2.monitoring",[77,82,87,92],{"title":78,"path":79,"stem":80,"icon":81},"Activity","\u002Fmonitoring\u002Factivity","2.monitoring\u002F1.activity","i-lucide-activity",{"title":83,"path":84,"stem":85,"icon":86},"Lifecycle","\u002Fmonitoring\u002Flifecycle","2.monitoring\u002F2.lifecycle","i-lucide-recycle",{"title":88,"path":89,"stem":90,"icon":91},"Alerts","\u002Fmonitoring\u002Falerts","2.monitoring\u002F3.alerts","i-lucide-bell",{"title":93,"path":94,"stem":95,"icon":96},"Dynamic Detection","\u002Fmonitoring\u002Fdynamic-detection","2.monitoring\u002F4.dynamic-detection","i-lucide-hat-glasses",{"title":98,"modifiedAt":73,"defaultOpen":9,"path":99,"stem":100,"children":101,"page":12},"SDKs","\u002Fsdks","3.sdks",[102,107,128,153,169,200,244],{"title":103,"path":104,"stem":105,"icon":106},"Available SDKs","\u002Fsdks\u002Favailable-sdks","3.sdks\u002F1.available-sdks","i-lucide-list",{"title":108,"modifiedAt":73,"icon":109,"defaultOpen":12,"path":110,"stem":111,"children":112,"page":12},"Browser SDKs","i-lucide-monitor","\u002Fsdks\u002Fbrowser","3.sdks\u002F2.browser",[113,118,123],{"title":114,"path":115,"stem":116,"icon":117},"Javascript","\u002Fsdks\u002Fbrowser\u002Fjavascript","3.sdks\u002F2.browser\u002F1.javascript","cd-logos-javascript",{"title":119,"path":120,"stem":121,"icon":122},"React","\u002Fsdks\u002Fbrowser\u002Freact","3.sdks\u002F2.browser\u002F2.react","cd-logos-react-light",{"title":124,"path":125,"stem":126,"icon":127},"Vue","\u002Fsdks\u002Fbrowser\u002Fvue","3.sdks\u002F2.browser\u002F3.vue","cd-logos-vue",{"title":129,"modifiedAt":73,"icon":130,"defaultOpen":12,"path":131,"stem":132,"children":133,"page":12},"Mobile SDKs","i-lucide-smartphone","\u002Fsdks\u002Fmobile","3.sdks\u002F3.mobile",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":122},"React Native","\u002Fsdks\u002Fmobile\u002Freact-native","3.sdks\u002F3.mobile\u002F1.react-native",{"title":139,"path":140,"stem":141,"icon":142},"Flutter","\u002Fsdks\u002Fmobile\u002Fflutter","3.sdks\u002F3.mobile\u002F2.flutter","cd-logos-flutter",{"title":144,"path":145,"stem":146,"icon":147},"Swift","\u002Fsdks\u002Fmobile\u002Fswift","3.sdks\u002F3.mobile\u002F3.swift","cd-logos-swift",{"title":149,"path":150,"stem":151,"icon":152},"Android","\u002Fsdks\u002Fmobile\u002Fandroid","3.sdks\u002F3.mobile\u002F4.android","cd-logos-android",{"title":154,"modifiedAt":73,"icon":155,"defaultOpen":12,"path":156,"stem":157,"children":158,"page":12},"Meta-framework SDKs","i-lucide-monitor-smartphone","\u002Fsdks\u002Fmeta","3.sdks\u002F4.meta",[159,164],{"title":160,"path":161,"stem":162,"icon":163},"Next.js","\u002Fsdks\u002Fmeta\u002Fnextjs","3.sdks\u002F4.meta\u002F1.nextjs","cd-logos-nextjs",{"title":165,"path":166,"stem":167,"icon":168},"Nuxt","\u002Fsdks\u002Fmeta\u002Fnuxt","3.sdks\u002F4.meta\u002F2.nuxt","cd-logos-nuxt",{"title":170,"modifiedAt":73,"icon":171,"defaultOpen":12,"path":172,"stem":173,"children":174,"page":12},"Server SDKs","i-lucide-server","\u002Fsdks\u002Fserver","3.sdks\u002F5.server",[175,180,185,190,195],{"title":176,"path":177,"stem":178,"icon":179},"Node.js","\u002Fsdks\u002Fserver\u002Fnode-js","3.sdks\u002F5.server\u002F1.node-js","cd-logos-nodejs",{"title":181,"path":182,"stem":183,"icon":184},"Python","\u002Fsdks\u002Fserver\u002Fpython","3.sdks\u002F5.server\u002F2.python","cd-logos-python",{"title":186,"path":187,"stem":188,"icon":189},"Java","\u002Fsdks\u002Fserver\u002Fjava","3.sdks\u002F5.server\u002F3.java","cd-logos-java",{"title":191,"path":192,"stem":193,"icon":194},".NET","\u002Fsdks\u002Fserver\u002Fdotnet","3.sdks\u002F5.server\u002F4.dotnet","cd-logos-dotnet",{"title":196,"path":197,"stem":198,"icon":199},"Go","\u002Fsdks\u002Fserver\u002Fgo","3.sdks\u002F5.server\u002F5.go","cd-logos-go",{"title":201,"modifiedAt":73,"icon":202,"defaultOpen":12,"path":203,"stem":204,"children":205},"OpenFeature Providers","cd-logos-openfeature","\u002Fsdks\u002Fopenfeature","3.sdks\u002F6.openfeature\u002Findex",[206,207,212,216,220,224,228,232,236,240],{"title":55,"path":203,"stem":204,"icon":202},{"title":208,"path":209,"stem":210,"icon":202,"icons":211},"Web","\u002Fsdks\u002Fopenfeature\u002Fweb","3.sdks\u002F6.openfeature\u002F1.web",[202,117],{"title":144,"path":213,"stem":214,"icon":202,"icons":215},"\u002Fsdks\u002Fopenfeature\u002Fswift","3.sdks\u002F6.openfeature\u002F2.swift",[202,147],{"title":149,"path":217,"stem":218,"icon":202,"icons":219},"\u002Fsdks\u002Fopenfeature\u002Fandroid","3.sdks\u002F6.openfeature\u002F3.android",[202,152],{"title":139,"path":221,"stem":222,"icon":202,"icons":223},"\u002Fsdks\u002Fopenfeature\u002Fflutter","3.sdks\u002F6.openfeature\u002F4.flutter",[202,142],{"title":176,"path":225,"stem":226,"icon":202,"icons":227},"\u002Fsdks\u002Fopenfeature\u002Fnode-js","3.sdks\u002F6.openfeature\u002F5.node-js",[202,179],{"title":181,"path":229,"stem":230,"icon":202,"icons":231},"\u002Fsdks\u002Fopenfeature\u002Fpython","3.sdks\u002F6.openfeature\u002F6.python",[202,184],{"title":186,"path":233,"stem":234,"icon":202,"icons":235},"\u002Fsdks\u002Fopenfeature\u002Fjava","3.sdks\u002F6.openfeature\u002F7.java",[202,189],{"title":191,"path":237,"stem":238,"icon":202,"icons":239},"\u002Fsdks\u002Fopenfeature\u002Fdotnet","3.sdks\u002F6.openfeature\u002F8.dotnet",[202,194],{"title":196,"path":241,"stem":242,"icon":202,"icons":243},"\u002Fsdks\u002Fopenfeature\u002Fgo","3.sdks\u002F6.openfeature\u002F9.go",[202,199],{"title":245,"path":246,"stem":247,"icon":248},"Maven Repository","\u002Fsdks\u002Fmaven-repository","3.sdks\u002F7.maven-repository","i-lucide-package",{"title":250,"modifiedAt":73,"defaultOpen":9,"path":251,"stem":252,"children":253,"page":12},"Integrations","\u002Fintegrations","4.integrations",[254,259,264,269,274,278,283],{"title":255,"path":256,"stem":257,"icon":258},"MCP","\u002Fintegrations\u002Fmcp","4.integrations\u002F1.mcp","cd-logos-mcp",{"title":260,"path":261,"stem":262,"icon":263},"Agent Skills","\u002Fintegrations\u002Fagents","4.integrations\u002F2.agents","i-lucide-bot",{"title":265,"path":266,"stem":267,"icon":268},"CLI","\u002Fintegrations\u002Fcli","4.integrations\u002F3.cli","cd-logos-cli",{"title":270,"path":271,"stem":272,"icon":273},"Terraform","\u002Fintegrations\u002Fterraform","4.integrations\u002F4.terraform","cd-logos-terraform",{"title":275,"path":276,"stem":277,"icon":41},"Admin API","\u002Fintegrations\u002Fadmin-api","4.integrations\u002F5.admin-api",{"title":279,"path":280,"stem":281,"icon":282},"Webhooks","\u002Fintegrations\u002Fwebhooks","4.integrations\u002F6.webhooks","i-lucide-webhook",{"title":284,"modifiedAt":285,"icon":286,"defaultOpen":12,"path":287,"stem":288,"children":289},"Analytics","2026-11-08T17:10:01.000Z","i-lucide-chart-line","\u002Fintegrations\u002Fanalytics","4.integrations\u002F7.analytics\u002Findex",[290,292,296,300,304,308,312,316],{"title":55,"path":287,"stem":288,"icon":291},"i-lucide-blocks",{"title":293,"path":294,"stem":295,"icon":286},"Amplitude","\u002Fintegrations\u002Fanalytics\u002Famplitude","4.integrations\u002F7.analytics\u002F1.amplitude",{"title":297,"path":298,"stem":299,"icon":286},"Datadog","\u002Fintegrations\u002Fanalytics\u002Fdatadog","4.integrations\u002F7.analytics\u002F2.datadog",{"title":301,"path":302,"stem":303,"icon":286},"Google Analytics 4","\u002Fintegrations\u002Fanalytics\u002Fga4","4.integrations\u002F7.analytics\u002F3.ga4",{"title":305,"path":306,"stem":307,"icon":286},"Grafana","\u002Fintegrations\u002Fanalytics\u002Fgrafana","4.integrations\u002F7.analytics\u002F4.grafana",{"title":309,"path":310,"stem":311,"icon":286},"Mixpanel","\u002Fintegrations\u002Fanalytics\u002Fmixpanel","4.integrations\u002F7.analytics\u002F5.mixpanel",{"title":313,"path":314,"stem":315,"icon":286},"PostHog","\u002Fintegrations\u002Fanalytics\u002Fposthog","4.integrations\u002F7.analytics\u002F6.posthog",{"title":317,"path":318,"stem":319,"icon":286},"Segment","\u002Fintegrations\u002Fanalytics\u002Fsegment","4.integrations\u002F7.analytics\u002F7.segment",{"title":321,"modifiedAt":322,"defaultOpen":9,"path":323,"stem":324,"children":325,"page":12},"Migration Guides","2026-10-16T17:57:49.000Z","\u002Fmigration-guides","5.migration-guides",[326,331,335],{"title":327,"path":328,"stem":329,"icon":330},"From LaunchDarkly","\u002Fmigration-guides\u002Flaunchdarkly","5.migration-guides\u002F1.launchdarkly","i-lucide-baggage-claim",{"title":332,"path":333,"stem":334,"icon":330},"From Firebase Remote Config","\u002Fmigration-guides\u002Ffirebase-remote-config","5.migration-guides\u002F2.firebase-remote-config",{"title":336,"path":337,"stem":338,"icon":330},"From Firebase A\u002FB Testing","\u002Fmigration-guides\u002Ffirebase-ab-testing","5.migration-guides\u002F3.firebase-ab-testing",{"id":340,"title":341,"body":342,"description":1512,"extension":1513,"links":1514,"meta":1515,"navigation":1517,"path":337,"seo":1518,"stem":338,"__hash__":1521},"docs\u002F5.migration-guides\u002F3.firebase-ab-testing.md","Migrating from Firebase A\u002FB Testing",{"type":343,"value":344,"toc":1498},"minimark",[345,349,362,374,379,444,448,452,695,699,716,808,823,827,837,841,852,950,953,957,966,1127,1134,1163,1166,1194,1198,1207,1327,1339,1349,1357,1361,1364,1494],[346,347,348],"p",{},"This guide is for apps that run A\u002FB tests with Firebase A\u002FB Testing on Remote Config and want to run them in ConfigDirector. It maps Firebase's experiment concepts onto ConfigDirector's, shows the Flutter and web code that replaces the Remote Config and Analytics calls, compares the two results pages, and ends with a checklist.",[346,350,351,352,356,357,361],{},"The biggest change is where the results come from. Firebase measures an experiment on the events your app logs to Google Analytics. ConfigDirector measures it on events your app reports to ConfigDirector with ",[353,354,355],"code",{},"track",", and shows the results on the experiment's ",[358,359,360],"strong",{},"Experiment"," tab, so an experiment does not need Google Analytics. Your Analytics events stay as they are: keep logging them for everything else you use them for.",[346,363,364,365,369,370,373],{},"If your app also reads Remote Config parameters that are not part of an experiment, ",[366,367,368],"a",{"href":333},"Migrating from Firebase Remote Config"," covers them, along with setting up the project and the SDK. The ",[366,371,372],{"href":51},"Experiments guide"," walks through each step of an experiment in the dashboard.",[375,376,378],"h2",{"id":377},"before-you-start","Before you start",[380,381,382,389,422],"ol",{},[383,384,385,388],"li",{},[358,386,387],{},"Let the running experiments finish in Firebase."," ConfigDirector assigns variations in its own way, so an experiment moved halfway would put some users in a different variation from the one they had, and its counts would start from zero. Start new experiments in ConfigDirector, and leave each running one in Firebase until you have read its result.",[383,390,391,394,395,399,400,403,404,406,407,409,410,413,414,416,417,421],{},[358,392,393],{},"Decide how your app identifies users."," Firebase assigns and counts each installation of your app. ConfigDirector assigns and counts each ",[366,396,398],{"href":397},"\u002Fgetting-started\u002Fcore-concepts#contexts","context"," with an ",[353,401,402],{},"id",", so a signed-in user keeps the same variation on every device. A context without an ",[353,405,402],{}," is still served a variation in Flutter and in the browser, but no experiment counts it. To count signed-out users, give each installation an ",[353,408,402],{}," of your own, such as a random identifier you store on the device the first time the app starts, and leave ",[353,411,412],{},"anonymous"," off. ConfigDirector does not merge that context with the one a user gets by signing in: the context with their user ",[353,415,402],{}," is another context, which can be served another variation and is counted on its own. ",[366,418,420],{"href":419},"\u002Fgetting-started\u002Fexperiments#give-every-context-a-stable-identifier","Give every context a stable identifier"," has the details.",[383,423,424,427,428,432,433,435,436,438,439,443],{},[358,425,426],{},"List the Analytics events your experiments measure."," Each goal and metric of an experiment you plan to move becomes a ",[366,429,431],{"href":430},"\u002Fgetting-started\u002Fexperiments#define-a-metric","metric"," on an event your app reports with ",[353,434,355],{},". Only those events need ",[353,437,355],{},": ConfigDirector stores up to 100,000 tracked events per environment per day, so report the outcomes your experiments compare rather than every Analytics event. See ",[366,440,442],{"href":441},"\u002Fmonitoring\u002Factivity#limits","Limits",".",[375,445,447],{"id":446},"how-the-concepts-map","How the concepts map",[449,450,48],"h3",{"id":451},"experiments",[453,454,455,468],"table",{},[456,457,458],"thead",{},[459,460,461,465],"tr",{},[462,463,464],"th",{},"Firebase A\u002FB Testing",[462,466,467],{},"ConfigDirector",[469,470,471,487,499,519,530,542,554,562,570,581,596,615,626,641,649,657,673,683],"tbody",{},[459,472,473,477],{},[474,475,476],"td",{},"Experiment on Remote Config parameters",[474,478,479,480,483,484,486],{},"An ",[366,481,482],{"href":51},"experiment",": a config with the ",[353,485,360],{}," role, of type String, Integer, Float or Boolean. Keep the parameter's key as the config's key, so only the SDK call changes in your code.",[459,488,489,492],{},[474,490,491],{},"A variant that changes several parameters",[474,493,494,495,498],{},"One experiment whose variations stand for the whole change. Your code turns each variation into the values it used to read from the parameters, such as a String variation ",[353,496,497],{},"compact"," that selects a layout, its spacing and its copy.",[459,500,501,504],{},[474,502,503],{},"Baseline",[474,505,506,507,510,511,514,515,443],{},"The ",[358,508,509],{},"control",": the variation the default targeting rule serves when the run starts, so pick the baseline's variation as the ",[358,512,513],{},"Default variation"," when you create the experiment. See ",[366,516,518],{"href":517},"\u002Fgetting-started\u002Fexperiments\u002Fhow-results-are-computed#the-control","The control",[459,520,521,524],{},[474,522,523],{},"Variants",[474,525,526,529],{},[358,527,528],{},"Variations",": named values. Name each one, since the dashboard, the audit log, the CLI and OpenFeature refer to a variation by its name.",[459,531,532,535],{},[474,533,534],{},"Variant weights",[474,536,537,538,541],{},"The shares of a ",[358,539,540],{},"percentage rollout",". Saving different shares while the experiment runs ends the run and starts a new one, whose counts start from zero.",[459,543,544,547],{},[474,545,546],{},"Targeting conditions",[474,548,549,550,553],{},"A conditional rule with the percentage rollout inside it, using the conditions or a ",[366,551,552],{"href":63},"segment"," that matches the same users. Contexts the rule does not match get the rules below it and are not part of the experiment.",[459,555,556,559],{},[474,557,558],{},"Percentage of target users",[474,560,561],{},"Shares that add up to less than 100%, such as 5% for each of two variations. The other contexts go on to the rules below the percentage rollout, are served what those rules serve, and are not part of the experiment. The targeting rules editor warns you when an experiment's shares add up to less than 100%.",[459,563,564,567],{},[474,565,566],{},"Activation event",[474,568,569],{},"A context joins the experiment when your code reads the experiment and the percentage rollout serves it a variation, and only events it reports from then on count. Read the experiment where the change shows, not when the app starts, so only the users who reach that point count.",[459,571,572,575],{},[474,573,574],{},"Goal metric",[474,576,506,577,580],{},[358,578,579],{},"primary metric",", with the metric kind it decides on.",[459,582,583,586],{},[474,584,585],{},"Other metrics",[474,587,588,591,592,595],{},[358,589,590],{},"Secondary metrics",", which support the decision, and ",[358,593,594],{},"guardrails",", metrics that must not get worse. An experiment can have up to 10 metrics, and a project up to 50.",[459,597,598,601],{},[474,599,600],{},"Starting the experiment",[474,602,603,604,607,608,611,612,443],{},"Saving the targeting rules with the percentage rollout starts a ",[358,605,606],{},"run"," in that environment. Each environment runs its own split, so try the experiment in ",[353,609,610],{},"Test"," before ",[353,613,614],{},"Production",[459,616,617,620],{},[474,618,619],{},"Stopping the experiment",[474,621,622,625],{},[358,623,624],{},"Stop run…",": every percentage rollout is removed, the default targeting rule value keeps serving, and the run keeps a note on why you stopped it.",[459,627,628,631],{},[474,629,630],{},"Rolling out the leader",[474,632,633,636,637,443],{},[358,634,635],{},"Ship…",": in one save, the variation you pick becomes the default targeting rule value and every percentage rollout is removed. It applies to the whole environment, not only to the users the experiment targeted; a conditional rule that serves a single value stays, so the contexts it matches keep that value. See ",[366,638,640],{"href":639},"\u002Fgetting-started\u002Fexperiments#ship-a-variation","Ship a variation",[459,642,643,646],{},[474,644,645],{},"A time limit on experiments",[474,647,648],{},"None: a run lasts until you ship a variation, stop it or change the split.",[459,650,651,654],{},[474,652,653],{},"A limit on running experiments per project",[474,655,656],{},"No limit of its own. An experiment is a config, so it counts toward the configs limit: 100 on Free, unlimited on Pay as you go. Each environment has one run of an experiment at a time.",[459,658,659,662],{},[474,660,661],{},"Test devices",[474,663,664,665,668,669,672],{},"A conditional rule above the percentage rollout that serves a single variation to your own test users, with ",[353,666,667],{},"identifier is one of",". Contexts served by a conditional rule's single value are not part of the experiment, so add the rule before the run starts and testing does not change the results. The ",[366,670,671],{"href":44},"targeting rules tester"," shows which variation any context gets.",[459,674,675,678],{},[474,676,677],{},"Google Analytics, required for A\u002FB Testing",[474,679,680,681,443],{},"Not needed. The results come from the events your app reports with ",[353,682,355],{},[459,684,685,688],{},[474,686,687],{},"Experiment membership in Analytics and BigQuery",[474,689,690,691,443],{},"Optional: report each exposure to your analytics tool from the SDK's evaluation event. See ",[366,692,694],{"href":693},"#send-exposures-to-your-analytics-tool","Send exposures to your analytics tool",[449,696,698],{"id":697},"metrics","Metrics",[346,700,701,702,704,705,707,708,711,712,715],{},"A ConfigDirector metric is an event name your app reports with ",[353,703,355],{},", defined once per project on the ",[358,706,698],{}," page with a direction, ",[358,709,710],{},"Higher is better"," or ",[358,713,714],{},"Lower is better",". Each metric kind turns a variation's events into one number:",[453,717,718,728],{},[456,719,720],{},[459,721,722,725],{},[462,723,724],{},"Firebase goal or metric",[462,726,727],{},"ConfigDirector metric",[469,729,730,742,753,771,786,797],{},[459,731,732,735],{},[474,733,734],{},"An Analytics event, such as a completed checkout",[474,736,737,738,741],{},"A metric on the same event name, with ",[358,739,740],{},"Conversion",": the share of the variation's contexts that reported the event at least once.",[459,743,744,747],{},[474,745,746],{},"How often users trigger an event",[474,748,749,752],{},[358,750,751],{},"Count per context",": how many times each context reported the event, averaged over the variation's contexts.",[459,754,755,758],{},[474,756,757],{},"Purchase revenue, or other revenue",[474,759,760,763,764,767,768,770],{},[358,761,762],{},"Value per context",": the sum of the event's ",[353,765,766],{},"value"," per context, averaged over the variation's contexts. Track the event with the amount as its ",[353,769,766],{},". The value is a plain number, so report every amount in the same currency.",[459,772,773,776],{},[474,774,775],{},"An event you want less of",[474,777,778,779,781,782,785],{},"Any of the kinds, on a metric with ",[358,780,714],{},", such as ",[353,783,784],{},"support_ticket_opened",". The results read fewer tickets than the control as better.",[459,787,788,791],{},[474,789,790],{},"Retention",[474,792,793,794,796],{},"Not available. ",[358,795,751],{}," on an event your app tracks each time it opens compares how many times each variation's users open the app during the run.",[459,798,799,802],{},[474,800,801],{},"Crash-free users",[474,803,804,805,807],{},"Not available: ConfigDirector does not receive crash reports. A guardrail on an event your app tracks when it shows an error, with ",[358,806,714],{},", catches the failures your code sees.",[346,809,810,814,815,818,819,443],{},[366,811,813],{"href":812},"\u002Fgetting-started\u002Fexperiments\u002Fhow-results-are-computed#metrics","How ConfigDirector computes results"," explains each kind, and the Experiments guide shows how to ",[366,816,817],{"href":430},"define a metric"," and ",[366,820,822],{"href":821},"\u002Fgetting-started\u002Fexperiments#attach-metrics-and-write-the-hypothesis","attach it with a role",[375,824,826],{"id":825},"replace-the-sdk-calls","Replace the SDK calls",[346,828,829,830,833,834,836],{},"Set the client up as ",[366,831,368],{"href":832},"\u002Fmigration-guides\u002Ffirebase-remote-config#switch-the-sdks"," shows, with the ",[353,835,402],{}," you chose for each context. Then two calls change: reading the experiment, and reporting the outcome.",[449,838,840],{"id":839},"read-the-experiment","Read the experiment",[346,842,843,844,818,847,851],{},"Read the experiment where Remote Config's value used to be read, with the control's value as the in-code default value. Your app gets the in-code default value when it cannot get a value from ConfigDirector. The ",[366,845,139],{"href":846},"\u002Fsdks\u002Fmobile\u002Fflutter#retrieve-config-values",[366,848,850],{"href":849},"\u002Fsdks\u002Fbrowser\u002Fjavascript#retrieve-config-values","JavaScript"," SDK pages describe each way to read a config:",[853,854,855,910],"code-group",{},[856,857,862],"pre",{"className":858,"code":859,"filename":139,"language":860,"meta":861,"style":861},"language-dart shiki shiki-themes github-light-high-contrast github-light github-dark","\u002F\u002F Before: remoteConfig.getString('checkout_cta_copy')\nfinal buttonLabel = client.getValue('checkout_cta_copy', 'Start free trial');\n","dart","",[353,863,864,873],{"__ignoreMap":861},[865,866,869],"span",{"class":867,"line":868},"line",1,[865,870,872],{"class":871},"sM45V","\u002F\u002F Before: remoteConfig.getString('checkout_cta_copy')\n",[865,874,876,880,884,887,890,894,897,901,904,907],{"class":867,"line":875},2,[865,877,879],{"class":878},"sLDKn","final",[865,881,883],{"class":882},"sbRM4"," buttonLabel ",[865,885,886],{"class":878},"=",[865,888,889],{"class":882}," client.",[865,891,893],{"class":892},"sEIia","getValue",[865,895,896],{"class":882},"(",[865,898,900],{"class":899},"suztq","'checkout_cta_copy'",[865,902,903],{"class":882},", ",[865,905,906],{"class":899},"'Start free trial'",[865,908,909],{"class":882},");\n",[856,911,915],{"className":912,"code":913,"filename":208,"language":914,"meta":861,"style":861},"language-ts shiki shiki-themes github-light-high-contrast github-light github-dark","\u002F\u002F Before: getValue(remoteConfig, \"checkout_cta_copy\").asString()\nconst buttonLabel = client.getValue(\"checkout_cta_copy\", \"Start free trial\");\n","ts",[353,916,917,922],{"__ignoreMap":861},[865,918,919],{"class":867,"line":868},[865,920,921],{"class":871},"\u002F\u002F Before: getValue(remoteConfig, \"checkout_cta_copy\").asString()\n",[865,923,924,927,931,934,936,938,940,943,945,948],{"class":867,"line":875},[865,925,926],{"class":878},"const",[865,928,930],{"class":929},"sxMNI"," buttonLabel",[865,932,933],{"class":878}," =",[865,935,889],{"class":882},[865,937,893],{"class":892},[865,939,896],{"class":882},[865,941,942],{"class":899},"\"checkout_cta_copy\"",[865,944,903],{"class":882},[865,946,947],{"class":899},"\"Start free trial\"",[865,949,909],{"class":882},[346,951,952],{},"A context the percentage rollout serves is exposed once per variation, however often it reads the experiment.",[449,954,956],{"id":955},"track-the-outcome","Track the outcome",[346,958,959,960,962,963,965],{},"Keep your Analytics call, and report the same outcome to ConfigDirector with ",[353,961,355],{},", under the same event name, so the metric has the name your team already knows. Give the event a ",[353,964,766],{}," when it carries an amount:",[853,967,968,1077],{},[856,969,971],{"className":858,"code":970,"filename":139,"language":860,"meta":861,"style":861},"await FirebaseAnalytics.instance.logEvent(\n  name: 'checkout_completed',\n  parameters: {'value': 49.0, 'currency': 'USD'},\n);\n\nclient.track('checkout_completed', const TrackOptions(value: 49.0));\n",[353,972,973,990,1004,1036,1041,1047],{"__ignoreMap":861},[865,974,975,978,981,984,987],{"class":867,"line":868},[865,976,977],{"class":878},"await",[865,979,980],{"class":929}," FirebaseAnalytics",[865,982,983],{"class":882},".instance.",[865,985,986],{"class":892},"logEvent",[865,988,989],{"class":882},"(\n",[865,991,992,995,998,1001],{"class":867,"line":875},[865,993,994],{"class":882},"  name",[865,996,997],{"class":878},":",[865,999,1000],{"class":899}," 'checkout_completed'",[865,1002,1003],{"class":882},",\n",[865,1005,1007,1010,1012,1015,1018,1020,1023,1025,1028,1030,1033],{"class":867,"line":1006},3,[865,1008,1009],{"class":882},"  parameters",[865,1011,997],{"class":878},[865,1013,1014],{"class":882}," {",[865,1016,1017],{"class":899},"'value'",[865,1019,997],{"class":878},[865,1021,1022],{"class":929}," 49.0",[865,1024,903],{"class":882},[865,1026,1027],{"class":899},"'currency'",[865,1029,997],{"class":878},[865,1031,1032],{"class":899}," 'USD'",[865,1034,1035],{"class":882},"},\n",[865,1037,1039],{"class":867,"line":1038},4,[865,1040,909],{"class":882},[865,1042,1044],{"class":867,"line":1043},5,[865,1045,1046],{"emptyLinePlaceholder":9},"\n",[865,1048,1050,1053,1055,1057,1060,1062,1064,1067,1070,1072,1074],{"class":867,"line":1049},6,[865,1051,1052],{"class":882},"client.",[865,1054,355],{"class":892},[865,1056,896],{"class":882},[865,1058,1059],{"class":899},"'checkout_completed'",[865,1061,903],{"class":882},[865,1063,926],{"class":878},[865,1065,1066],{"class":929}," TrackOptions",[865,1068,1069],{"class":882},"(value",[865,1071,997],{"class":878},[865,1073,1022],{"class":929},[865,1075,1076],{"class":882},"));\n",[856,1078,1080],{"className":912,"code":1079,"filename":208,"language":914,"meta":861,"style":861},"logEvent(analytics, \"checkout_completed\", { value: 49, currency: \"USD\" });\n\nclient.track(\"checkout_completed\", { value: 49 });\n",[353,1081,1082,1107,1111],{"__ignoreMap":861},[865,1083,1084,1086,1089,1092,1095,1098,1101,1104],{"class":867,"line":868},[865,1085,986],{"class":892},[865,1087,1088],{"class":882},"(analytics, ",[865,1090,1091],{"class":899},"\"checkout_completed\"",[865,1093,1094],{"class":882},", { value: ",[865,1096,1097],{"class":929},"49",[865,1099,1100],{"class":882},", currency: ",[865,1102,1103],{"class":899},"\"USD\"",[865,1105,1106],{"class":882}," });\n",[865,1108,1109],{"class":867,"line":875},[865,1110,1046],{"emptyLinePlaceholder":9},[865,1112,1113,1115,1117,1119,1121,1123,1125],{"class":867,"line":1006},[865,1114,1052],{"class":882},[865,1116,355],{"class":892},[865,1118,896],{"class":882},[865,1120,1091],{"class":899},[865,1122,1094],{"class":882},[865,1124,1097],{"class":929},[865,1126,1106],{"class":882},[346,1128,1129,1131,1132,443],{},[353,1130,355],{}," reports the event for the client's current context, so the event counts for the variation that context was served. ConfigDirector does not read your Analytics events: an outcome counts only when your app reports it with ",[353,1133,355],{},[346,1135,506,1136,818,1139,1142,1143,1145,1146,903,1149,903,1152,818,1155,1158,1159,443],{},[366,1137,139],{"href":1138},"\u002Fsdks\u002Fmobile\u002Fflutter#track-events",[366,1140,850],{"href":1141},"\u002Fsdks\u002Fbrowser\u002Fjavascript#track-events"," SDK pages describe ",[353,1144,355],{},", its options and its limits. The ",[366,1147,119],{"href":1148},"\u002Fsdks\u002Fbrowser\u002Freact#track",[366,1150,124],{"href":1151},"\u002Fsdks\u002Fbrowser\u002Fvue#track",[366,1153,160],{"href":1154},"\u002Fsdks\u002Fmeta\u002Fnextjs#track",[366,1156,165],{"href":1157},"\u002Fsdks\u002Fmeta\u002Fnuxt#track"," SDKs offer it too, and every other SDK is listed under ",[366,1160,1162],{"href":1161},"\u002Fmonitoring\u002Factivity#tracked-events","Tracked events",[449,1164,694],{"id":1165},"send-exposures-to-your-analytics-tool",[346,1167,1168,1169,1172,1173,1176,1177,1180,1181,1185,1186,1189,1190,1193],{},"If your team also wants to see experiments next to its other events in Google Analytics, as Firebase's experiment user properties allowed, report each exposure from the SDK's evaluation event: ",[353,1170,1171],{},"onConfigEvaluated"," in ",[366,1174,139],{"href":1175},"\u002Fsdks\u002Fmobile\u002Fflutter#events"," and the ",[353,1178,1179],{},"configEvaluated"," hook in the ",[366,1182,1184],{"href":1183},"\u002Fsdks\u002Fbrowser\u002Fjavascript#hooks","browser",". The ",[366,1187,1188],{"href":302},"Google Analytics 4 page"," has a handler to copy for the browser, and ",[366,1191,694],{"href":1192},"\u002Fgetting-started\u002Fexperiments#send-exposures-to-your-analytics-tool"," lists the hook for every SDK.",[375,1195,1197],{"id":1196},"read-the-results","Read the results",[346,1199,506,1200,1203,1204,1206],{},[358,1201,1202],{},"Results"," panel on the experiment's ",[358,1205,360],{}," tab compares each variation with the control on every metric the experiment is measured on. This is how it relates to Firebase's results page:",[453,1208,1209,1219],{},[456,1210,1211],{},[459,1212,1213,1216],{},[462,1214,1215],{},"Firebase results page",[462,1217,1218],{},"ConfigDirector Results panel",[469,1220,1221,1230,1248,1259,1270,1285,1311,1319],{},[459,1222,1223,1225],{},[474,1224,503],{},[474,1226,506,1227,1229],{},[358,1228,509],{},", first in each table.",[459,1231,1232,1235],{},[474,1233,1234],{},"Observed totals and rates",[474,1236,1237,1238,1241,1242,1245,1246,443],{},"Each variation's value, such as ",[353,1239,1240],{},"14.3%"," with ",[353,1243,1244],{},"672 of 4,702"," beneath it for ",[358,1247,740],{},[459,1249,1250,1253],{},[474,1251,1252],{},"Improvement over the baseline",[474,1254,1255,1258],{},[358,1256,1257],{},"Lift",": how much higher or lower the variation's value is than the control's.",[459,1260,1261,1264],{},[474,1262,1263],{},"Confidence interval",[474,1265,1266,1269],{},[358,1267,1268],{},"Likely range",": where the real lift is likely to be. A range that spans zero means the variation could still turn out better or worse than the control.",[459,1271,1272,1275],{},[474,1273,1274],{},"p-value",[474,1276,1277,1280,1281,1284],{},[358,1278,1279],{},"Chance to beat control",": the chance that the variation is better than the control, in the metric's direction. It reads directly as that chance, which a p-value does not. With three or more variations, ",[358,1282,1283],{},"Chance to be best"," gives each variation's chance of being the best of them all.",[459,1286,1287,1290],{},[474,1288,1289],{},"Leader",[474,1291,1292,1293,1296,1297,903,1300,711,1303,1306,1307,1310],{},"A ",[358,1294,1295],{},"verdict"," for each variation on each metric kind: ",[358,1298,1299],{},"Likely better",[358,1301,1302],{},"Likely worse",[358,1304,1305],{},"No clear difference",". The verdict on the primary metric's kind is the one the ",[358,1308,1309],{},"Ship"," dialog shows. Nothing ships or stops on its own: your team decides.",[459,1312,1313,1316],{},[474,1314,1315],{},"Results updated once a day",[474,1317,1318],{},"Results up to 25 minutes behind what your app reports.",[459,1320,1321,1324],{},[474,1322,1323],{},"Cumulative chart",[474,1325,1326],{},"A daily chart of each variation's value for the run so far, with a point for each day in UTC, ending with the current numbers.",[346,1328,1329,1330,1333,1334,1338],{},"The panel also warns you when a guardrail is likely worse for a variation, and the ",[358,1331,1332],{},"Exposures"," card above it checks the split against the shares you configured: when they ",[366,1335,1337],{"href":1336},"\u002Fgetting-started\u002Fexperiments#the-mismatch-warning","do not match",", the run's results are not reliable. Until every variation has enough exposures, the panel shows each variation's value and says the run needs more exposures before the variations can be compared.",[346,1340,1341,1342,1344,1345,1348],{},"ConfigDirector computes these numbers with a Bayesian method, which ",[366,1343,813],{"href":58}," explains in full, along with what the method does not do. ",[366,1346,1197],{"href":1347},"\u002Fgetting-started\u002Fexperiments#read-the-results"," in the Experiments guide walks through the panel with screenshots.",[346,1350,1351,1352,1356],{},"Experiments, metrics and results are available on Free and Pay as you go, and tracked events are not billed. ",[366,1353,1355],{"href":1354},"\u002Fgetting-started\u002Fpricing-and-billing#billing-metrics","Pricing and billing"," lists what is.",[375,1358,1360],{"id":1359},"checklist","Checklist",[346,1362,1363],{},"For each experiment you move:",[1365,1366,1369,1381,1390,1399,1410,1419,1431,1442,1457,1465,1471,1477,1488],"ul",{"className":1367},[1368],"contains-task-list",[383,1370,1373,1377,1378,1380],{"className":1371},[1372],"task-list-item",[1374,1375],"input",{"disabled":9,"type":1376},"checkbox"," The experiment exists in ConfigDirector with the parameter's key and a named variation for each Firebase variant, with the baseline's variation as the ",[358,1379,513],{},", so that it is the control.",[383,1382,1384,1386,1387,1389],{"className":1383},[1372],[1374,1385],{"disabled":9,"type":1376}," Each Analytics event the experiment measured has a metric on the ",[358,1388,698],{}," page, with its direction and the metric kinds you want.",[383,1391,1393,1395,1396,1398],{"className":1392},[1372],[1374,1394],{"disabled":9,"type":1376}," The metrics are attached to the experiment, with the goal metric as the ",[358,1397,579],{}," and the kind it decides on, and the hypothesis is written, before the run starts.",[383,1400,1402,1404,1405,1407,1408,443],{"className":1401},[1372],[1374,1403],{"disabled":9,"type":1376}," Your app gives each context the ",[353,1406,402],{}," you chose, signed-out users included, without ",[353,1409,412],{},[383,1411,1413,1415,1416,1418],{"className":1412},[1372],[1374,1414],{"disabled":9,"type":1376}," Your app reads the experiment with the SDK's ",[353,1417,893],{}," where the change shows, with the control's value as the in-code default value.",[383,1420,1422,1424,1425,1427,1428,1430],{"className":1421},[1372],[1374,1423],{"disabled":9,"type":1376}," Your app calls ",[353,1426,355],{}," for each measured event, beside its Analytics call, with the amount as the ",[353,1429,766],{}," for revenue.",[383,1432,1434,1436,1437,1441],{"className":1433},[1372],[1374,1435],{"disabled":9,"type":1376}," The release with these calls reaches your users before you start the run. The ",[366,1438,1440],{"href":1439},"\u002Fgetting-started\u002Fexperiments#sdk-versions-that-report-exposures","SDK versions that report exposures"," are 1.8.0 for Flutter and 1.11.0 for JavaScript, or later.",[383,1443,1445,1447,1448,1450,1451,1453,1454,1456],{"className":1444},[1372],[1374,1446],{"disabled":9,"type":1376}," In ",[353,1449,610],{},", the split is saved, the ",[358,1452,1332],{}," card counts contexts, and the ",[358,1455,1202],{}," panel shows each metric receiving events.",[383,1458,1460,1447,1462,1464],{"className":1459},[1372],[1374,1461],{"disabled":9,"type":1376},[353,1463,614],{},", the split is saved with the shares you want, inside a conditional rule if the experiment targets part of your users.",[383,1466,1468,1470],{"className":1467},[1372],[1374,1469],{"disabled":9,"type":1376}," You decided before the run how long it will go, and you read the primary metric's verdict then, checking the guardrails and the mismatch warning.",[383,1472,1474,1476],{"className":1473},[1372],[1374,1475],{"disabled":9,"type":1376}," You shipped a variation or stopped the run, with a note, in each environment that ran it.",[383,1478,1480,1482,1483,1487],{"className":1479},[1372],[1374,1481],{"disabled":9,"type":1376}," Once the experiment's config has ",[366,1484,1486],{"href":1485},"\u002Fgetting-started\u002Fexperiments#remove-the-config-once-it-has-settled","settled",", its value is in your code and the config is archived.",[383,1489,1491,1493],{"className":1490},[1372],[1374,1492],{"disabled":9,"type":1376}," Once the app versions that read the Firebase parameter have faded from your user base, the parameter is removed from the Remote Config template.",[1495,1496,1497],"style",{},"html pre.shiki code .sM45V, html code.shiki .sM45V{--shiki-light:#66707B;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sLDKn, html code.shiki .sLDKn{--shiki-light:#A0111F;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbRM4, html code.shiki .sbRM4{--shiki-light:#0E1116;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sEIia, html code.shiki .sEIia{--shiki-light:#622CBC;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .suztq, html code.shiki .suztq{--shiki-light:#032563;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxMNI, html code.shiki .sxMNI{--shiki-light:#023B95;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":861,"searchDepth":875,"depth":875,"links":1499},[1500,1501,1505,1510,1511],{"id":377,"depth":875,"text":378},{"id":446,"depth":875,"text":447,"children":1502},[1503,1504],{"id":451,"depth":1006,"text":48},{"id":697,"depth":1006,"text":698},{"id":825,"depth":875,"text":826,"children":1506},[1507,1508,1509],{"id":839,"depth":1006,"text":840},{"id":955,"depth":1006,"text":956},{"id":1165,"depth":1006,"text":694},{"id":1196,"depth":875,"text":1197},{"id":1359,"depth":875,"text":1360},"Move your A\u002FB tests from Firebase A\u002FB Testing to ConfigDirector experiments. How experiments, variants, goal metrics and the results page map, the Flutter and web code that replaces the Remote Config and Analytics calls, and a checklist.","md",null,{"modifiedAt":1516},"2026-11-11T18:59:57.000Z",{"title":336,"icon":330},{"title":1519,"description":1520},"Migrating from Firebase A\u002FB Testing to ConfigDirector","A step by step guide to moving A\u002FB tests from Firebase A\u002FB Testing and Remote Config to ConfigDirector experiments. Concept mapping, metrics on tracked events instead of Google Analytics, the Flutter and web SDK calls that replace getValue and logEvent, reading the Results panel, and a migration checklist.","5LmxM9lBPXiEbn0z9K40czZsBlt1iu0x96l_ODf3sUM",[1523,1514],{"title":332,"path":333,"stem":334,"description":1524,"icon":330,"children":-1},"Move your remote configuration from Firebase Remote Config to ConfigDirector. How parameters, conditions, in-app defaults, and fetch and activate map, how to recreate the parameters, and how to switch the Flutter, Android, iOS, and web SDKs over.",1794464795885]