24.07.2026・TechStuff
24.07.2026・TechStuff

Shopware 6 Hidden Gems #4: The feature flag system can do way more than FEATURE_ALL=1

Fabian Blechschmidt

Most Shopware developers know exactly two things about feature flags: there is a feature.yaml, and you can put FEATURE_ALL=1 in your .env to see what breaks. That was my level of knowledge too, until I read Feature.php and FeatureFlagRegistry.php top to bottom for this series. Turns out there is a whole feature management system in there — with database persistence, a REST API, events and a Twig tag. Almost none of it is documented outside of a couple of ADRs.

Let’s go through it, from „nice to know“ to „wait, what?“.

FEATURE_ALL=major

FEATURE_ALL with any truthy value activates all minor flags. But the string major is special-cased (Framework/Feature.php:114-131): it additionally activates the major flags — the breaking-change ones like V6_8_0_0.

# activate all minor flags (experimental features)
FEATURE_ALL=1

# additionally activate major/breaking flags — hello, future
FEATURE_ALL=major

That second line is a one-env-var way to run your CI against next-major behavior today. Individual flags still win over FEATURE_ALL, so you can exclude one flag while enabling everything else:

FEATURE_ALL=major
V6_8_0_0=0

What’s actually in the box (6.7.0.0)

bin/console feature:list shows what’s registered. On a stock 6.7 that includes some interesting disabled minor flags, all declared in Framework/Resources/config/packages/feature.yaml:

FlagWhat it gates
PERFORMANCE_TWEAKSAlternative algorithms in promotion/cart calculation (PromotionCalculator.php:268, CartScopeDiscountPackager.php:52)
FLOW_EXECUTION_AFTER_BUSINESS_PROCESSFlows execute after the main unit of work instead of inline
TELEMETRY_METRICSOpenTelemetry metrics support

Each entry has major, toggleable and default properties — and toggleable is where it gets interesting.

Flags can be toggled at runtime — and persisted in the database

This one genuinely surprised me. FeatureFlagRegistry (Framework/Feature/FeatureFlagRegistry.php) merges the static flags from YAML with flags stored in the key-value storage (app_config table, key feature.flags). Toggling goes through enable()/disable() and:

The whole mechanism sits behind shopware.feature_toggle.enable — which defaults to true. This is not an opt-in experiment; it’s on in your shop right now.

There is a REST API for it

Framework/Api/Controller/FeatureFlagController.php:

GET  /api/_action/feature-flag                     # list all flags + state
POST /api/_action/feature-flag/enable/{feature}
POST /api/_action/feature-flag/disable/{feature}

All three require the ACL privilege api_feature_flag_toggle. So: gradual rollout of an experimental feature in production, from your deployment pipeline, without touching .env, without a restart. We have built similar switches in the past for Magento 1 — Shopware ships a small version of it and tells nobody.

# try the performance tweaks on the live shop, revert if the numbers look bad
curl -X POST https://shop.example/api/_action/feature-flag/enable/PERFORMANCE_TWEAKS \
  -H "Authorization: Bearer $TOKEN"

The supporting cast

A few smaller pieces that make the system rounder than expected:

{% sw_silent_feature_call %} — a Twig tag (Framework/Adapter/Twig/TokenParser/FeatureFlagCallTokenParser.php) that wraps template code and suppresses deprecation warnings while a flag is inactive:

{% sw_silent_feature_call "v6.8.0.0" %}
    {# legacy template code that would spam deprecations #}
{% endsw_silent_feature_call %}

Symfony profiler integrationcore/Profiling/FeatureFlag/FeatureFlagProfiler.php puts all flags with state and description into the debug toolbar. No console round-trip to check „is the flag on in this request?“.

Test helpersFeature::fake() swaps the entire flag registry for a closure and restores it afterwards; the PHPUnit attribute #[DisabledFeatures(['v6.8.0.0'])] (from core/Test/Annotation) disables flags per test with automatic cleanup.

Flexible namingFeature::normalizeName() means v6.8.0.0, V6_8_0_0 and v6_8_0_0 are all the same flag. One less way to shoot yourself in the foot.

Custom flags for your own project

Since flags are just config, your project (or plugin config) can declare its own:

# config/packages/feature.yaml
shopware:
  feature:
    flags:
      - name: ACME_NEW_CHECKOUT
        default: false
        major: false
        toggleable: true
        description: "New checkout flow, rollout Q3"

Now your code gates on Feature::isActive('ACME_NEW_CHECKOUT'), QA enables it per API call on staging, and the rollout on production is one POST request — reversible with another one. In dev mode, checking an unregistered flag even triggers a warning (Feature.php:102-107), which catches typos before they become „why is the feature never active“.

TL;DR

The feature flag system is a real feature management tool: FEATURE_ALL=major for next-major CI runs, toggleable: true flags persist runtime toggles in the database (major flags are protected), GET/POST /api/_action/feature-flag/* manages them remotely behind the api_feature_flag_toggle ACL, and there’s a Twig tag, profiler panel and test attributes to go with it. Documented in two ADRs and zero guides.

Next up: turning down the noise — the four undocumented shopware.logger.* options that decide what lands in your logs.

If you’re using flags for rollouts already, I’d love to hear how — drop me an email!


Found in Shopware 6.7.0.0: Framework/Feature.php:114-131 (FEATURE_ALL), Framework/Feature/FeatureFlagRegistry.php:36-105 (persistence + events), Framework/Api/Controller/FeatureFlagController.php:26-46 (REST API), Framework/DependencyInjection/Configuration.php:793-795 (feature_toggle.enable, default true), Framework/Adapter/Twig/TokenParser/FeatureFlagCallTokenParser.php (Twig tag).