In Laravel Pennant, how do activate(), deactivate(), activateForEveryone() and purge() change stored flag values, and when would you run pennant:purge?
answer
- activate() writes one scope's value
- forget() deletes one scope's row
- ForEveryone updates stored rows only
- purge() deletes, next check re-resolves
- --except and --except-registered
basics
~20 sactivate() and deactivate() store a value for one scope, forget() deletes it, and activateForEveryone() overwrites every stored row for a feature. purge() deletes a feature's rows so the next check re-runs its definition; pennant:purge does that from a deploy.
solid answer
~40 s`Feature::for($user)->activate('new-checkout')` stores `true` for that scope, and `activate('purchase-button', 'seafoam-green')` stores a rich value; `deactivate()` stores `false`; `forget()` deletes the row so the definition runs again next time. `Feature::activateForEveryone('new-checkout')` runs one UPDATE on every **stored** row for the feature, so users never checked yet still get the definition, which is why the docs say to update the definition too. `Feature::purge('new-checkout')` deletes all stored values, so everyone re-resolves on the next check, which re-draws lotteries. `php artisan pennant:purge new-checkout` does the same from a deploy script, `--except=` keeps named features, and `--except-registered` purges everything not defined in the app, which cleans out removed flags.
code
php · 12 lines<?php
use Laravel\Pennant\Feature;
// A support agent opts one customer into the new checkout
Feature::for($customer)->activate('new-checkout');
// Rollout succeeded: flip every stored answer to true
Feature::activateForEveryone('new-checkout');
// The old experiment was deleted from the code: drop its rows
Feature::purge('checkout-button-colour');go deeper
Recall that activate() and deactivate() switch a flag for one user, and purge() clears stored values so the flag is decided again.
Explain that bulk updates touch stored rows only, what forget() and purge() delete, and the pennant:purge options for deploy scripts.
Plan rollout completion and cleanup without re-rolling users unexpectedly, and use Pennant's events to audit flag changes.
Set the team's lifecycle for flags, from rollout to full release to deletion, and decide who may change stored values in production.
## Stored values are the state Pennant stores each feature's first resolved value per scope. After that, the **stored value is the flag**; the definition is only consulted for scopes that have no stored value. Every update method below works on that stored state, which is the key to predicting their effect. ## Per-scope changes | Call | Effect | |---|---| | `Feature::for($user)->activate('new-checkout')` | stores `true` for that user | | `Feature::activate('purchase-button', 'seafoam-green')` | stores a rich value for the default scope | | `Feature::for($user->team)->deactivate('billing-v2')` | stores `false` for that team | | `Feature::for($user)->forget('new-checkout')` | deletes the stored value; the next check runs the definition again | `for()` accepts several scopes at once, so `Feature::for($betaTesters)->activate('new-checkout')` opts a whole collection in. Without `for()`, these calls act on the default scope, usually the authenticated user, which makes an 'opt into the beta' button a one-liner. ## Bulk changes that touch stored rows only `Feature::activateForEveryone('new-checkout')` and `deactivateForEveryone('new-checkout')` call the driver's `setForAllScopes()`. On the database store that is a single `UPDATE features SET value = ... WHERE name = 'new-checkout'`: - every user **already resolved** gets the new value; - a user **never checked** has no row, so the definition still decides for them; - the docs therefore say to change the definition as well, for example to return `true`, when you mean 'everyone'. A common sequence when the 10% rollout of the new checkout goes well: 1. change the definition to `fn () => true`; 2. deploy; 3. run `Feature::activateForEveryone('new-checkout')` so users drawn `false` earlier flip too, or purge so everyone re-resolves against the new definition. ## Purging `Feature::purge('new-checkout')` deletes every stored value for that feature; `purge(['a', 'b'])` deletes several; `purge()` with no argument deletes the whole table. After a purge, each scope re-runs the definition on its next check. That is right when: - the definition changed and you want everyone re-evaluated; - a feature has been removed from the code and its rows are dead weight. It is risky mid-rollout: a purged `Lottery` feature is re-drawn, so roughly 90% of the users who had the new checkout lose it and a different 10% gain it. ## pennant:purge in deploys The Artisan command, alias `pennant:clear`, exposes the same operation for scripts: 1. `php artisan pennant:purge new-checkout` purges named features; 2. `php artisan pennant:purge --except=new-checkout --except=purchase-button` purges everything except those; 3. `php artisan pennant:purge --except-registered` purges everything **not defined** in the running app, the tidy-up after deleting flags from the code; 4. `--store=array` targets a store other than the default. ## Events for auditing Every change dispatches an event: `FeatureUpdated` for one scope, `FeatureUpdatedForAllScopes` for bulk updates, `FeatureDeleted` for `forget()`, and `FeaturesPurged` or `AllFeaturesPurged` for purges. Listening to them is a cheap audit trail of who changed which flag, which matters once support staff can toggle flags for customers. ## Interview traps - **"`activateForEveryone()` turns it on for all users."** Only for users with a stored value. - **"Purging is harmless."** It re-rolls lotteries and changes what users see. - **"`deactivate()` deletes the row."** It stores `false`; `forget()` deletes. ## Where these calls usually live Per-scope `activate()` and `deactivate()` calls typically sit behind an admin screen or a support tool, so an agent can put one customer on the new checkout. Bulk updates and purges usually belong in deploy scripts or one-off Artisan commands, where they run once and are reviewed like a migration. Keeping them out of request code avoids a stray page view rewriting thousands of rows.
- After Laravel Pennant's activateForEveryone('new-checkout'), a user who signed up today still gets the old checkout. Why?`activateForEveryone()` updates only rows that already exist in `features`. The new user had never been checked, so there was no row; their first check ran the definition, which still returned the 10% lottery. Change the definition to return `true` as well.
- What is the difference between deactivate() and forget() in Laravel Pennant?`deactivate()` stores `false` for the scope, so the feature stays off for that user regardless of the definition. `forget()` deletes the stored value, so the next check runs the definition again and may resolve it on.
saying these in an interview costs you the question
- activateForEveryone() also covers users who were never checked.
- purge() switches a feature off for everyone permanently.
- Purging a Lottery feature keeps the same users in the rollout.
- deactivate() deletes the stored row for the scope.
- pennant:purge --except-registered deletes the features you still define.