django-waffle
A battle-tested feature flipper for Django apps, with flags, switches, and samples for controlled rollouts.
Repository Health
Technical Analysis
Django Waffle is a feature-flag library for Django that gives you three distinct primitives instead of one blunt on/off switch: Flags (per-request conditions based on user, group, staff status, language, or percentage rollout), Switches (simple global on/off toggles), and Samples (statistical percentages unrelated to any particular user or request). Each ships as its own Django model, manager, and cache-backed lookup path, so checks are cheap even at high request volume.
The library plugs into every layer of a Django project: function-based views via @waffle_flag/@waffle_switch decorators, class-based views via WaffleFlagMixin/WaffleSwitchMixin/WaffleSampleMixin, templates via {% flag %}/{% switch %}/{% sample %} tags (and a Jinja2 extension), and JavaScript via an inline waffle.flag_is_active() bridge generated per-request. A WaffleMiddleware persists percentage-rollout decisions to cookies so a user’s bucket doesn’t flip on every request.
Originally built at Mozilla, it has been maintained continuously since 2011 and is now a mature, low-drama dependency: swappable models (WAFFLE_FLAG_MODEL, etc.) let teams subclass Flag/Switch/Sample for custom fields, and admin integration, management commands (waffle_flag, waffle_switch, waffle_sample, waffle_delete), and full test utilities (override_flag, override_switch, override_sample) round out the developer experience for teams doing gradual rollouts, staff-only betas, or A/B tests.
What You Get
- Flag model: per-request activation based on superuser/staff/authenticated status, specific users or groups, request language, or a percentage rollout persisted via cookie
- Switch and Sample models: a simple global boolean toggle and a request-independent statistical percentage, respectively, for cases that don’t need per-user logic
- Integration points for every layer:
@waffle_flag/@waffle_switchdecorators,WaffleFlagMixin/WaffleSwitchMixin/WaffleSampleMixinfor CBVs,{% flag %}/{% switch %}/{% sample %}template tags, a Jinja2 extension, and a JS bridge via{% wafflejs %} - Cache-first reads with automatic invalidation on save/delete, so flag checks don’t hit the database on every request
- Swappable models (
WAFFLE_FLAG_MODEL,WAFFLE_SWITCH_MODEL,WAFFLE_SAMPLE_MODEL) for teams that need custom fields on their flags - Django admin integration,
waffle_flag/waffle_switch/waffle_sample/waffle_deletemanagement commands, and test utilities (override_flag,override_switch,override_sample) for deterministic tests
Common Use Cases
- Gradual percentage-based rollout of a new feature to production traffic, with the bucket decision pinned per-user via cookie
- Staff-only or authenticated-only preview of in-progress features before a public launch
- Simple global kill-switches for expensive or risky code paths that need to be toggled without a deploy
- A/B testing UI or behavior variants at the template or view layer using the same flag primitives
- Language- or group-targeted feature exposure for internationalized or B2B multi-tenant Django apps
Under The Hood
Architecture
The library is organized around a shared BaseModel abstract class (waffle/models.py) that gives Flag, Switch, and Sample a common cache-first get()/get_all() lookup path, with save()/delete() hooked to flush the relevant cache keys on transaction commit. AbstractBaseFlag layers request-scoped evaluation logic on top (everyone-override, testing-cookie override, language match, user/group/staff/superuser match, then percentage rollout), and AbstractUserFlag adds prefetch-friendly user/group membership checks via a dedicated FlagManager. Every consumer-facing surface — decorators, class-based-view mixins, template tags, the JS bridge in views.py, and WaffleMiddleware — funnels through the same flag_is_active()/switch_is_active()/sample_is_active() entry points in waffle/__init__.py, so the model layer is the single source of truth and a change to the caching or evaluation logic there propagates to all integration points at once. Swappable models are resolved dynamically via get_waffle_flag_model() and friends, following Django’s own swappable-model convention.
Tech Stack
Pure Python/Django: pyproject.toml declares django>=4.2 as the only runtime dependency, with the test matrix (via tox.ini) covering Python 3.9-3.13 against Django 4.2, 5.1, and 5.2. Build backend is setuptools with a dynamic version sourced from waffle.__version__. Optional extras add Sphinx for docs. The package ships a py.typed marker and is type-annotated throughout (checked with mypy in strict-ish mode), and uses ruff for linting with a broad rule set (pycodestyle, Pyflakes, flake8-django, Pylint, pyupgrade, and more).
Code Quality
Testing is extensive — over 2,300 lines across dedicated test modules for models, decorators, middleware, mixins, admin, management commands, templates, views, and test utilities themselves, run through a run.sh wrapper and tox across the full Python/Django support matrix in CI (GitHub Actions). A separate typecheck tox environment runs mypy against the whole package (excluding tests), and an i18n environment verifies translation catalogs stay in sync. Error handling favors explicit Django exceptions (ImproperlyConfigured, Http404) over silent failure, and naming/structure follow Django app conventions closely (models, managers, admin, middleware, templatetags).
What Makes It Unique Rather than a single flag concept, Waffle deliberately separates three distinct rollout mechanics — per-user Flags, global Switches, and independent-of-user Samples — and wires all three into every Django integration surface (views, CBVs, templates, JS, admin, management commands) with the same caching and swappable-model machinery. That consistency, plus over a decade of production use originating at Mozilla, is what distinguishes it from lighter or single-purpose flag helpers.