JavaScript
If you're using React, consider using our React Client instead, which also provides full TypeScript support.
Install the latest version
Use your favorite package manager to install @reforge-com/javascript npm | github
- npm
- yarn
- <script> tag
npm install @reforge-com/javascript
yarn add @reforge-com/javascript
Initialize the client
Initialize reforge with your SDK key:
- JavaScript
- <script> tag
import { reforge, Context } from "@reforge-com/javascript";
const options = {
sdkKey: "YOUR_SDK_KEY",
context: new Context({
user: {
email: "test@example.com",
},
device: { mobile: true },
}),
};
await reforge.init(options);
reforge.init will request the calculated feature flags for the provided context as a single HTTPS request. If you need to check for updates to feature flag values, you can learn more about polling below.
You aren't required to await the init -- it is a promise, so you can use .then, .finally, .catch, etc. instead if you prefer.
// `reforge` is available globally on the window object
const options = {
sdkKey: "REFORGE_FRONTEND_SDK_KEY",
};
reforge.init(options).then(() => {
console.log(options);
console.log("test-flag is " + reforge.isEnabled("test-flag"));
});
While reforge is loading, isEnabled will return false, get will return undefined, and shouldLog will use your defaultLevel.
Feature Flags
Now you can use reforge's feature flag evaluation, e.g.
- JavaScript
if (reforge.isEnabled("cool-feature")) {
// ... this code only evaluates if `cool-feature` is enabled for the current context
}
You can also use:
-
getto access the value of non-boolean flagsconst stringValue = reforge.get("my-string-flag"); -
getDurationfor time-specific valuesconst timeout = reforge.getDuration("api-timeout");
if (timeout) {
console.log(`Timeout: ${timeout.seconds}s (${timeout.ms}ms)`);
}
Context
Context accepts an object with keys that are context names and key value pairs with attributes describing the context. You can use this to write targeting rules, e.g. segment your users.
- JavaScript
- <script> tag
import { reforge, Context } from "@reforge-com/javascript";
const options = {
sdkKey: "REFORGE_FRONTEND_SDK_KEY",
context: new Context({
user: { key: "abcdef", email: "test@example.com" },
device: { key: "hijklm", mobile: true },
}),
};
await reforge.init(options);
// `reforge` is available globally on the window object
// `Context` is available globally as `window.reforgeNamespace.Context`
const options = {
sdkKey: "REFORGE_FRONTEND_SDK_KEY",
context: new reforgeNamespace.Context({
user: {
email: "test@example.com",
},
device: { mobile: true },
}),
};
reforge.init(options).then(() => {
console.log(options);
console.log("test-flag is " + reforge.isEnabled("test-flag"));
console.log("ex1-copywrite " + reforge.get("ex1-copywrite"));
$(".copywrite").text(reforge.get("ex1-copywrite"));
});
poll()
After reforge.init(), you can start polling. Polling uses the context you defined in init by default. You can update the context for future polling by setting it on the reforge object.
- JavaScript
// some time after init
reforge.poll({ frequencyInMs: 300000 });
// we're now polling with the context used from `init`
// later, perhaps after a visitor logs in and now you have the context of
// their current user
reforge.updateContext({
...reforge.context,
user: { email: user.email, key: user.trackingId },
});
// updateContext will immediately load the newest from Reforge based on the
// new context. Future polling will use the new context as well.
Dynamic Config
Config values are accessed the same way as feature flag values. You can use isEnabled as a convenience for boolean values, and get works for all data types.
By default configs are not sent to client SDKs. You must enable access for each individual config. You can do this by checking the "Send to client SDKs" checkbox when creating or editing a config.
Dynamic Log Levels
The Reforge JavaScript SDK provides basic dynamic log level control for client-side applications. This allows you to control console logging verbosity from the Reforge dashboard.
The JavaScript SDK evaluates log levels once during initialization using the provided context. Unlike backend SDKs that support real-time per-request context, the JavaScript SDK:
- Evaluates log levels with a single context at initialization time
- Best suited for application-wide log level control rather than per-user targeting
For more advanced logging features, consider using backend SDKs.
Basic Usage
The SDK provides a built-in logger with standard log levels:
- JavaScript
import { reforge } from "@reforge-com/javascript";
await reforge.init({
sdkKey: "YOUR_SDK_KEY",
context: new Context({ user: { email: "test@example.com" } }),
});
// Use the built-in logger
reforge.logger.trace("Trace message");
reforge.logger.debug("Debug message");
reforge.logger.info("Info message");
reforge.logger.warn("Warning message");
reforge.logger.error("Error message");
reforge.logger.fatal("Fatal error");
The logger automatically checks the configured log level and only outputs to console when appropriate.
Configuration
Create a LOG_LEVEL_V2 config in your Reforge dashboard with key log-levels.default:
# Default log level for the application
default: INFO
# Optional: Use rules for context-based levels (evaluated once at init)
rules:
- criteria:
user.email:
ends-with: "@mycompany.com"
value: DEBUG
You can customize the config key name using the loggerKey option. This is particularly useful if you have multiple applications sharing the same Reforge project:
- JavaScript
await reforge.init({
sdkKey: "YOUR_SDK_KEY",
context: new Context({ user: { email: "test@example.com" } }),
loggerKey: "my-app.log-levels", // Custom config key
});
Programmatic Log Level Checking
You can check log levels programmatically to conditionally execute expensive logging operations:
- JavaScript
import { reforge, LogLevel, shouldLogAtLevel } from "@reforge-com/javascript";
// Get the configured log level
const currentLevel = reforge.getLogLevel("my.logger");
// Check if a specific level should be logged
if (shouldLogAtLevel(currentLevel, LogLevel.DEBUG)) {
// Only compute expensive debug info when DEBUG is enabled
console.debug("Expensive computation:", computeExpensiveData());
}
// Get numeric severity for custom logic
const severity = getLogLevelSeverity(currentLevel);
Available log levels (in order of severity):
LogLevel.TRACE(most verbose)LogLevel.DEBUGLogLevel.INFOLogLevel.WARNLogLevel.ERRORLogLevel.FATAL(least verbose)
How It Works
The JavaScript SDK evaluates the log level configuration once during initialization:
- When you call
reforge.init(), the SDK fetches all configs including log levels - Log levels are evaluated using the context provided to
init() - The configured level remains static until you call
reforge.updateContext()or manually refresh - The built-in
reforge.logger.*methods automatically filter based on the configured level
Since log levels are evaluated with the initial context, the loggerKey option is the most effective way to isolate log levels between different applications or deployments. Use separate config keys like "app-v1.log-levels" and "app-v2.log-levels" rather than relying on complex context-based rules.
Tracking Experiment Exposures
If you're using Reforge Launch for A/B testing, you can supply code for tracking experiment exposures to your data warehouse or analytics tool of choice.
- JavaScript
import { reforge, Context } from "@reforge-com/javascript";
const options = {
sdkKey: "REFORGE_FRONTEND_SDK_KEY",
context: new Context({
user: { key: "abcdef", email: "test@example.com" },
device: { key: "hijklm", mobile: true },
}),
afterEvaluationCallback: (key, value) => {
// call your analytics tool here...in this example we are sending data to posthog
window.posthog?.capture("Feature Flag Evaluation", {
key,
value,
});
},
};
await reforge.init(options);
afterEvaluationCallback will be called each time you evaluate a feature flag or config using get or isEnabled.
Telemetry
By default, Reforge will collect summary counts of config and feature flag evaluations to help you understand how your configs and flags are being used in the real world. You can opt out of this behavior by passing collectEvaluationSummaries: false in the options to reforge.init.
Reforge also stores the context that you pass in. The context keys are used to power autocomplete in the rule editor, and the individual values power the Contexts page for troubleshooting targeting rules and individual flag overrides. If you want to change what Reforge stores, you can pass a different value for collectContextMode.
collectContextMode value | Behavior |
|---|---|
PERIODIC_EXAMPLE | Stores context values and context keys. This is the default. |
SHAPE_ONLY | Stores context keys only. |
NONE | Stores nothing. Context will only be used for rule evaluation. |
Testing
In your test suite, you should skip reforge.init altogether and instead use reforge.hydrate to set up your test state.
- JavaScript
it("shows the turbo button when the feature is enabled", () => {
reforge.hydrate({
turbo: true,
defaultMediaCount: 3,
});
const rendered = new MyComponent().render();
expect(rendered).toMatch(/Enable Turbo/);
expect(rendered).toMatch(/Media Count: 3/);
});
Reference
reforge Properties
| property | example | purpose |
|---|---|---|
context | reforge.context | get the current context (after init()). |
extract | reforge.extract() | returns the current config as a plain object of key, config value pairs |
getDuration | reforge.getDuration("timeout-key") | returns a Duration object with seconds and ms properties for duration configs |
get | reforge.get('retry-count') | returns the value of a flag or config evaluated in the current context |
getLogLevel | reforge.getLogLevel("my.logger") | returns the configured LogLevel enum value for the specified logger name |
hydrate | reforge.hydrate(configurationObject) | sets the current config based on a plain object of key, config value pairs |
isEnabled | reforge.isEnabled("new-logo") | returns a boolean (default false) if a feature is enabled based on the current context |
loaded | if (reforge.loaded) { ... } | a boolean indicating whether reforge content has loaded |
logger | reforge.logger.info("message") | built-in logger with methods: trace, debug, info, warn, error, fatal |
poll | reforge.poll({frequencyInMs}) | starts polling every frequencyInMs ms. |
shouldLog | if (reforge.shouldLog(...)) { | returns a boolean indicating whether the proposed log level is valid for the current context |
stopPolling | reforge.stopPolling() | stops the polling process |
stopTelemetry | reforge.stopTelemetry() | stops telemetry collection and clears aggregators |
updateContext | reforge.updateContext(newContext) | update the context and refetch. Pass false as a second argument to skip refetching |
init() Options
| option | type | default | description |
|---|---|---|---|
sdkKey | string | required | Your Reforge SDK key |
context | Context | {} | Initial context for evaluation |
loggerKey | string | "log-levels.default" | Config key for LOG_LEVEL_V2 configuration (useful for isolating log levels per application) |
defaultLevel | LogLevel | LogLevel.WARN | Default log level when no configuration is found |
collectEvaluationSummaries | boolean | true | Send evaluation summary telemetry to Reforge |
collectContextMode | string | "PERIODIC_EXAMPLE" | Context telemetry mode: "PERIODIC_EXAMPLE", "SHAPE_ONLY", or "NONE" |
afterEvaluationCallback | function | undefined | Callback invoked after each flag/config evaluation |