← Toggley Blog

How to add feature flags to a Laravel app

Three ways to add feature flags to Laravel: a config value, a database table, or a flag service. Code for each, plus rollouts, Blade, routes and testing.

  • laravel
  • php
  • feature-flags
  • tutorial

A feature flag lets you deploy code without turning it on. The new checkout, the redesigned dashboard or the rewritten importer ships to production switched off, and you decide later who sees it. If it breaks, you switch it off again instead of rolling back a deploy.

This guide covers three ways to add feature flags to a Laravel app, from the simplest to the most flexible, and where each one stops working.

Option 1: a config value

The quickest flag is a config entry backed by an environment variable.

// config/features.php
return [
    'new_checkout' => env('FEATURE_NEW_CHECKOUT', false),
];
if (config('features.new_checkout')) {
    return view('checkout.new');
}

return view('checkout.legacy');

This is fine for a flag you flip once, such as enabling a feature in staging before production. It has two limits:

  • Changing it means a deploy. In production you probably run php artisan config:cache, so a new .env value does nothing until the config is rebuilt. Your “off switch” is still a deploy.
  • It is all or nothing. Everyone gets the same value. You cannot turn a feature on for your own account, your beta customers or 10% of users.

Option 2: a database table

The next step most teams take is a feature_flags table with a name and an enabled column, plus a small helper that reads it (usually through the cache). Now you can flip a flag without a deploy, from a tinker session or an admin page you build.

This works, and plenty of apps run on it for years. Over time, though, the table tends to grow requirements:

  • Per-user overrides, so you can test in production on your own account.
  • Percentage rollouts, which need consistent hashing so a user does not flip between old and new on every request.
  • Separate values per environment.
  • A record of who changed what, for when something goes wrong at 2 AM.
  • An admin UI that someone other than you can use safely.
  • A way to find flags that are finished and should be deleted from the code.

Each of these is a small project. Together they are a product you maintain alongside your actual product.

Laravel also has a first-party package for feature flags, Laravel Pennant, which gives you a clean API for defining and checking features and stores their values for you. It ships with a database driver (the default) and an in-memory array driver, and you can write your own. You can do a simple percentage rollout by returning a Lottery from a feature definition, and Pennant stores each user’s result after the first check. Its documentation does not describe a dashboard, so turning features on and off for people outside the code is up to you. It is worth a look if you want to keep everything inside your app and are happy to build the admin side yourself.

Option 3: a hosted flag service

The third option is to keep the flag definitions and the switches outside your app, in a service with a dashboard, and have your app read them through an SDK. The rest of this guide uses Toggley’s Laravel SDK, since that is the one we build, but the pattern is the same for any service of this kind.

Install and configure

composer require albrightlabs/toggley-php

Add your environment’s server key to .env:

TOGGLEY_SERVER_KEY=tgly_srv_...

The SDK requires PHP 8.1 or newer and supports Laravel 10, 11 and 12 out of the box.

Check a flag

use Toggley\Laravel\Facades\Toggley;

if (Toggley::isEnabled('new-checkout')) {
    // on for the signed-in user
}

$theme = Toggley::string('theme', 'light');
$limit = Toggley::number('upload-limit-mb', 10);

Flags are not only booleans. A text flag can choose a theme and a number flag can set a limit, and you change the value from the dashboard.

Use it in Blade

@toggley('new-checkout')
    <x-new-checkout />
@else
    <x-checkout />
@endtoggley

Gate a route

Route::get('/beta', BetaController::class)->middleware('toggley:beta-dashboard'); // 404 when off

While the flag is off, the route returns a 404, so an unreleased page is not reachable by guessing its URL.

Send user attributes for targeting

With no context argument, the signed-in user’s ID is used as the context key. That key is what keeps percentage rollouts consistent: the same user gets the same answer on every request.

To target by attributes such as email or plan, publish the config file (php artisan vendor:publish --tag=toggley-config) and set a context resolver:

'context_resolver' => fn ($user) => $user ? ['key' => (string) $user->id, 'email' => $user->email, 'plan' => $user->plan] : null,

Now you can write rules in the dashboard like “plan is one of pro” or “email ends with @yourcompany.com”, or roll a flag out to 10% of users. These attributes are used inside your app to evaluate the flag and are not sent to Toggley.

Test it

In your tests, fake the flags you need:

Toggley::fake(['new-checkout' => true]);

There are also Artisan commands: toggley:check shows the connection and current values, and toggley:sync refreshes the cached config immediately.

What happens on each request

A fair question about any flag service is whether it adds a network call to every page. With this SDK, it does not.

  • The flag configuration is stored in your Laravel cache, and flags are evaluated in your PHP process.
  • When the cached config is older than the poll interval (30 seconds by default), the SDK checks for changes after the response has been sent, so the user does not wait for it. Only an empty cache makes a request during the page.
  • If the service cannot be reached, the last good config keeps working for up to 7 days.

Use a cache shared by your servers, such as Redis, Memcached or the database, so every server sees the same config and they share the refresh work.

Which option should you use?

  • Config value: one or two flags that change rarely, and you are fine deploying to flip them.
  • Database table or Pennant: you want everything in your app and you are willing to build the rollout, targeting and admin pieces you need.
  • Hosted service: you want percentage rollouts, targeting, per-environment values and a change history without building them.

Whichever you pick, delete flags once a feature is fully released. A flag that has served the same value for months is dead code with an extra if around it.

Try Toggley with your Laravel app

Toggley is the hosted feature flag service from Albright Labs, and the Laravel SDK above is its PHP package. The free plan includes 5 flags, 4 environments and the SDKs, with no credit card. Percentage rollouts, targeting and segments start on the $9/month Basic plan. Every change is recorded with who made it, and Toggley marks stale flags and emails your admins a weekly list, so old flags get removed from your code.

Start toggling free and ship your next feature switched off. If you also want to know when your Laravel app goes down, take a look at Pulsey, our uptime monitoring service.

Ship faster. Roll back in one click.

Turn a bad release off in one click, without a redeploy.

No credit card · 5 flags free