SDKs
Browser SDK
Read flags in web pages with a public client key. Pages get each user's values, never the rules.
Browser code can’t be trusted with targeting rules: a list of user keys or an email-domain rule is private. So in the browser, Signal evaluates flags on its side and sends back only the results for the user you pass.
Install
It’s part of the same package as the server SDK, imported from @mafzaldev/signal/browser:
npm install @mafzaldev/signal
Bundlers such as Vite, webpack and Next.js include only the browser client, so the server-side evaluation code stays out of your page.
Setup
- In project settings → SDK keys, create a key and choose Client. Client keys start with
client-and are safe to ship in a web page: all they can do is ask for values. - On each flag the page should read, turn on Client-side access in the flag’s settings. Flags without it are left out of the results.
Quick start
import { SignalClient } from '@mafzaldev/signal/browser';
const signal = new SignalClient({
clientKey: 'client-…',
baseUrl: 'https://signal.example.com',
context: { key: 'user-42', country: 'PK', plan: 'pro' },
});
await signal.ready({ timeout: 3000 });
if (signal.bool('new-checkout', false)) {
// new checkout
}
When a flag changes, Signal tells the page over a stream and the client fetches its values again, so change fires within a second.
Switching users
The context is set once, so methods don’t take one. When the user signs in or changes, call identify:
await signal.identify({ key: 'user-77', country: 'AE' });
It resolves true once the new user’s values have arrived from Signal, or false if Signal couldn’t be reached. If identify is called again before that, only the latest user’s values are applied.
Switching to a different key drops the previous user’s values straight away, so one person’s flags are never shown to another. Until the new user’s values arrive, reads return their saved values if this browser has them, otherwise your defaults. Changing only attributes, with the same key, keeps the current values until the new ones arrive.
When Signal is unavailable
- While the page is open, flags keep their last values and the client reconnects with backoff. When Signal is back, it fetches fresh values and
changefires for anything that differs. - On a reload, the client starts from the values it saved in
localStoragefor the same user, so the page doesn’t fall back to defaults during an outage.dataSource()tells you which you have:'server','cache'or'none'. - On a first visit with Signal down, there’s nothing saved, so every read returns your default. Make defaults the safe, existing behaviour.
The saved entry holds the last user’s evaluated values, never rules, under a key that includes the start of your client key, so environments don’t mix. Values are only served back to the same user key. Pass storage: null to keep nothing, or your own object with getItem and setItem.
Methods
| Method | Description |
|---|---|
bool(key, default) |
A boolean flag’s value |
string(key, default) |
A string flag’s value |
number(key, default) |
A number flag’s value |
json(key, default) |
A JSON flag’s value |
value(key, default) |
Any flag value |
detail(key, default) |
{ value, variationIndex, reason }, as in the server SDK |
allFlags() |
Every flag’s value for the current user |
context() / identify(ctx) |
Read or change the current user |
ready({ timeout? }) |
Resolves true once values arrive, or false on timeout or a rejected key |
isReady() |
Whether there are values for the current user, from Signal or the cache |
dataSource() |
'server', 'cache' or 'none': where the current values came from |
on('ready' | 'change' | 'error', fn) |
Adds a listener; returns a function that removes it |
close() |
Stops listening for changes. Values stay readable |
Like the server SDK, every read returns your default until values arrive, for flags without client-side access, and for values of the wrong type.
Options
| Option | Default | Description |
|---|---|---|
clientKey |
required | A client key for the environment |
baseUrl |
required | URL of the Signal API |
context |
required | The first user to evaluate for |
storage |
localStorage |
Where to save the last values; null saves nothing |
streamTimeoutMs |
60000 |
Reconnect if nothing, not even a heartbeat, arrives for this long |
initialReconnectDelayMs |
1000 |
First reconnect delay |
maxReconnectDelayMs |
30000 |
Longest reconnect delay |
fetch |
global fetch |
Custom fetch implementation |
logger |
console |
Receives warn and error messages |
Using it with React
Create one client outside your components and re-render when values change:
const signal = new SignalClient({ clientKey, baseUrl, context: currentUser });
function useFlags() {
const [, setVersion] = useState(0);
useEffect(() => {
const rerender = () => setVersion((v) => v + 1);
const off = [signal.on('ready', rerender), signal.on('change', rerender)];
return () => off.forEach((remove) => remove());
}, []);
return signal;
}
function Checkout() {
const flags = useFlags();
return flags.bool('new-checkout', false) ? <OneClickCheckout /> : <ClassicCheckout />;
}
Server or browser?
| Server SDK | Browser SDK | |
|---|---|---|
| Key | Server key, kept secret | Client key, public |
| Receives | Every rule, evaluated in memory | One user’s values |
| Which flags | All of them | Only those with client-side access |
| When Signal is down | Serves the last rules, even after a restart | Serves the last values, even after a reload |
If your page is rendered on a server you control, reading flags there with the server SDK keeps even the values off the network.