SkillAgentSearch skills...

Feature

Etsy's Feature flagging API used for operational rampups and A/B testing.

Install / Use

/learn @etsy/Feature
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Feature is no longer actively maintained and is no longer in sync with the version used internally at Etsy.

Feature API

Etsy's Feature flagging API used for operational rampups and A/B testing.

The Feature API is how we selectively enable and disable features at a very fine grain as well as enabling features for a percentage of users for operational ramp-ups and for A/B tests. A feature can be completely enabled, completely disabled, or something in between and can comprise a number of related variants.

For features that are not completely enabled or disabled, we log every time we check whether a feature is enabled and include the result, including what variant was selected, in the events we fire.

The two main API entry points are:

Feature::isEnabled('my_feature')

which returns true when my_feature is enabled and, for multi-variant features:

Feature::variant('my_feature')

which returns the name of the particular variant which should be used.

The single argument to each of these methods is the name of the feature to test.

A typical use of Feature::isEnabled for a single-variant feature would look something like this:

if (Feature::isEnabled('my_feature')) {
    // do stuff
}

For a multi-variant feature, within the block guarded by the Feature::isEnabled check, we can determine the appropriate code to run for each variant with something like this:

if (Feature::isEnabled('my_feature')) {

    switch (Feature::variant('my_feature')) {
      case 'foo':
          // do stuff appropriate for the foo variant
          break;
      case 'bar':
          // do stuff appropriate for the bar variant
          break;
    }
}

It is an error (and will be logged as such) to ask for the variant of a feature that is not enabled. So the calls to variant should always be guarded by an Feature::isEnabled check.

The API also provides two other pairs of methods that will be used much less frequently:

Feature::isEnabledFor('my_feature', $user)

Feature::variantFor('my_feature', $user)

and

Feature::isEnabledBucketingBy('my_feature', $bucketingID)

Feature::variantBucketingBy('my_feature', $bucketingID)

These methods exist only to support a couple very specific use-cases: when we want to enable or disable a feature based not on the user making the request but on some other user or when we want to bucket a percentage of executions based on something entirely other than a user.) The canonical case for the former, at Etsy, is if we wanted to change something about how we deal with listings and instead of enabling the feature for only some users but for all listings those users see, but instead we want to enable it for all users but for only some of the listings. Then we could use isEnabledFor and variantFor and pass in the user object representing the owner of the listing. That would also allow us to enable the feature for specific listing owners. The bucketingBy methods serve a similar purpose except when there either is no relevant user or where we don't want to always put the same user in the same bucket. Thus if we wanted to enable a certain feature for 10% of all listings displayed, independent of both the user making the request and the user who owned the listing, we could use isEnabledBucketingBy with the listing id as the bucketing ID.

In general it is much more likely you want to use the plain old isEnabled and variant methods.

For Smarty templates, where static methods can’t readily be called, there is an object, $feature, wired up in Tpl.php that exposes the same four methods as the Feature API but as instance methods, for instance:

{% if $feature->isEnabled("my_feature") %}

Configuration cookbook

There are a number of common configurations so before I explain the complete syntax of the feature configuration stanzas, here are some of the more common cases along with the most concise way to write the configuration.

A totally enabled feature:

$server_config['foo'] = 'on';

A totally disabled feature:

$server_config['foo'] = 'off';

Feature with winning variant turned on for everyone

$server_config['foo'] = 'blue_background';

Feature enabled only for admins:

$server_config['foo'] = array('admin' => 'on');

Single-variant feature ramped up to 1% of users.

$server_config['foo'] = array('enabled' => 1);

Multi-variant feature ramped up to 1% of users for each variant.

$server_config['foo'] = array(
   'enabled' => array(
       'blue_background'   => 1,
       'orange_background' => 1,
       'pink_background'   => 1,
   ),
);

Enabled for a single specific user.

$server_config['foo'] = array('users' => 'fred');

Enabled for a few specific users.

$server_config['foo'] = array(
   'users' => array('fred', 'barney', 'wilma', 'betty'),
);

Enabled for a specific group

$server_config['foo'] = array('groups' => 1234);

