Docs · Migration · Flagsmith

Migrate from Flagsmith, in both directions.

Straight up: wagle has no one-click importer. What it has is a small admin surface that is plain JSON over HTTP, and one BatchWrite call that applies your whole config atomically - so a Flagsmith migration is a short script you control, not a wizard you trust. This runbook covers the export, the transform, the one-call import, verification, and the way back out. Weighing the products themselves is the comparison page's job, including when Flagsmith is the better pick.

01 · The model

What maps to what

Most of Flagsmith's model has a direct wagle counterpart; the honest exceptions are identity-shaped data (wagle stores none) and nested segment rules (wagle's are flat on purpose). Read this table once and the rest of the runbook is mechanical.

FlagsmithwagleNotes
ProjectProjectIdentified by an opaque UUID, never a name - discover it via ListProjects and pass it as project on every call.
EnvironmentenvironmentA free-string namespace created on first write. A flag's identity is project-wide; its serving state lives per env in environments[env].
Feature (flag + remote-config value)Flag with a value_typeA feature flag is the BOOL case of a typed config entry (BOOL / NUMBER / STRING / JSON). Variation values are always strings on the wire; booleans are "true"/"false".
Enabled stateenvironments[env].enabledDisabled serves offVariationKey; enabled with no matching rule serves defaultVariationKey.
Multivariate options + % splitRollout ruleWeights are basis points of a 10000-bucket space and must sum to exactly 10000 (a Flagsmith 30% becomes 3000).
SegmentSegmentFlat, ANDed conditions; any operator except IN_SEGMENT. One level deep, permanently - no nested segments. Scoped per (project, environment).
Segment overrideFlag rule with IN_SEGMENTA rule {"op": "OPERATOR_IN_SEGMENT", "values": ["<segment-key>"]} serving a variation. First matching rule wins.
Identity overrideAn explicit ruleNo per-identity store exists. Write it as a visible rule, e.g. EQ on an attribute your app sends in the eval context.
Identities & traitsContext attributeswagle stores nothing about your users: attributes ride each evaluate call and are gone after it. Nothing identity-shaped migrates, by design.
  • Keys are byte-sensitive: no case normalization, Checkout and checkout are two flags. Copy Flagsmith feature names verbatim.
  • new is a reserved flag key (the console's create route). A Flagsmith feature named new must be renamed on the way in.
02 · Export

Get your config out of Flagsmith

Two routes, per Flagsmith's bulk import/export docs: the UI export (flags and values, but no segments) or the environment document their local-evaluation SDKs consume, which carries feature states and segment rules in one JSON file. Prefer the environment document if you use segments.

Shell · Export from Flagsmith
# Route A - the UI bulk export: Project Settings -> Export tab, pick the
# source environment, download JSON. Covers flags, values and multivariate
# weights. Segments are NOT included, and exports expire after two weeks
# (both per Flagsmith's own docs), so keep a local copy.

# Route B - the environment document: the JSON Flagsmith's local-evaluation
# SDKs consume. One environment's feature states AND segment rules, no
# identities/traits. Their CLI outputs it (authenticate with a server-side
# "ser." environment key; see the flagsmith-cli README):
npx flagsmith-cli environment document > flagsmith-production.json
  • Both routes are per-environment. Export each Flagsmith environment you keep; each becomes one wagle BatchWrite later.
  • Neither route carries identities or traits - see the FAQ for why that data has no wagle destination anyway.
03 · Transform

Reshape features into write ops

The target shape is a list of WriteOps: each op one flag or one segment, exactly as the admin API takes them. The mechanical 80% (on/off features, single-value config) is a loop; the script below does it and prints the 20% that needs your judgment instead of guessing at it.

Node · flagsmith doc -> ops.json
// migrate.mjs - Flagsmith environment document -> wagle BatchWrite ops.
// Converts the mechanical part (on/off features, single-value config) and
// PRINTS what needs your judgment (multivariate splits, segment rules)
// instead of guessing. It is your script, not a wagle feature - adapt it.
import { readFileSync, writeFileSync } from "node:fs";

const ENV = "production"; // the wagle environment this batch lands in
const doc = JSON.parse(readFileSync(process.argv[2], "utf8"));
const ops = [];
const review = [];

for (const state of doc.feature_states ?? []) {
  const key = state.feature.name;
  const value = state.feature_state_value;
  if ((state.multivariate_feature_state_values ?? []).length > 0) {
    review.push(`${key}: multivariate split -> write a rollout rule by hand`);
    continue;
  }
  const serving = { enabled: state.enabled, defaultVariationKey: "on", offVariationKey: "off" };
  const flag =
    value == null
      ? {
          // pure on/off feature -> a BOOL flag
          key,
          valueType: "VALUE_TYPE_BOOL",
          variations: [
            { key: "on", value: "true" },
            { key: "off", value: "false" },
          ],
          environments: { [ENV]: serving },
        }
      : {
          // remote-config value -> a typed flag; wagle values are ALWAYS strings
          key,
          valueType: typeof value === "number" ? "VALUE_TYPE_NUMBER" : "VALUE_TYPE_STRING",
          variations: [
            { key: "on", value: String(value) },
            { key: "off", value: "" },
          ],
          environments: { [ENV]: serving },
        };
  ops.push({ flag });
}

for (const segment of doc.project?.segments ?? []) {
  review.push(`segment ${segment.name}: translate rules by hand (flat ANDed conditions)`);
}

writeFileSync("ops.json", JSON.stringify(ops, null, 2));
console.log(`${ops.length} ops -> ops.json`);
if (review.length) console.log(`needs review:\n- ${review.join("\n- ")}`);
  • Multivariate splits: Flagsmith allocates percentages out of 100, wagle rollout weights are basis points summing to exactly 10000. Multiply by 100 and make the weights add up, or the write is rejected.
  • Flagsmith regex conditions must fit wagle's portable subset (the intersection of RE2 and JavaScript regex) - the price of byte-identical evaluation across SDKs. Rewrite what doesn't fit.
  • One op per key per batch: dedupe before writing, later ops replace earlier ones.
04 · Import

One atomic BatchWrite per environment

BatchWrite applies every op all-or-nothing under a single config version bump: a bad op rejects the whole batch and nothing lands, and no SDK ever syncs a half-imported config. That atomicity is the reason this runbook is safe to run against a live project.

curl · BatchWrite
# One BatchWrite applies every op all-or-nothing under a SINGLE config
# version bump: an invalid op rejects the whole batch and nothing is written,
# and no SDK ever observes a half-imported config. Run it once per target
# environment. Example ops: a segment, then a flag that references it.
curl -sS $WAGLE_URL/api.admin.v1.AdminService/BatchWrite \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WAGLE_TOKEN" \
  -d '{
    "project": "baseline",
    "environment": "production",
    "ops": [
      {"segment": {"key": "beta-testers", "conditions": [
        {"attribute": "plan", "op": "OPERATOR_IN", "values": ["pro", "enterprise"]}
      ]}},
      {"flag": {
        "key": "new-checkout",
        "valueType": "VALUE_TYPE_BOOL",
        "variations": [{"key": "on", "value": "true"}, {"key": "off", "value": "false"}],
        "environments": {"production": {
          "enabled": true,
          "defaultVariationKey": "off",
          "offVariationKey": "off",
          "rules": [
            {"conditions": [{"op": "OPERATOR_IN_SEGMENT", "values": ["beta-testers"]}],
             "variationKey": "on"},
            {"conditions": [],
             "rollout": {"entries": [
               {"variationKey": "on", "weight": 2500},
               {"variationKey": "off", "weight": 7500}
             ]}}
          ]
        }}
      }}
    ]
  }'
