🐀 Canary
user_dfc4deba…

Feature flags for SvelteKit

Flip features,
not deploys.

Canary resolves every flag on the server, renders the answer into the page, and re-evaluates the moment something changes. Everything on this page is live and running against your visitor ID.

GitHub β†’

This page's accent colour is an A/B test. You got variant-a. Roll a new identity in the bar above to try your luck again.

Live flags evaluated on the server
  • ON
    new-dashboard Feature enabled
  • ON
    gradual-rollout Feature enabled
  • OFF
    beta-checkout User not in required group
  • OFF
    experimental-feature User not in required group
  • ON
    ab-test-homepage Feature enabled with variant
    variant-a

Flip the beta and internal switches in the bar and watch this change. No reload, one request.

01 Β· The basics

Wrap it. Ship it.

A flag is one component. The decision is made on the server and rendered into the HTML, so there is no loading spinner and no flash of the wrong thing.

new-dashboard

✨ New Dashboard Enabled

This card came down in the server-rendered HTML. View source and you'll find it.

+page.svelte
<Feature flag="new-dashboard">
	<NewDashboard />

	{#snippet fallback()}
		<ClassicDashboard />
	{/snippet}
</Feature>

02 Β· Percentage rollouts

Start at 1%. Sleep at night.

Your visitor ID is hashed with the flag's name into a bucket between 0 and 100. You are in as soon as the rollout passes your bucket, and you stay in as it grows.

You are in bucket 35.7, so at 50% you would be IN

gradual-rollout Β· really set to 50%

🎲 You're in the rollout

The server put you in the half of visitors who get this feature.

features.config.json
{
	"features": {
		"gradual-rollout": {
			"enabled": true,
			"rollout": 50
		}
	}
}

03 Β· User groups

Let the right people in early.

List userGroups on a flag and only those groups get it, even while it is switched off for everyone else. Perfect for a beta programme, or for testing in production before launch.

beta-checkout

πŸ”’ Beta Checkout Locked

Only the beta and internal groups get the new checkout.

experimental-feature Β· enabled: false

πŸ”’ Experimental Feature

Disabled globally. The internal group can still get in.

features.config.json
{
	"features": {
		"beta-checkout": {
			"enabled": true,
			"userGroups": ["beta", "internal"]
		},
		"experimental-feature": {
			"enabled": false,
			"userGroups": ["internal"]
		}
	}
}
anywhere in your app
import { joinGroup, getUserInfo, checkFeature } from 'sveltekit-canary';

// One round trip: the cookie is set and every
// feature on the page comes back re-evaluated
await joinGroup('beta').updates(getUserInfo, checkFeature);

04 Β· A/B testing

Same visitor, same variant. Always.

Variants are assigned by hashing, not by rolling dice on each request, so nobody sees the page change under them. The split below is 3 variants across 3,000 simulated visitors.

control 33.0%
variant-a 32.9% you
variant-b 34.1%
ab-test-homepage Β· variant-a

The blue hero. Same words, cooler palette. Does calm convert better?

+page.svelte
{#snippet control()}<ClassicHero />{/snippet}
{#snippet variantA()}<BlueHero />{/snippet}
{#snippet variantB()}<VioletHero />{/snippet}

<Feature
	flag="ab-test-homepage"
	variants={{
		control,
		'variant-a': variantA,
		'variant-b': variantB
	}}
/>

05 Β· Route redirects

Send people to the right version.

One server hook redirects requests before a page ever renders. This is where each route would send you right now. Change your groups and the destinations change with them.

Click a route to try it for real.

features.config.json
{
	"routes": {
		"/dashboard": {
			"feature": "new-dashboard",
			"enabled": "/dashboard-v2",
			"disabled": "/dashboard-v1"
		},
		"/checkout": { "beta": "/checkout-beta" },
		"/checkout-beta": {
			"requiresGroup": "beta",
			"disabled": "/checkout"
		},
		"/admin": { "requiresGroup": "internal" }
	}
}

Get started

Three steps. Then go flip something.

  1. Install

    terminal
    npm install sveltekit-canary
  2. Describe your flags

    src/features.config.json
    {
    	"features": {
    		"gradual-rollout": {
    			"enabled": true,
    			"rollout": 50
    		}
    	}
    }
  3. Add the hook

    src/hooks.server.ts
    import { initCanary, handleFeatureRouting } from 'sveltekit-canary';
    import { sequence } from '@sveltejs/kit/hooks';
    import config from './features.config.json';
    
    initCanary({ config });
    
    export const handle = sequence(handleFeatureRouting);

Read the docs on GitHub β†’

🐀 Canary · MIT licensed · built with SvelteKit remote functions