How Ghost Handles Feature Flags in Labs: GA, Beta, and Private Tiers Explained

Ghost organizes feature flags into three tiers—GA (always enabled), Public Beta (user-toggleable), and Private (developer-only)—managed through the labs.js module and exposed via conditional UI components in Admin-X Settings.

Ghost uses a Labs feature-flag system to safely roll out functionality across its self-hosted and managed platforms. The architecture separates features into distinct tiers that control visibility, persistence, and access permissions according to the TryGhost/Ghost source code. This system lives primarily in ghost/core/core/shared/labs.js and integrates with the Admin-X Settings React application.

The Three Feature Flag Tiers in Ghost

Ghost defines feature flags in three explicit tiers within the core labs module. Each tier determines whether a feature appears in the admin interface, who can toggle it, and whether it respects user preferences.

GA (General Availability) Features

GA features are fully released capabilities that remain always enabled for every Ghost installation. These flags reside in the GA_FEATURES array inside labs.js. Because these features are considered stable, they are added to the labs object unconditionally and do not display a toggle in the Labs settings page.

Public Beta Features

Public Beta features reside in the PUBLIC_BETA_FEATURES array. These flags appear under the "Beta features" tab in the Labs settings page, allowing site owners to opt into experimental functionality. Ghost exposes these via WRITABLE_KEYS_ALLOWLIST, which permits user-level persistence through the Settings API.

Private Features

Private features live in the PRIVATE_FEATURES array and represent experimental capabilities intended strictly for development and testing. These flags appear under the "Private features" tab, which itself is only visible when the developerExperiments flag is enabled. This tier protects production sites from accessing unstable or unsafe functionality.

Core Implementation in labs.js

The ghost/core/core/shared/labs.js module serves as the central authority for feature flag resolution and exposure.

Flag Registration and Exposure

Ghost registers flags through three distinct arrays:

const GA_FEATURES = ['featureOne', 'featureTwo'];
const PUBLIC_BETA_FEATURES = ['editorExcerpt', 'giftSubscriptions'];
const PRIVATE_FEATURES = ['automations', 'myNewPrivateFlag'];

module.exports.GA_KEYS = [...GA_FEATURES];
module.exports.WRITABLE_KEYS_ALLOWLIST = [...PUBLIC_BETA_FEATURES, ...PRIVATE_FEATURES];

The WRITABLE_KEYS_ALLOWLIST explicitly defines which flags users can toggle through the Settings UI, combining both Public Beta and Private tiers while excluding GA features.

Flag Resolution and Checking

The getAll() method merges default GA flags (forced to true), persisted labs JSON settings from the database, and derived flags such as members (based on members_signup_access). To verify a specific flag, code calls labs.isSet('flagName'), which returns true only when the merged configuration contains an explicit true value.

Helper Utilities for Conditional Execution

Ghost provides enabledHelper and enabledMiddleware wrappers to conditionally execute Handlebars helpers or Express routes:

const labs = require('../../core/shared/labs');

module.exports.enabledMiddleware = (req, res, next) => {
    if (labs.isSet('giftSubscriptions')) {
        return next();
    }
    return next(new errors.NotFoundError());
};

These utilities return standardized error responses when required flags are disabled, ensuring consistent behavior across the application.

Admin UI Components for Feature Toggles

The Admin-X Settings application renders feature toggles using React components that respect the tier-based visibility rules.

Public Beta Flags Interface

Public beta flags render in apps/admin-x-settings/src/components/settings/advanced/labs/beta-features.tsx. This component maps over PUBLIC_BETA_FEATURES to display FeatureToggle controls:

import FeatureToggle from './feature-toggle';
import LabItem from './lab-item';

const BetaFeatures: React.FC = () => (
    <List titleSeparator={false}>
        <LabItem
            action={<FeatureToggle flag="editorExcerpt" />}
            detail={<>Adds the excerpt input below the post title</>}
            title='Show post excerpt inline'
        />
    </List>
);

Private Flags Interface

Private flags render in apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx. The entire tab remains hidden unless developerExperiments is active:

const PrivateFeatures: React.FC = () => (
    <List titleSeparator={false}>
        <LabItem
            action={<FeatureToggle flag="automations" />}
            detail='Enable automations management interface.'
            title='Automations'
        />
    </List>
);

Adding New Feature Flags

To implement a new private feature flag, developers must update both the backend definitions and frontend UI:

// In ghost/core/core/shared/labs.js
const PRIVATE_FEATURES = [
    'automations',
    'myNewPrivateFlag'  // Add new flag here
];

Then add the corresponding UI entry in private-features.tsx to expose the toggle to developers.

Summary

  • Ghost Labs uses three explicit tiers: GA (always enabled), Public Beta (user-toggleable), and Private (developer-gated).
  • The GA_FEATURES, PUBLIC_BETA_FEATURES, and PRIVATE_FEATURES arrays in labs.js control flag classification and visibility.
  • WRITABLE_KEYS_ALLOWLIST determines which flags persist through the Settings API.
  • The Admin-X Settings app conditionally renders Beta and Private feature tabs based on the developerExperiments flag.
  • enabledMiddleware and enabledHelper utilities provide safe conditional execution patterns for routes and template helpers.

Frequently Asked Questions

What is the difference between Public Beta and Private feature flags in Ghost?

Public Beta flags appear to all site owners under the Beta features tab and can be toggled freely for early feedback. Private flags only appear when the Developer experiments setting is enabled, restricting access to developers who understand the risks of unstable functionality.

How do I check if a feature flag is enabled in Ghost code?

Import the labs module and call isSet() with the flag name: labs.isSet('flagName'). This method checks the merged configuration of GA defaults, database settings, and derived values to determine if the feature is active.

Why can't I see the Private features tab in Ghost Labs?

The Private features tab requires the Developer experiments flag to be enabled first. Without this prerequisite, Ghost hides the entire tab to prevent accidental activation of experimental features on production sites.

How do I add a new feature flag to Ghost?

Add the flag name to the appropriate array (GA_FEATURES, PUBLIC_BETA_FEATURES, or PRIVATE_FEATURES) in ghost/core/core/shared/labs.js. For Public or Private flags, also add a corresponding entry in the respective React component (beta-features.tsx or private-features.tsx) to render the toggle in the admin interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →