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

> Understand how Ghost uses feature flags like GA, Beta, and Private tiers. Learn about labs.js and conditional UI components for managing features in Ghost Admin.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: internals
- Published: 2026-05-18

---

**Ghost organizes feature flags into three tiers—GA (always enabled), Public Beta (user-toggleable), and Private (developer-only)—managed through the [`labs.js`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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:

```javascript
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:

```javascript
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`](https://github.com/TryGhost/Ghost/blob/main/apps/admin-x-settings/src/components/settings/advanced/labs/beta-features.tsx). This component maps over `PUBLIC_BETA_FEATURES` to display `FeatureToggle` controls:

```tsx
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`](https://github.com/TryGhost/Ghost/blob/main/apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx). The entire tab remains hidden unless `developerExperiments` is active:

```tsx
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:

```javascript
// 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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/shared/labs.js). For Public or Private flags, also add a corresponding entry in the respective React component ([`beta-features.tsx`](https://github.com/TryGhost/Ghost/blob/main/beta-features.tsx) or [`private-features.tsx`](https://github.com/TryGhost/Ghost/blob/main/private-features.tsx)) to render the toggle in the admin interface.