# -> {"models":{...},"graphs":[...],"version":"1"}
  • The example project is the placeholder baseline; your real project is an opaque UUID - discover it via ListProjects.
  • Ops apply in order: write a segment before the flag that references it. (A dangling IN_SEGMENT reference is legal and evaluates false until the segment exists - but why make yourself debug that.)
  • The returned version is a uint64 serialized as a JSON string - that's protojson, not a bug.
05 · Verify

Prove the migration before you cut over

Evaluate the imported flags with the same identities and traits Flagsmith served and compare answers. Run both products in parallel for a while if you like: wagle SDKs evaluate locally against a synced snapshot, so the double-read costs your app nothing on the hot path.

curl · Evaluate + audit
# Spot-check evaluations against what Flagsmith served for the same
# identity + traits. Reasons tell you WHY a variation was served.
curl -sS $WAGLE_URL/api.eval.v1.EvaluationService/Evaluate \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WAGLE_TOKEN" \
  -d '{"project":"baseline","environment":"production","flagKey":"new-checkout",
       "context":{"entityId":"user-42","attributes":{"plan":"pro"}}}'
# -> {"variationKey":"on","value":"true","reason":"REASON_TARGET_MATCH",...}

# The import itself is auditable: it landed as ONE config version, attributed
# to you, diffable per flag.
curl -sS $WAGLE_URL/api.admin.v1.AdminService/GetHistory \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WAGLE_TOKEN" \
  -d '{"project":"baseline","environment":"production"}'
  • Percentage rollouts reshuffle membership once (different bucketing hash) - a 25% stays 25%, but not the same 25%. Cut over load-bearing rollouts at 0% or 100%.
