67 · SAP Feature Flags Service

Read boolean and string flag values at runtime, gate handlers and UI routes behind flags, and manage rollout percentage.

Episode 67 — SAP Feature Flags Service

What you'll learn

Why this matters

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.

How Feature Flags Service works

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.

Binding the service

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

Initialising the client

// 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;
};

Reading a flag value

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.

Gating CAP handlers behind flags

// 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();
  }
};

Gating front-end routes

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 */ }

Managing flags in the BTP Cockpit

  1. Navigate to: Services → Feature Flags → your instance → Dashboard
  2. Create a flag: name + type + default value
  3. Add targeting rules (user segment, tenant list, or percentage rollout)
  4. Toggle the flag on — no deployment required

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

Flag lifecycle hygiene

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.

Common mistakes

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

Checkpoint ✓

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 →