FlagDash SDK for Elixir & Erlang
Feature flags, remote config, AI configs, translations and experiments for the BEAM.
The client is a plain struct backed by a public ETS cache — no GenServer, no supervision tree to wire up, and safe to share across processes.
Installation
def deps do
[{:flagdash, "~> 0.1"}]
end
Quick start
{:ok, client} = FlagDash.Client.new(System.fetch_env!("FLAGDASH_SDK_KEY"))
if FlagDash.Client.flag(client, "checkout-v2", %{user_id: "alice"}, false) do
# new checkout
end
FlagDash.Client.close(client)
Hold the client in your application state — Application.put_env/3, a
:persistent_term, or your own supervision tree — and reuse it. Each new/2
creates its own ETS table.
API key tiers
The key decides which project and environment you read, and what you may reach.
There is no environment option anywhere in this SDK — the key carries it.
| Key | Prefix | Reaches |
|---|---|---|
| Client | pk_ | Flag values and configs. |
| Server | sk_ | The above, plus targeting rules, translations and experiments. |
Configuration
{:ok, client} =
FlagDash.Client.new(sdk_key,
base_url: "https://flagdash.io", # self-hosted? point it here
timeout: 5_000, # milliseconds
cache_ttl: 60_000, # milliseconds
region: "eu-west-1", # omit to auto-detect
req_options: [] # passed through to Req
)
new/2 returns {:error, :missing_sdk_key} for an empty key rather than
raising. Region is detected from FLY_REGION, AWS_REGION and friends when
omitted, so region-scoped targeting works with no wiring.
Feature flags
alias FlagDash.Client
context = %{user_id: "alice", country: "GB"}
Client.flag(client, "checkout-v2", context, false)
Client.all_flags(client, context)
# Why did it resolve that way?
Client.flag_detail(client, "checkout-v2", context)
# %{"value" => true, "reason" => "rule_match", "variation_key" => "treatment"}
# Flag metadata without evaluating anything.
Client.list_flags(client)
Include a :user_id (or :unit_id) whenever you want a stable answer.
Percentage rollouts and A/B variations hash it, so a context without one
re-rolls on every call by design.
Any attribute you put in the context map can be targeted on:
Client.flag(client, "beta-banner", %{
user_id: "alice",
country: "GB",
plan: "premium"
}, false)
Remote config
Client.config(client, "rate_limit", 100)
Client.get_config(client, "rate_limit") # the full record
Client.list_configs(client)
AI configs
Prompts, agents, skills and rules, versioned per environment and editable without a deploy.
Client.ai_config(client, "support-agent.md")
Client.list_ai_configs(client, file_type: "agent")
Translations
Client.translation(client, "checkout.greeting", "fr",
default: "Hello",
variables: %{name: "Alice"}
)
The key is namespace.message. {placeholders} come from :variables, and
:default (falling back to the key itself) is returned whenever the catalogue,
namespace or message is missing — a lookup never raises.
Experiments
case Client.experiment(client, "checkout-redesign", %{user_id: "alice"}) do
%{"variant" => "treatment"} -> # ...
_ -> # control, or no assignment
end
Client.track_experiment_metric(client, %{
experiment_key: "checkout-redesign",
event_name: "purchase",
user_id: "alice",
value: 42.50,
properties: %{currency: "GBP"}
})
experiment/3 returns nil for a context with no identifier — an assignment
that cannot be stable is worse than none.
Unlike the other server SDKs, metrics here are sent immediately rather than
buffered: track_experiment_metric/2 returns :ok or {:error, reason} and
there is no flush. Wrap it in a Task if you do not want the caller to wait.
Caching
Reads are cached in ETS for :cache_ttl (60s by default), so a burst of flag
calls costs one request.
Client.clear_cache(client)
Client.close(client) # deletes the ETS table
close/1 is what releases the table — a long-lived client never needs it, but a
short-lived one in a test should call it.
From Erlang
The flagdash_sdk module wraps the common calls so you do not need Elixir
syntax:
{ok, Client} = flagdash_sdk:new(<<"sk_...">>),
Enabled = flagdash_sdk:flag(Client, <<"checkout-v2">>, #{user_id => <<"alice">>}, false),
flagdash_sdk:close(Client).
new/1,2, flag/3,4, all_flags/1,2, config/3, ai_config/2,
list_ai_configs/1, clear_cache/1 and close/1 are exported. For anything
else, call 'Elixir.FlagDash.Client' directly.
Failure behaviour
Evaluation reads return the default you passed rather than raising, so an
outage degrades to your fallback values instead of taking a request path down.
Calls that fetch metadata return {:ok, _} / {:error, _} so an unreachable
host or a bad key stays visible.
Security
Keep a server key on the server. A client key never receives targeting rules, so an untrusted client cannot see who else you are targeting.
License
MIT