Play+ Feature Flag Helper: playfeature
Introduction
In the Play+ ecosystem, we believe shipping code should be empowering, not risky. This helper is based on the concept of decoupling deployment from release, a modern development practice that allows us to merge and deploy features to production safely, while precisely controlling their visibility.
Feature flags (or toggles) are essential for mitigating risk, enabling A/B testing, and personalizing user experiences at scale. This aligns directly with our Adaptive pillar, allowing the application to change dynamically based on user context or business needs. It also promotes an Intuitive development workflow by simplifying a complex topic and makes the product more Engaging by allowing for controlled experiments with new features.
Package Info
The playfeature helper is provided through the @playplus/features package, which is included by default in the Golden Path.
Description | Package / Path |
---|---|
Golden Path (Recommended) | Pre-installed (/system/play.feature.ts) |
Uplift Path | npm install @playplus/features |
Folder Reference
The feature flag helper and its configuration follow our standardized folder structure for core system logic.
File / Directory | Purpose & Guidelines |
---|---|
system/play.feature.ts | The core feature flag service. It abstracts the third-party provider and provides a consistent API. |
config/play.feature.config.json | User-overridable configuration for the flag provider, client keys, and default values for offline development. |
Helper - Pillars Alignment
The playfeature helper is a key enabler of our core design pillars.
Pillar | How This Helper Aligns |
---|---|
Adaptive | Primary Pillar: Allows the application's UI and behavior to adapt in real-time based on user, region, or experimental group. |
Intuitive | Abstracts the complexity of third-party SDKs into a simple, synchronous API, making it trivial for developers to use flags. |
Engaging | Empowers teams to safely A/B test new, delightful features and interactions, gathering data to create more engaging experiences. |
Helper Overview
The playfeature helper is your application's runtime control panel. It provides a clean, synchronous, and framework-agnostic interface to your feature flagging provider (e.g., LaunchDarkly, Optimizely, Flagsmith). Its purpose is to abstract the plumbing of flag management, so developers can focus on building features, not on SDK integration.
Behind the scenes, the helper automates the entire process:
- Initialization: On app startup, it connects to your flag provider using the configured client key.
- User Identification: It automatically identifies the current user to fetch targeted flags.
- Real-time Updates: It maintains a live connection, so any change made in your provider's dashboard is reflected in the app in real-time, without a page refresh.
- Offline Graceful Degradation: If the provider is unreachable, it falls back to default values defined in the configuration.
This means a developer can simply ask playfeature.isEnabled('my-flag')
and trust that the system is handling all the complex state management in the background.
Config Options
Global configuration is managed in config/play.feature.config.json.
Config Variable | Default Value | Description | Recommended Value |
---|---|---|---|
provider | "local" | The name of your feature flag provider (e.g., launchdarkly, flagsmith, optimizely). local uses bootstrap values. | Your provider's name. |
clientKey | null | The client-side ID or key for your feature flag project. | Your provider's client key. |
bootstrap | An object of default flag values to use during initial load or for offline development and testing. | Define key flags for testing. |
Example play.feature.config.json:
{
"provider": "launchdarkly",
"clientKey": "sdk-12345-abcde",
"bootstrap": {
"feat-new-dashboard-2025-q3": false,
"enable-beta-analytics": true
}
}
Helper Methods
The helper provides a simple, consistent API for accessing flag values.
Method Name | What It Does | Method Signature |
---|---|---|
isEnabled | Checks if a boolean feature flag is enabled. Returns defaultValue if the flag doesn't exist. | isEnabled(key: string, defaultValue?: boolean): boolean |
getVariant | Gets the string value for a multivariate or experiment flag. | getVariant(key: string, defaultVariant?: string): string |
onUpdate | Subscribes to all flag changes from the provider, allowing the UI to react in real-time. | onUpdate(callback: () => void): void |
offUpdate | Unsubscribes from flag changes to prevent memory leaks. | offUpdate(callback: () => void): void |
Usage Examples
React: The useFeature Hook
For a seamless experience in React, the starter kit provides a useFeature hook that automatically subscribes to live updates.
// hooks/useFeature.ts
import { useState, useEffect } from 'react';
import { playfeature } from '../system/play.feature';
export function useFeature(key: string, defaultValue: boolean): boolean {
const [value, setValue] = useState(() => playfeature.isEnabled(key, defaultValue));
useEffect(() => {
// A function to update state when flags change.
const handleUpdate = () => {
setValue(playfeature.isEnabled(key, defaultValue));
};
// Set initial value and subscribe to live updates.
handleUpdate();
playfeature.onUpdate(handleUpdate);
// Clean up the subscription on component unmount.
return () => {
playfeature.offUpdate(handleUpdate);
};
}, [key, defaultValue]);
return value;
}
// In a component:
// MainDashboard.tsx
import { useFeature } from '../hooks/useFeature';
function MainDashboard() {
const showNewAnalyticsCard = useFeature('feat-new-analytics-card', false);
return (
<div>
<UserInfoCard />
{showNewAnalyticsCard ? <NewAnalyticsCard /> : <OldAnalyticsCard />}
</div>
);
}
Angular: The PlayFeatureGuard for Routes
For Angular, a CanActivate guard is the perfect way to toggle access to entire feature routes.
// guards/play-feature.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, Router } from '@angular/router';
import { playfeature } from '@playplus/core'; // Assuming helper is available
@Injectable({ providedIn: 'root' })
export class PlayFeatureGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
const flagName = route.data['featureFlag'];
if (flagName && playfeature.isEnabled(flagName)) {
return true; // Allow access to the route
}
// If flag is off or doesn't exist, redirect away
this.router.navigate(['/dashboard']);
return false;
}
}
// app-routing.module.ts
import { PlayFeatureGuard } from './guards/play-feature.guard';
const routes: Routes = [
{
path: 'next-gen-dashboard',
loadChildren: () => import('./next-gen/dashboard.module').then(m => m.DashboardModule),
canActivate: [PlayFeatureGuard],
data: { featureFlag: 'feat-next-gen-dashboard-enabled' }
}
];
Additional Info
Why We Created This Helper
Without a centralized helper, each developer would need to:
- Integrate and learn a complex, third-party feature flag SDK.
- Manually handle user identification for targeted rollouts.
- Build their own logic for real-time updates and subscriptions.
- Write boilerplate code to handle cases where the flag provider is offline.
This is inefficient and error-prone. The playfeature helper abstracts all of this into a single, reliable system. It provides a consistent, tested interface so developers can add or check a feature flag in a single line of code, trusting that the underlying complexity is managed for them.
Best Practices
- Keep Flags Short-Lived: Flags are temporary. Create a process for cleaning them up after a feature is fully rolled out to avoid technical debt.
- Use Descriptive Names: A good name like
feat-newDashboard-rollout-2025-q3
is self-documenting. It tells you the feature, its purpose, and its expected lifespan. - Abstract Flag Checks: Don't sprinkle
playfeature.isEnabled()
calls directly in JSX. Encapsulate the logic in a variable, hook (useFeature), or function for better readability and maintenance. - Always Provide a Default Value: This ensures your application behaves predictably if the flagging service is down or a flag is deleted.
Developer Checklist
- Does my new flag have a descriptive name and a cleanup plan?
- Have I provided a safe defaultValue for my isEnabled check?
- Is the flag check abstracted (e.g., in a variable) rather than being inline in the UI logic?
- Have I added the flag to the bootstrap config for offline testing?
- If the flag is long-term, have I discussed its purpose with the team?
Governance & Hygiene
As flag usage grows, discipline is key. Consider establishing formal governance rules:
- Flag Types: Categorize flags to clarify their purpose (e.g., release-toggle, experiment, ops-switch, kill-switch).
- Environments: Ensure your provider supports toggling flags independently across different environments (dev, staging, prod).
- Auditing: Your provider should have an audit log to track who created, toggled, or removed a flag.
- Lifecycle Metadata: Use tags or descriptions in your provider's UI to add context like
owner: team-core-ui
,jira: PROJ-123
,introduced: 2025-Q3
.
Treat feature flags as first-class citizens in your architecture. Without good hygiene, they become technical debt.