Django-waffle lets you sprinkle feature flags throughout your Django application, allowing you to enable or disable features dynamically without redeploying code.
Let’s see it in action. Imagine you have a new "beta_dashboard" feature you want to roll out gradually.
# views.py
from django.shortcuts import render
from waffle import flag_is_active
def dashboard_view(request):
if flag_is_active(request, 'beta_dashboard'):
return render(request, 'dashboard/beta_dashboard.html')
else:
return render(request, 'dashboard/classic_dashboard.html')
Here’s what’s happening under the hood. When flag_is_active(request, 'beta_dashboard') is called, Waffle checks a few things in order:
- Request-specific overrides: Is there a cookie or GET parameter for this flag that forces it on or off just for this request? This is useful for testing.
- User-specific settings: If the user is logged in, does their profile have a direct override for this flag?
- Group settings: If the user is logged in, are they part of a group that has this flag enabled or disabled?
- Percentage rollout: Is this flag enabled for a certain percentage of all requests (or logged-in users, depending on configuration)? This is your gradual rollout mechanism.
- Superusers: Are superusers supposed to see this flag enabled by default?
- Staff users: Are staff users supposed to see this flag enabled by default?
- Everybody: Is the flag simply enabled for everyone, or disabled for everyone?
The configuration for these checks happens in your settings.py.
# settings.py
WAFFLE_SWITCH_DEFAULT = False # Default for switches (on/off)
WAFFLE_FLAG_DEFAULT = False # Default for flags (percentage rollout, etc.)
WAFFLE_SAMPLE_DEFAULT = 100 # Default percentage for flags
The core problem Waffle solves is decoupling feature deployment from code deployment. You can merge code for a feature behind a flag, deploy that code, and then turn the feature on via the Django admin or API without another deployment. This enables strategies like canary releases, A/B testing, and phased rollouts.
The flag_is_active function is just one piece. Waffle also provides template tags:
{% load waffle %}
{% flag 'beta_dashboard' %}
<p>This is the new beta dashboard content!</p>
{% else %}
<p>This is the old dashboard content.</p>
{% endflag %}
This is functionally equivalent to the view logic, but often more convenient for presentation layer toggles.
To manage your flags, you’ll access the Django admin interface. You can create "Switches" (which are simply on/off for everyone) or "Flags" (which allow for more complex targeting like percentages, users, groups, etc.).
When you create a flag named beta_dashboard, you can then set its criteria. For a 10% rollout, you’d set the "Percentage" to 10.0. If you want to enable it for a specific user, say with username alice, you can add alice to the "Users" list for that flag.
The most surprising thing about Waffle is how its internal evaluation order, from request overrides down to the "Everybody" setting, creates a powerful, layered system for controlling feature visibility. A specific user override, for instance, trumps a percentage rollout for that flag, which in turn trumps the global default. This means you can test a flag on yourself via a cookie, while the rest of the world sees it based on the percentage, and eventually, everyone sees it if you enable it globally.
The "Everyone" setting on a flag is a bit of a misnomer; it’s evaluated last and is effectively a global "on" switch for that flag, but only if all other more specific conditions (user, group, percentage) haven’t already determined the flag’s state.
Once you’ve mastered feature flags, you’ll want to look into A/B testing with Waffle, which builds upon flags to test variations of content or behavior.