Read boolean and string flag values at runtime, gate handlers and UI routes behind flags, and manage rollout percentage.
Feature flags decouple code deployment from feature activation. You can merge incomplete features behind a flag, roll them out to internal users first, increase the percentage incrementally, and roll back instantly without a new deployment. On BTP, SAP Feature Flags Service stores the flag configuration centrally so all app instances see the same state without a restart.
The service stores feature flag definitions (name + current state) in a central store. Your application queries the REST API (or uses the Node.js client) to get the current value at request time. The service supports:
| Flag type | Description |
|---|---|
| Boolean | Simple on/off |
| Number | Numeric variants (A/B test weights) |
| String | Named strategy variants |
Each flag supports targeting rules: user ID lists, tenant IDs, attribute-based matchers, and percentage-based rollouts.
cf create-service feature-flags standard my-feature-flags
cf bind-service my-cap-app my-feature-flags
Install the client:
npm install @sap/feature-flags-node-sdk
// srv/feature-flags.js
const FeatureFlags = require('@sap/feature-flags-node-sdk');
let client;
module.exports.getFF = function () {
if (!client) {
const vcap = JSON.parse(process.env.VCAP_SERVICES || '{}');
const creds = vcap['feature-flags']?.[0]?.credentials;
if (!creds) throw new Error('Feature Flags Service not bound');
client = new FeatureFlags({
instanceUrl: creds.uri,
clientId: creds.username,
clientSecret: creds.password
});
}
return client;
};
const { getFF } = require('./feature-flags');
// Boolean flag — is the new approval UI enabled?
async function isApprovalV2Enabled(userId, tenantId) {
const ff = getFF();
return ff.getBooleanVariation('approval-workflow-v2', false, {
identifier: userId,
attributes: { tenant: tenantId }
});
}
// String flag — which email template to use?
async function getEmailTemplate(userId) {
const ff = getFF();
const variant = await ff.getStringVariation('email-template', 'classic', {
identifier: userId
});
return variant; // 'classic' | 'modern' | 'minimal'
}
getBooleanVariation(flagName, defaultValue, context) — returns the default if the service is unavailable. Always provide a sensible default so a flag service outage degrades gracefully.
// srv/approval-service.js
module.exports = class ApprovalService extends cds.ApplicationService {
async init() {
const { Approvals } = this.entities;
this.on('submitForApproval', async req => {
const ff = require('./feature-flags').getFF();
const useV2 = await ff.getBooleanVariation(
'approval-workflow-v2', false,
{ identifier: req.user.id }
);
if (useV2) {
return this._submitV2(req);
} else {
return this._submitV1(req);
}
});
await super.init();
}
};
Expose a lightweight /feature-flags endpoint from your CAP service:
service FeatureFlagService @(path:'/feature-flags') {
@readonly function evaluate(flag: String) returns Boolean;
}
this.on('evaluate', async req => {
const { flag } = req.data;
const ff = require('./feature-flags').getFF();
const ALLOWED = ['approval-workflow-v2', 'arena-season-2'];
if (!ALLOWED.includes(flag)) return req.reject(400, `Unknown flag: ${flag}`);
return ff.getBooleanVariation(flag, false, {
identifier: req.user.id,
attributes: { tenant: req.tenant }
});
});
React side:
const v2 = await fetch('/feature-flags/evaluate(flag=\'approval-workflow-v2\')').then(r => r.json());
if (v2.value) { /* render new UI */ }
Percentage rollout workflow:
- Start at 0% (off for everyone)
- Set to 5% → monitor error rates in Kibana (Episode 66)
- Increase to 25%, 50%, 100% over days or weeks
- Once stable at 100%, remove the flag from code and retire it
| Stage | Action |
|---|---|
| Development | Create flag; default false; gate the new code path |
| Internal test | Enable for specific user list |
| Beta | 10% percentage rollout |
| GA | 100% rollout |
| Retired | Remove flag check from code; delete flag in cockpit |
Stale flags are tech debt — schedule a review every quarter.
| Mistake | Fix |
|---|---|
Calling getBooleanVariation in a hot loop without caching |
Cache the result per request, not per call |
| No default value | Provide sensible defaults — service outages must not break the app |
| Exposing all flags via the evaluate endpoint | Allowlist flags that the frontend is permitted to query |
| Never retiring flags | Flags accumulate; stale ones make the code unreadable |
| Different flag names in dev/prod | Use the same flag name everywhere; control the value per environment |
You can bind Feature Flags Service, read boolean and string flag values at runtime, gate CAP handlers and UI routes behind flags, use percentage rollouts to reduce risk, and manage the flag lifecycle from development to retirement.
Sign in for progress tracking & Q&A →