Automatic type-safe interfaces for your feature flags with complete client code
FlagFlow automatically generates complete TypeScript definitions for your feature flags, providing full type safety and IntelliSense support. The generated code includes type definitions, default values, hash constants, and ready-to-use client functions.
Zero Configuration: Simply download the generated TypeScript file and start using fully-typed feature flags in your application immediately.
When you access /type/typescript, FlagFlow generates a complete TypeScript file
containing:
FlagFlow maps its internal flag types to appropriate TypeScript types:
| FlagFlow Type | TypeScript Type | Example |
|---|---|---|
| BOOLEAN | boolean | true | false |
| INTEGER | number | 42 |
| STRING | string | "hello world" |
| ENUM | Union Type | "red" | "green" | "blue" |
| TAG | Array of Union | ("red" | "green" | "blue")[] |
| AB-TEST | string | "A" | "B" (runtime) |
Here's an example of the TypeScript types generated for a hierarchical flag structure:
// Generated TypeScript Definitions
// Root type for all flags
export type FlagFlow = {
readonly enableNewFeature: boolean;
readonly maxRetries: number;
readonly theme: "light" | "dark" | "auto";
readonly enabledFeatures: ("analytics" | "chat" | "notifications")[];
readonly abTestVariant: string; // AB-TEST type
readonly accounting: FlagFlow__Accounting;
};
// Nested group type
export type FlagFlow__Accounting = {
readonly enableCurrencyExchange: boolean;
readonly defaultCurrency: "USD" | "EUR" | "HUF";
readonly huf: FlagFlow__Accounting__Huf;
};
// Deeply nested group type
export type FlagFlow__Accounting__Huf = {
readonly exchangeRate: number;
readonly allowExchange: boolean;
};
// Complete type mapping for all groups
export type FlagFlow_DescriptorTypeMap = {
'#root': FlagFlow;
'accounting': FlagFlow__Accounting;
'accounting/huf': FlagFlow__Accounting__Huf;
};// Generated default value objects for runtime use
export const defaultFlagFlow: FlagFlow = {
enableNewFeature: false,
maxRetries: 3,
theme: "auto",
enabledFeatures: ["analytics"],
abTestVariant: "A", // Default for AB-TEST
accounting: defaultFlagFlow__Accounting,
};
export const defaultFlagFlow__Accounting: FlagFlow__Accounting = {
enableCurrencyExchange: true,
defaultCurrency: "USD",
huf: defaultFlagFlow__Accounting__Huf,
};
export const defaultFlagFlow__Accounting__Huf: FlagFlow__Accounting__Huf = {
exchangeRate: 380.5,
allowExchange: false,
};The TypeScript file includes complete, ready-to-use client code with full type safety:
import axios from "axios";
import { flagFlow_Descriptors, FlagFlow_DescriptorTypeMap } from "./flagflowTypes";
const FLAGFLOW_BASE_URL = 'http://localhost:3000/flags';
// Basic fetch function with full type safety
export const fetchData = async <K extends keyof FlagFlow_DescriptorTypeMap>(
key: K
): Promise<FlagFlow_DescriptorTypeMap[K]> => {
const { uri, hash } = flagFlow_Descriptors[key];
const { data } = await axios.get<FlagFlow_DescriptorTypeMap[K]>(
FLAGFLOW_BASE_URL + uri, {
responseType: 'json',
headers: {
'Content-Type': 'application/json',
'x-accept-flaggroup-hash': hash,
}
})
return data
}
// Factory function for creating fetch functions
export const createFetchFunction = <K extends keyof FlagFlow_DescriptorTypeMap>(
key: K
): (() => Promise<FlagFlow_DescriptorTypeMap[K]>) => () => fetchData(key);
// Cached fetch function with TTL support
export const createFetchFunctionWithCache = <K extends keyof FlagFlow_DescriptorTypeMap>(
key: K,
ttlSeconds: number = 60
) => {
const fetchDataFn = createFetchFunction(key);
let data: FlagFlow_DescriptorTypeMap[K] | undefined;
let lastFetchTime: number = 0;
return async (): Promise<FlagFlow_DescriptorTypeMap[K]> => {
const now = Date.now();
if (data && (now - lastFetchTime < ttlSeconds * 1000)) return data;
data = await fetchDataFn();
lastFetchTime = now;
return data;
}
}import { fetchData, FlagFlow } from './flagflowTypes';
// Fetch all root flags with full type safety
const flags: FlagFlow = await fetchData('#root');
// Access flags with IntelliSense support
if (flags.enableNewFeature) {
console.log('New feature is enabled!');
}
// Type-safe enum access
console.log(`Current theme: ${flags.theme}`); // "light" | "dark" | "auto"
// Array type support
if (flags.enabledFeatures.includes('analytics')) {
initializeAnalytics();
}
// Nested group access
const accountingFlags = await fetchData('accounting');
console.log(`Exchange enabled: ${accountingFlags.enableCurrencyExchange}`);import { createFetchFunctionWithCache } from './flagflowTypes';
// Create cached fetch function (30 second TTL)
const getCachedFlags = createFetchFunctionWithCache('#root', 30);
// Use in your application
async function checkFeatures() {
const flags = await getCachedFlags(); // Uses cache if fresh
return {
showNewUI: flags.enableNewFeature,
theme: flags.theme,
maxRetries: flags.maxRetries
};
}
// Multiple calls within 30 seconds use cached data
const features1 = await checkFeatures(); // Network request
const features2 = await checkFeatures(); // From cache
const features3 = await checkFeatures(); // From cacheimport React, { useState, useEffect } from 'react';
import { createFetchFunctionWithCache, FlagFlow } from './flagflowTypes';
// Create cached fetcher outside component
const getCachedFlags = createFetchFunctionWithCache('#root', 60);
export function useFeatureFlags() {
const [flags, setFlags] = useState<FlagFlow | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let mounted = true;
getCachedFlags()
.then(data => {
if (mounted) {
setFlags(data);
setLoading(false);
}
})
.catch(err => {
if (mounted) {
setError(err);
setLoading(false);
}
});
return () => { mounted = false; };
}, []);
return { flags, loading, error };
}
// Usage in component
function MyComponent() {
const { flags, loading, error } = useFeatureFlags();
if (loading) return <div>Loading flags...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!flags) return null;
return (
<div className={flags.theme}>
{flags.enableNewFeature && <NewFeatureComponent />}
<Analytics enabled={flags.enabledFeatures.includes('analytics')} />
</div>
);
}# Get TypeScript definitions curl http://localhost:3000/type/typescript # Save to file curl http://localhost:3000/type/typescript > flagflowTypes.ts # Download as file attachment curl http://localhost:3000/type/typescript?download=1
The generated file includes helpful metadata:
// This file is auto-generated by FlagFlow // Generated at: 2024-08-10T14:30:25.123Z // FlagFlow Version: 1.0.0 // Total Flags: 15 // Total Groups: 4 // // To regenerate this file: // curl http://localhost:3000/type/typescript > flagflowTypes.ts // // ⚠️ Do not edit this file manually - changes will be lost!
// flagService.ts
import { createFetchFunctionWithCache, FlagFlow } from './flagflowTypes';
class FlagService {
private getCachedFlags = createFetchFunctionWithCache('#root', 300); // 5 minutes
private flags: FlagFlow | null = null;
async initialize(): Promise<void> {
try {
this.flags = await this.getCachedFlags();
console.log('✅ Feature flags loaded successfully');
} catch (error) {
console.error('❌ Failed to load feature flags:', error);
throw error;
}
}
isEnabled(feature: keyof FlagFlow): boolean {
return this.flags?.[feature] === true;
}
getValue<K extends keyof FlagFlow>(key: K): FlagFlow[K] | null {
return this.flags?.[key] || null;
}
async refresh(): Promise<void> {
// Force refresh by creating new fetcher
this.getCachedFlags = createFetchFunctionWithCache('#root', 300);
await this.initialize();
}
}
export const flagService = new FlagService();
// Initialize at app startup
flagService.initialize().catch(console.error);// config.ts
const FLAGFLOW_URLS = {
development: 'http://localhost:3000',
staging: 'https://staging-flags.example.com',
production: 'https://flags.example.com'
};
const environment = process.env.NODE_ENV || 'development';
export const FLAGFLOW_BASE_URL = FLAGFLOW_URLS[environment] + '/flags';
// flagService.ts - Updated with environment config
import { FLAGFLOW_BASE_URL } from './config';
// Override the generated base URL
const originalFetchData = fetchData;
export const fetchData = async <K extends keyof FlagFlow_DescriptorTypeMap>(
key: K
): Promise<FlagFlow_DescriptorTypeMap[K]> => {
const { uri, hash } = flagFlow_Descriptors[key];
const { data } = await axios.get<FlagFlow_DescriptorTypeMap[K]>(
FLAGFLOW_BASE_URL + uri, {
responseType: 'json',
headers: {
'Content-Type': 'application/json',
'x-accept-flaggroup-hash': hash,
}
});
return data;
};curl /type/typescript > flagflowTypes.ts