Skip to content
Content
Skill

/add-feature-flag

Adding a new feature flag to the Webiny system. Use this skill when creating a new feature flag (simple boolean or nested group), gating a feature at the config/admin/API level, or wiring a flag into the WCP license system. Covers IFeatureFlagsDto, KnownFeatureFlag, Zod schema,

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill add-feature-flag --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/add-feature-flag

Context preview

The summary Claude sees to decide when to auto-load this skill.

Adding a new feature flag to the Webiny system. Use this skill when creating a new feature flag (simple boolean or nested group), gating a feature at the config/admin/API level, or wiring a flag into the WCP license system. Covers IFeatureFlagsDto, KnownFeatureFlag, Zod schema,

SKILL.md

add-feature-flag.SKILL.md
name: webiny-add-feature-flag
description: >
  Adding a new feature flag to the Webiny system. Use this skill when creating a new
  feature flag (simple boolean or nested group), gating a feature at the config/admin/API
  level, or wiring a flag into the WCP license system. Covers IFeatureFlagsDto, KnownFeatureFlag,
  Zod schema, FeatureFlag.CanUse components, useFeatureFlags().isEnabled(), the API FeatureFlags
  abstraction, toDto(), and the LICENSE_CHECKS decorator pattern.

Adding a New Feature Flag

A WCP license is required for feature flags to work. The license is the gate; the config is the switch within the gate.

Decision Flow

1. No license at all             → false (everything off, config ignored)
2. License blocks the flag       → false (config ignored)
3. License allows + config=false → false (config can disable what license allows)
4. License allows + config=true  → true
5. License allows + config unset → true  (license is the authority for unset flags)
6. Not in LICENSE_CHECKS + license exists + config unset → true
7. Not in LICENSE_CHECKS + license exists + config=false → false

Key points:

  • Config can **disable** what the license allows, but cannot **enable** what the license blocks.
  • Flags not governed by a license (`LICENSE_CHECKS`) still require a license to exist — then config decides.
  • Without any license, all flags are off regardless of config.

Architecture

  • **`FeatureFlags` class** (`packages/feature-flags/src/FeatureFlags.ts`) — single `isEnabled(name)` method resolves dot-path strings against the DTO. Flags are disabled by default (`undefined → false`). Also provides `isExplicitlyDisabled(name)` to distinguish "not set" from "set to false".
  • **`IFeatureFlagsDto`** (`packages/feature-flags/src/types.ts`) — the typed DTO interface.
  • **`KnownFeatureFlag`** (`packages/feature-flags/src/FeatureFlags.ts`) — string literal union for autocomplete.
  • **Zod schema** (`packages/project/src/extensions/FeatureFlags.tsx`) — validates the config input.
  • **`toDto()`** returns the fully resolved state (all flags explicitly set), used by the `featureFlags` GraphQL query.
  • **License decorators** intercept `isEnabled()` and apply the decision flow above via a `LICENSE_CHECKS` map.

Steps to Add a Simple Boolean Flag

1. Add to DTO type

**File:** `packages/feature-flags/src/types.ts`

Add the new flag to `IFeatureFlagsDto`:

export interface IFeatureFlagsDto {
  // ... existing flags
  myNewFeature?: boolean;
}

2. Add to KnownFeatureFlag union

**File:** `packages/feature-flags/src/FeatureFlags.ts`

Add the string to the `KnownFeatureFlag` type:

export type KnownFeatureFlag =
  // ... existing flags
  "myNewFeature";

3. Add to toDto()

**File:** `packages/feature-flags/src/FeatureFlags.ts`

Add the flag to the `toDto()` method so the API returns it:

toDto() {
    return {
        // ... existing flags
        myNewFeature: this.isEnabled("myNewFeature")
    };
}

4. Add to Zod schema

**File:** `packages/project/src/extensions/FeatureFlags.tsx`

Add to the `paramsSchema` so users get validation in `webiny.config.tsx`:

myNewFeature: z.boolean().optional();

5. Gate the feature

At the **config level** (controls whether extensions mount at build time):

// In the extension component (e.g., MyFeature.tsx)
import { FeatureFlag } from "@webiny/project";

export const MyFeature = () => (
    <FeatureFlag.CanUse name="myNewFeature">
        <Api.Extension src={...} />
        <Admin.Extension src={...} />
    </FeatureFlag.CanUse>
);

Or add a named convenience component in `packages/project/src/components/FeatureFlag.tsx`:

function CanUseMyNewFeature({ children }: { children: React.ReactNode }) {
  return <CanUse name="myNewFeature">{children}</CanUse>;
}

At the **admin runtime level** (controls UI visibility):

import { useFeatureFlags } from "@webiny/app-admin";

const featureFlags = useFeatureFlags();
if (!featureFlags.isEnabled("myNewFeature")) {
  return null;
}

At the **API runtime level** (controls backend behavior):

import { FeatureFlags } from "~/features/featureFlags/abstractions.js";

// In a DI-resolved class:
constructor(private featureFlags: FeatureFlags.Interface) {}

someMethod() {
    if (!this.featureFlags.get().isEnabled("myNewFeature")) {
        return;
    }
}

6. User configuration

Users configure flags in `webiny.config.tsx`:

export const FeatureFlags = () => (
  <Project.FeatureFlags
    features={{
      myNewFeature: false // disabled
    }}
  />
);

Omitting a flag means the license decides (enabled if licensed, disabled if not). Setting a flag to `false` disables it even if the license allows it.

Adding a Nested Flag Group

For flags with sub-options (like `aiPowerups` or `advancedAccessControlLayer`):

DTO type — use a union:

export interface IMyFeatureOptions {
  subFeatureA?: boolean;
  subFeatureB?: boolean;
}

export interface IFeatureFlagsDto {
  myFeature?: boolean | IMyFeatureOptions;
}

KnownFeatureFlag — add parent and children:

export type KnownFeatureFlag = "myFeature" | "myFeature.subFeatureA" | "myFeature.subFeatureB";

toDto() — collapse parent when disabled:

myFeature: this.isEnabled("myFeature")
  ? {
      subFeatureA: this.isEnabled("myFeature.subFeatureA"),
      subFeatureB: this.isEnabled("myFeature.subFeatureB")
    }
  : false;

Zod schema — union type:

myFeature: z.union([
  z.boolean(),
  z.object({
    subFeatureA: z.boolean().optional(),
    subFeatureB: z.boolean().optional()
  })
]).optional();

User config:

// Disable entirely
<Project.FeatureFlags features={{ myFeature: false }} />

// Disable specific sub-feature
<Project.FeatureFlags features={{ myFeature: { subFeatureA: false } }} />

WCP License Gating

A WCP license is required for any feature flag to work. Without a license,

Read more
Ships withwebiny-js

Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.

Get the whole plugin
Stats
8,048
Stars
682
Forks
Active
Maintenance
TypeScript
Language
2h ago
Last commit
8y ago
Created
9h ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.