Enabled for 10% of regular users and all admin.

$server_config['foo'] = array(
   'enabled' => 10,
   'admin' => 'on',
);

Feature ramped up to 1% of requests, bucketing at random rather than by user

$server_config['foo'] = array(
   'enabled' => 1,
   'bucketing' => 'random',
);

Single-variant feature in 50/50 A/B test

$server_config['foo'] = array('enabled' => 50);

Multi-variant feature in A/B test with 20% of users seeing each variant (and 40% left in control group).

$server_config['foo'] = array(
   'enabled' => array(
       'blue_background'   => 20,
       'orange_background' => 20,
       'pink_background'   => 20,
   ),
);

New feature intended only to be enabled by adding ?features=foo to a URL

$server_config['foo'] = array('enabled' => 0);

This is kind of a funny edge case. It could also be written:

$server_config['foo'] = array();

since a missing 'enabled' is defaulted to 0.

Configuration details

Each feature’s config stanza controls when the feature is enabled and what variant should be used when it is.

Leaving aside a few shorthands that will be explained in a moment, the value of a feature config stanza is an array with a number of special keys, the most important of which is 'enabled'.

In its full form, the value of the 'enabled' property is either the string 'off', meaning the feature is entirely disabled, any other string, meaning the named variant is enabled for all requests, or an array whose keys are names of variants and whose values are the percentage of requests that should see each variant.

As a shorthand to support the common case of a feature with only one variant, 'enabled' can also be specified as a percentage from 0 to 100 which is equivalent to specifying an array with the variant name 'on' and the given percentage.

The next four most important properties of a feature config stanza specify a particular variant that special classes of users should see: 'admin', 'internal', 'users', and 'groups'.

The 'admin' and 'internal' properties, if present, should name a variant that should be shown for all admin users or all internal requests. For single-variant features this name will almost always be 'on'. (Technically you could also specify 'off' to turn off a feature for admin users or internal requests that would be otherwise enabled. But that would be weird.) For multi-variant features it can be any of the variants mentioned in the 'enabled' array.

The 'users' and 'groups' variants provide a mapping from variant names to lists of users or numeric group ids. In the fully specified case, the value will be an array whose keys are the names of variants and whose values are lists of user names or group ids, as appropriate. As a shorthand, if the list of user names or group ids is a single element it can be specified with just the name or id. And as a further shorthand, in the configuration of a single-variant feature, the value of the 'users' or 'groups' property can simply be the value that should be assigned to the 'on' variant. So using both shorthands, these are equivalent:

$server_config['foo'] => array('users' => array('on' => array('fred')));

and:

$server_config['foo'] => array('users' => 'fred');

None of these four properties have any effect if 'enabled' is a string since in those cases the feature is considered either entirely enabled or disabled. They can, however, enable a variant of a feature if no 'enabled' value is provided or if the variant’s percentage is 0.

On the other hand, when an array 'enabled' value is specified, as an aid to detecting typos, the variant names used in the 'admin', 'internal', 'users', and 'groups' properties must also be keys in the 'enabled' array. So if any variants are specified via 'enabled', they should all be, even if their percentage is set to 0.

The two remaining feature config properties are 'bucketing' and 'public_url_override'. Bucketing specifies how users are bucketed when a feature is enabled for only a percentage of users. The default value, 'uaid', causes bucketing via the UAID cookie which means a user will be in the same bucket regardless of whether they are signed in or not.

The bucketing value 'user', causes bucketing to be based on the signed-in user id. Currently we fall back to bucketing by UAID if the user is not signed in but this is problematic since it means that a user can switch buckets if they sign in or out. (We may change the behavior of this bucketing scheme to simply disable the feature for users who are not signed in.)

Finally the bucketing value 'random', causes each request to be bucketed independently meaning that the same user will be in different buckets on different requests. This is typically used for features that should have no user-visible effects but where we want to ramp up somethin

Related Skills

View on GitHub
GitHub Stars870
CategoryDevelopment
Updated1mo ago
Forks65

Languages

PHP

Security Score

95/100

Audited on Feb 15, 2026

No findings