Generating Types for Feature Flags
Tags: typescript, featureflags, opensource, devops
Published: 2023-10-23 17:23:53

Feature flags are amazing tools to help you ship your code safely by guarding your end users against incomplete features in production. Many teams use feature flags in this way, to ship code that makes up a larger feature, enabling testing by a group of internal or beta users without having to deploy the code all at once.

This ability to slowly roll out and guard functionality is especially helpful in environments where multiple applications (services) make up the larger end-user experience. Sometimes these services are owned by orthogonal teams, which makes the communication around and planning of feature releases all the more important.

We believe that feature flags give engineers superpowers, allowing them to change application functionality at runtime, without requiring re-deploys. But, as you may have heard, "with great power, comes great responsibility". This ability to modify a running application through the click of a button or external configuration change (see our post on [Flipt for GitOps](https://www.flipt.io/blog/gitops-announcement) ❤️) also introduces risk.

Feature flags introduce a dependency on an external system, which means your application code is no longer guaranteed to work the same in all environments. At [Flipt](https://www.flip.io) we believe that the power and flexibility gained through the use of feature flags in production is worth the risk they introduce, however, we do think there is more we can do to help mitigate this risk and remove some of the sharp edges.

## Flag Identifiers

In Flipt, flag keys are unique (within a namespace) identifiers that are assigned to each feature flag in order to differentiate them from one another. It is important to choose meaningful and descriptive flag keys that accurately represent the functionality they control. This will make it easier for developers and stakeholders to understand and manage the feature flags within the system.

When defining flag keys, we recommend to follow a consistent naming convention. This can help maintain a clear and organized system of flags, especially when dealing with a large number of flags. For example, using a prefix or a specific format for flag keys can provide better context and improve readability.

Because flag keys are simple strings in most feature flag domains, they are extremely flexible and can contain almost any value. However, as most programmers know, strings are also easily mistyped which can lead to errors. In the case of feature flags, these errors include incorrect or non-existent flags being referenced, leading to runtime exceptions or unexpected behavior.

To mitigate the risk associated with mistyped flag keys, one common approach is to use a constant to define and manage flag keys. This can provide type safety and help catch errors at compile time rather than runtime for compiled languages. Using constants for flag keys, however, does have its downsides. Nothing stops multiple constants from being created for the same value, which means there is no guarantee that when searching your code for a specific constant identifier, you will find all references for that flag key. There is also nothing stopping you from defining a constant with an invalid value in the first place.

Also, using constants does not give the end user flexibility to define their own type which can enforce limiting a field being of a specific set of values.

```typescript
interface EvaluationRequest {
  requestId?: string;
  flagKey: string; // we only want a subset of possible values here
  entityId: string;
  context?: Record<string, string | undefined>;
}

let req: EvaluationRequest = {
  flagKey: "foo", // this is allowed!
  entityId: "1234",
};
```

## Adding Type Safety

Recently, a user of Flipt [proposed adding type safety](https://github.com/flipt-io/flipt-node/issues/6) to our [Node/Typescript SDK](https://github.com/flipt-io/flipt-node). After some back and forth and some learning on our part, we concluded that we could leverage some of the pieces we already have in Flipt and create a CLI to generate these types.

When it comes to generating code, you first need a source or specification that can be parsed and then used for writing the code. We realized that for our feature flag data we already have this source, our [features.yml](https://www.flipt.io/docs/configuration/storage#defining-flag-state) file format which we use for [imports/exports](https://www.flipt.io/docs/operations/import-export) and to power our GitOps, Local, and Object Storage data backends!

Again, that same amazing user created a prototype/proof of concept CLI tool that parses the above-mentioned `features.yml` and generates `Flag` and `Context` union types for Typescript. We just open-sourced and published this tool that we’re calling [Typed](https://github.com/flipt-io/typed)!

Usage info is in the README, but here’s how you could quickly generate type-safe Typescript code from your features data in Flipt.

1. Export your feature data from Flipt, either via our API or through a direct DB connection using our [export](https://www.flipt.io/docs/operations/import-export#export) command: `flipt export`
2. Run `npx @flipt-io/typed --lang ts --input /path/to/features.yml` to output the generated Typescript code, or pipe it to a file.
3. Import the types and use in your code!

```typescript
import { Flag } from "./types/flipt";

interface EvaluationRequest {
  requestId?: string;
  flagKey: Flag["key"]; // enforces we can only use valid flag keys that exist in our Flipt instance
  entityId: string;
  context?: Record<string, string | undefined>;
}

let invalid: EvaluationRequest = {
  flagKey: "foo", // this will now error!
  entityId: "1234",
};

let req: EvaluationRequest = {
  flagKey: "language", // correct!
  entityId: "1234",
};
```

Here's what the generated types look like:

```typescript
// Generated by @flipt-io/typed
export type Flag = {
  key: "language";
  value?: "es" | "en" | "fr";
};
```

You can see that the generated types are very simple, and only contain the information that is needed to enforce type safety. The `Flag` type contains a [union string type](https://www.typescriptlang.org/docs/handbook/literal-types.html#string-literal-types) for the flag key and value.

Typed also supports the generation of a `Context` type which is the union of all the possible contexts that are defined in the `features.yml` file. This means that the `Context` type will contain all the possible fields that can be used in the `EvaluationRequest` interface.

## Contributing

[Typed](https://github.com/flipt-io/typed) is still very early days, and considered experimental. There are plenty of features/support we want to add and rough DX edges we want to smooth over, but importantly, we wanted to get it out there and see if anyone finds it useful. We also want to support more languages than Typescript in the future, including but not limited to Go, Java, Rust, Python.. pretty much every language that we have an ‘official’ [SDK](https://www.flipt.io/docs/integration/overview) for.

We’d love it if you gave [Typed](https://github.com/flipt-io/typed) a try and helped us out by filing issues, feature or language requests, or even contributing code. No contribution is too small and we’d love it if the open source community helped drive our roadmap.

In a future post, we’ll discuss another project we’re working on to improve the developer experience of working with feature flags.. so stay tuned!

As always, feel free to reach out on [GitHub](https://github.com/flipt-io/flipt) or come chat in our [Discord](https://flipt.io/discord). We’d love to learn more about your specific workflow when it comes to adding and working with feature flags in your codebases.