webiny-add-feature-flag
GitHub指导在 Webiny 系统中添加新功能标志,涵盖 DTO 类型、已知标志联合、toDto 方法、Zod 验证及许可证装饰器模式,实现配置与权限的门控逻辑。
Trigger Scenarios
Install
npx skills add webiny/webiny-js --skill webiny-add-feature-flag -g -y
SKILL.md
Frontmatter
{
"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
FeatureFlagsclass (packages/feature-flags/src/FeatureFlags.ts) — singleisEnabled(name)method resolves dot-path strings against the DTO. Flags are disabled by default (undefined → false). Also providesisExplicitlyDisabled(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 thefeatureFlagsGraphQL query.- License decorators intercept
isEnabled()and apply the decision flow above via aLICENSE_CHECKSmap.
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, all flags return false.
Flags NOT in LICENSE_CHECKS (like remoteComponents): a license must exist, but the license doesn't explicitly govern this flag. Config decides. Do NOT add a flag to LICENSE_CHECKS until the WCP backend supports it.
Flags IN LICENSE_CHECKS: the license explicitly gates the feature. If the license blocks it, the flag is false regardless of config. If the license allows it, config can still disable it.
To make a flag license-governed, add it to the LICENSE_CHECKS map in all three decorators:
- API level:
packages/api-core/src/features/featureFlags/decorators/FeatureFlagsWithLicenseDecorator.ts - Build level:
packages/project/src/decorators/GetFeatureFlagsWithLicense.ts - Config level:
packages/project/src/services/GetProjectConfigService/LicenseDecoratedFeatureFlags.ts
const LICENSE_CHECKS: Record<string, (license: ILicense) => boolean> = {
// ... existing checks
myNewFeature: l => l.canUseMyNewFeature()
};
This also requires adding canUseMyNewFeature() to the ILicense interface and its implementations in @webiny/wcp (License.ts, NullLicense.ts). Only do this when the WCP backend supports the flag.
Files Reference
| Purpose | File |
|---|---|
| DTO type | packages/feature-flags/src/types.ts |
| FeatureFlags class + KnownFeatureFlag | packages/feature-flags/src/FeatureFlags.ts |
| Zod schema | packages/project/src/extensions/FeatureFlags.tsx |
| Config-level CanUse components | packages/project/src/components/FeatureFlag.tsx |
| Admin hook | packages/app-admin/src/presentation/featureFlags/useFeatureFlags.ts |
| API abstraction | packages/api-core/src/features/featureFlags/abstractions.ts |
| API license decorator | packages/api-core/src/features/featureFlags/decorators/FeatureFlagsWithLicenseDecorator.ts |
| Build license decorator | packages/project/src/decorators/GetFeatureFlagsWithLicense.ts |
| Config license decorator | packages/project/src/services/GetProjectConfigService/LicenseDecoratedFeatureFlags.ts |
| GraphQL query | packages/api-core/src/graphql/featureFlags/FeatureFlagsSchemaFactory.ts |
Version History
- 80eb1c5 Current 2026-08-20 10:06


