Skip to main content

core.features

core.features declares behavior that switches itself on when a condition becomes true and off when it stops being true. It is how an addon reacts to optionalDependencies without writing its own bookkeeping.

The enabled flags are announced as one record under core-feature/flags — core.features.flags is the Announcement — so other addons can read them with ctx.feature() or core.features.of().

Import​

import { core } from '@bedrock-core/server';
import type { FeatureSpec, FeatureConditionContext, FeatureFlags, TypedFeatureAccessor } from '@bedrock-core/server';

Usage​

core.features.add('leaderboard-sync', {
condition: ctx => ctx.registry.has('drav0011_leaderboard'),
onEnable() { startSyncingScores(); },
onDisable() { stopSyncingScores(); },
});

The condition is evaluated immediately when you call add(), and re-evaluated on every registry change and every mirror change. onEnable / onDisable are edge-triggered — they run only when the result flips.

FeatureSpec​

interface FeatureSpec {
condition(ctx: FeatureConditionContext): boolean;
onEnable(): void;
onDisable(): void;
}

All three members are required. A feature that has nothing to tear down still needs an onDisable — write a no-op rather than omitting it.

condition receives a context object, not the registry

The callback's single argument is a FeatureConditionContext, so registry lookups read ctx.registry.has(...). The context has no has method of its own — a bare r => r.has(...) can still compile and then throws at runtime.

// ✅ correct
condition: ctx => ctx.registry.has('drav0011_leaderboard')

// ❌ wrong — ctx is not the Registry
condition: r => r.has('drav0011_leaderboard')
Conditions must be cheap and pure

Because a condition re-runs on every registry and mirror change — and mirror changes include every announcement and shared write in the world — it must be a fast predicate with no side effects. Do the work in onEnable / onDisable, never in condition.

FeatureConditionContext​

interface FeatureConditionContext {
registry: Registry;
state: State;
feature(addonId: string, featureId: string): boolean;
}
MemberUse it for
ctx.registryPeer presence — the full Registry API.
ctx.stateThe raw State mirror, for a value no typed surface covers (ctx.state.get(ns, key)).
ctx.featureAnother addon's feature flag, read from its announced record.

Another addon's feature flag​

core.features.add('cross-pvp', {
condition: ctx =>
ctx.registry.has('drav0011_pvp')
&& ctx.feature('drav0011_pvp', 'arena-mode'),
onEnable() { hookArenaRewards(); },
onDisable() { unhookArenaRewards(); },
});

A peer's shared value​

const shop = core.shared.of<ShopShared>('drav0011_shop');

core.features.add('shop-integration', {
condition: () => shop?.open.get() === true,
onEnable() { showShopButton(); },
onDisable() { hideShopButton(); },
});

The condition is re-run on every mirror change, so a core.shared read inside it is enough — no subscription needed.

API​

add​

core.features.add(id: string, spec: FeatureSpec): void

Declare a feature. Evaluated immediately, then on every registry or mirror change. Adding the same id twice replaces the previous spec and re-evaluates from a disabled baseline.

isEnabled​

core.features.isEnabled(id: string): boolean

Whether one of your own features is currently on. Unknown ids return false.

of​

core.features.of<T extends string = string>(addonId: string): TypedFeatureAccessor<T>

interface TypedFeatureAccessor<T extends string> {
isEnabled(id: T): boolean;
}

A typed accessor for reading another addon's feature flags. Reads are synchronous, from the record it announced — no RPC.

type ShopFeatures = 'discount-mode' | 'leaderboard-sync';

const shop = core.features.of<ShopFeatures>('drav0011_shop');

shop.isEnabled('discount-mode'); // ✅ type-checked
shop.isEnabled('unknown-feature'); // ❌ TS error

Omit the type parameter for untyped access (isEnabled(id: string)). An addon that is offline, or that never declared the feature, reads as false.

flags​

core.features.flags: Announcement<FeatureFlags>

type FeatureFlags = Record<string, boolean>;

Every addon's flags as one record each, the Announcement behind of() and ctx.feature(). Republished whole whenever one of this addon's features flips; a feature that has never been enabled is absent from it.