Reference · No lock-in

Export from wagle back to Flagsmith

Leaving must be as easy as arriving, or the arriving was a trap. The entire config of a (project, environment) is two list calls returning plain JSON - the same public API you use every day, no export gate, no tier. Reshape it for Flagsmith's import and you're out.

curl · ListFlags + ListSegments
# The whole config of one (project, environment) is two list calls: flags
# (identity + per-env serving state + rules) and segments. That JSON is
# yours; there is nothing else to ask for and no export gate to unlock.
curl -sS $WAGLE_URL/api.admin.v1.AdminService/ListFlags \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WAGLE_TOKEN" \
  -d '{"project":"baseline","environment":"production"}'
# -> {"models":{"flags":[...]},"graphs":[...]}

curl -sS $WAGLE_URL/api.admin.v1.AdminService/ListSegments \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WAGLE_TOKEN" \
  -d '{"project":"baseline","environment":"production"}'
# -> {"models":{"segments":[...]},"graphs":[...]}

# Going back to Flagsmith is this page in reverse: reshape into their bulk
# import format (Project Settings -> Import tab) or replay their Admin API.
Reference · Agents

Or hand the migration to an agent

The admin surface is exposed to AI agents over MCP at /mcp (flag and segment CRUD included), so a capable agent can read your Flagsmith export and recreate it: connect a host with an agent credential (minted in Settings → Agents), then use the prompt below - it encodes this page's transform rules, so the agent applies the same mapping the script would, and tells you what it couldn't.

MCP · Host config (.mcp.json)
{
  "mcpServers": {
    "wagle-admin": {
      "type": "http",
      "url": "$WAGLE_URL/mcp",
      "headers": { "Authorization": "Bearer <refresh-credential>" }
    }
  }
}
MCP · The migration prompt
# Register the endpoint once (Claude Code shown; any MCP host works):
claude mcp add --transport http wagle-admin \
  $WAGLE_URL/mcp \
  --header "Authorization: Bearer <refresh-credential>"

# Then hand it your export and this page's rules in one ask:

> Here is a Flagsmith environment document (attached:
  flagsmith-production.json). Recreate it in my wagle project "baseline",
  environment "production":
  - segments first: flat ANDed conditions; skip anything with nested
    any/all rule trees and tell me what you skipped and why;
  - each feature becomes a flag: pure on/off -> a BOOL flag; a valued
    feature -> a typed flag whose "on" variation carries the value
    (values are strings);
  - multivariate splits -> a rollout rule, weights in basis points
    summing to exactly 10000;
  - keep every key byte-identical, except rename a feature named "new"
    (reserved key) and flag it to me.
  When done, list everything you created, then evaluate new-checkout
  for user-42 with plan=pro so I can compare against Flagsmith.
  • MCP tools write one entity per call, so an agent migration is N version bumps, not one atomic batch like BatchWrite. Fine for a dozen flags; script the batch for hundreds.
  • The bearer is the agent's long-lived, revocable refresh credential - revoke the agent when the migration is done if you minted one just for this.
Reference · FAQ

Migration questions, answered honestly

Does wagle have a built-in Flagsmith importer?

No. There is no one-click importer and this page does not pretend otherwise. What wagle has instead is a small admin surface that is plain JSON over HTTP, and one BatchWrite call that applies your whole exported config atomically under a single version bump - so the import is a short script, not a product feature.

Will my rollout percentages keep the same users after migrating?

No. Both products bucket deterministically, but with different hashes: wagle hashes "{project}:{environment}:{flagKey}:{entityId}" with xxHash64 modulo 10000. A 25% rollout stays a stable 25%, but which users fall inside it reshuffles once at migration. If a partial rollout is load-bearing, migrate that flag at 0% or 100%, or accept one reshuffle.

Do Flagsmith segments migrate automatically?

No - translate them by hand. A wagle segment is a flat list of ANDed conditions (any operator except IN_SEGMENT), one level deep permanently, scoped per (project, environment). Flagsmith's nested any/all rule trees and richer operators don't map mechanically; regex rules must also fit wagle's portable RE2-and-JS subset.

What happens to identities and traits?

They don't migrate, by design. wagle stores nothing about your users: the evaluation context (entity id + attributes) rides each call from your app and is never persisted. Flagsmith identity overrides become explicit targeting rules on attributes you send; server-stored traits become attributes your app supplies itself.

Can I get my flags back out of wagle later?

Yes. ListFlags and ListSegments return the entire (project, environment) config as JSON - identity, serving state, rules, everything. Leaving is the same runbook in reverse, and the export path is the same public API you use every day, not a gated feature.

Import in one call, leave in two.

Create a project, run the BatchWrite, and watch the whole import land as one auditable config version.