{"id":14957487,"url":"https://github.com/timmikeladze/next-flag","last_synced_at":"2025-10-01T17:30:50.841Z","repository":{"id":241207090,"uuid":"803466794","full_name":"TimMikeladze/next-flag","owner":"TimMikeladze","description":"🏁 Feature flags powered by GitHub issues and NextJS. Toggle the features of your app by ticking a checkbox in a GitHub issue. Supports server-side rendering, multiple environments, custom conditions, and can be deployed as a stand-alone feature flag server.","archived":false,"fork":false,"pushed_at":"2024-10-01T23:38:36.000Z","size":10923,"stargazers_count":37,"open_issues_count":8,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-29T23:08:14.661Z","etag":null,"topics":["feature-flag","feature-flags","feature-flags-github","feature-flags-next","feature-flags-next-js","feature-flags-react","github-feature-flags","next-feature-flags","next-flag","next-flags","nextjs-feature-flags","nextjs-flags","react-feature-flags"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TimMikeladze.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-20T19:23:55.000Z","updated_at":"2024-09-03T22:52:13.000Z","dependencies_parsed_at":"2024-11-12T16:01:33.197Z","dependency_job_id":"1e173378-c8ed-4cc3-ab7a-add3edb3a015","html_url":"https://github.com/TimMikeladze/next-flag","commit_stats":{"total_commits":50,"total_committers":2,"mean_commits":25.0,"dds":0.06000000000000005,"last_synced_commit":"81d5b55a4844fecd3dac258df708d991171a7109"},"previous_names":["timmikeladze/next-flag"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-flag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-flag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-flag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-flag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TimMikeladze","download_url":"https://codeload.github.com/TimMikeladze/next-flag/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234883309,"owners_count":18901366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feature-flag","feature-flags","feature-flags-github","feature-flags-next","feature-flags-next-js","feature-flags-react","github-feature-flags","next-feature-flags","next-flag","next-flags","nextjs-feature-flags","nextjs-flags","react-feature-flags"],"created_at":"2024-09-24T13:14:58.379Z","updated_at":"2025-10-01T17:30:49.998Z","avatar_url":"https://github.com/TimMikeladze.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🏁 next-flag\n\nFeature flags powered by GitHub issues and NextJS. Toggle the features of your app without deploying a new version by ticking a checkbox in the body of a GitHub issue.\n\n## ✨ Features\n\n- [x] Enable or disable features by ticking a checkbox in a GitHub issue.\n- [x] Define feature flags across multiple environments or branches.\n- [x] Supports React Server Side and Client Side Components. Powered by the NextJS Cache.\n- [x] Define custom conditions that are evaluated at runtime to enable or disable features.\n- [x] Can be deployed as a stand-alone service to manage feature flags for multiple NextJS apps.\n\n\u003e Check-out a fully working [NextJS example](./examples/nextjs-example/) or jump to [Getting started](https://github.com/TimMikeladze/next-flag?tab=readme-ov-file#-getting-started).\n\n## 📡 Install\n\n```console\nnpm install next-flag\n\nyarn add next-flag\n\npnpm add next-flag\n```\n\n\u003e 👋 Hello there! Follow me [@linesofcode](https://twitter.com/linesofcode) or visit [linesofcode.dev](https://linesofcode.dev) for more cool projects like this one.\n\n## 🏗️ Architecture\n\n![architecture](./docs/architecture.png)\n\n## 🎥 Demo\n\nhttps://github.com/TimMikeladze/next-flag/assets/702718/d1707ae0-f9cf-4f80-a20f-6a6f9f715dc2\n\n## 🚀 Getting started\n\n### 📄 Create a new issue\n\nFirst, create a new issue in your repository with the following format. It is optional to include a list of environments that the feature should be enabled in.\n\n```markdown\n# 🏁 Feature Flags\n\n## WIP feature\n\n- [x] Enabled\n\n## New feature\n\n- [x] Enabled\n\n### Production\n\n- [ ] Enabled\n\n### Preview\n\n- [ ] Enabled\n\n### Development\n\n- [ ] Enabled\n```\n\n### 🐙 Setup GitHub\n\nNow let's get an auth token from GitHub and create a Webhook.\n\n1. [Create a new personal access token](https://github.com/settings/tokens?type=beta) in GitHub with **Read access to issues and metadata**.\n2. Create a GitHub Webhook by navigating to `https://github.com/\u003cOWNER\u003e/\u003cREPO\u003e/settings/hooks/new`\n   - Set the **Payload URL** to `https://\u003cYOUR_DOMAIN\u003e/api/next-flag`. Hint: Use [ngrok](https://ngrok.com/) for local development.\n   - Set the **Content type** to `application/json`\n   - Set the **Secret** to a random string\n   - Select the **Issues** event.\n3. Add the GitHub token and webhook secret to the `.env` file of your NextJS app.\n\n```bash\nNEXT_FLAG_GITHUB_TOKEN=\"\"\nNEXT_FLAG_WEBHOOK_SECRET=\"\"\n```\n\n### 💻 Configure your NextJS app\n\nFinally, let's write some code to use the `next-flag` package.\n\n```ts\n// src/app/api/next-flag/index.ts\nimport { NextFlag } from 'next-flag';\nimport { revalidateTag, unstable_cache } from 'next/cache';\n\nexport const nf = new NextFlag({\n  paths: [\n    {\n      repository: '\u003cOWNER\u003e/\u003cREPO\u003e',\n      issue: 123,\n    },\n  ],\n  cache: {\n    revalidateTag,\n    unstable_cache,\n  },\n});\n```\n\nNext, create a new API route to handle the incoming Webhook requests.\n\n```ts\n// src/app/api/next-flag/route.ts\nimport { NextRequest } from 'next/server';\n\nimport { nf } from '.';\n\nexport const POST = (req: NextRequest) =\u003e nf.POST(req);\n\nexport const GET = (req: NextRequest) =\u003e nf.GET(req);\n```\n\nYou can now use the `nf` instance to check if a feature is enabled in your app.\n\nThis can be done in a React Server Component:\n\n```ts\n// src/app/page.tsx\n'use server';\n\nimport { nf } from './api/next-flag';\n\nexport default async function Page() {\n  const wipFeatureEnabled = await nf.isFeatureEnabled('wip-feature');\n\n  return wipFeatureEnabled \u0026\u0026 \u003cdiv\u003eWIP feature enabled!\u003c/div\u003e;\n}\n```\n\nOr in a React Client Component:\n\n```tsx\n// src/app/components/Feature.tsx\n'use client';\n\nimport { useNextFlag } from 'next-flag/react';\n\nexport const Feature = () =\u003e {\n  const nf = useNextFlag();\n\n  if (nf.loading) {\n    return null;\n  }\n\n  const wipFeatureEnabled = nf.isFeatureEnabled('wip-feature');\n\n  return wipFeatureEnabled \u0026\u0026 \u003cdiv\u003eWIP feature enabled!\u003c/div\u003e;\n};\n```\n\nYou can also wrap your client side app with the `NextFlagProvider` to fetch features once on mount and provide them to child components when using the `useNextFlag` hook.\n\n```tsx\n// src/app/components/Feature.tsx\n'use client';\n\nimport { NextFlagProvider, useNextFlag } from 'next-flag/react';\n\nconst ContextProvider = () =\u003e {\n  return (\n    \u003cNextFlagProvider\u003e\n      \u003cComponent /\u003e\n    \u003c/NextFlagProvider\u003e\n  );\n};\n\nconst Component = () =\u003e {\n  const nf = useNextFlag();\n  const gettingStarted = nf.isFeatureEnabled('getting-started');\n  return (\n    \u003c\u003e\n      {gettingStarted \u0026\u0026 (\n        \u003cp\u003e\n          Get started by editing\u0026nbsp;\n          \u003ccode className={styles.code}\u003esrc/app/page.tsx\u003c/code\u003e\n        \u003c/p\u003e\n      )}\n    \u003c/\u003e\n  );\n};\n```\n\n## 💪 Advanced Usage\n\n### 🚦 Conditions\n\nEach feature flag can have a list of conditions that must be met for the feature to be enabled. Conditions are defined as a list of expressions that are evaluated at runtime. If any of the expressions return `false`, the feature will be disabled.\n\nTo get started, add a `#### Conditions` subheading to the feature issue and list the conditions as a series of checkboxes. If all conditions are met, the feature will be enabled. If a condition checkbox is unchecked, it will be ignored during evaluation. In other words, if a condition checkbox is not checked, it will not affect the feature flag.\n\n```markdown\n# 🏁 Feature Flags\n\n## My feature\n\n- [x] Enabled\n\n#### Conditions\n\n- [ ] Only if admin\n```\n\nNow define how the condition is evaluated during runtime.\n\n1. Define a `requestToContext` function that takes a request object and returns a context object. The context object is passed to the condition functions.\n2. For each path, define a condition function that takes the context object and returns a boolean.\n\nThe `requestToContext` is a good place to extract information from the request object that is needed to evaluate the conditions. For example, you can extract cookies or headers from the request object to determine if a user is signed in.\n\n\u003e ❗ Important: The `requestToContext` function is only called when communicating with the `NextFlag` API over HTTP. If you are using the `NextFlag` directly in a server-side component, you must build the context object yourself and pass it to the `isFeatureEnabled` method directly.\n\n```ts\n// src/app/api/next-flag/index.ts\nimport { NextFlag } from 'next-flag';\nimport { revalidateTag, unstable_cache } from 'next/cache';\n\nexport const nf = new NextFlag({\n  paths: [\n    {\n      repository: 'TimMikeladze/next-flag',\n      issue: 3,\n      conditions: {\n        'only-if-admin': (context) =\u003e context.isAdmin,\n      },\n    },\n  ],\n  async requestToContext(req) {\n    return {\n      isAdmin: false,\n    };\n  },\n  cache: {\n    revalidateTag,\n    unstable_cache,\n  },\n});\n```\n\n```ts\n// src/app/page.tsx\n'use server';\n\nimport { nf } from './api/next-flag';\n\nexport default async function Page() {\n  const wipFeatureEnabled = await nf.isFeatureEnabled('wip-feature', {\n    context: {\n      isAdmin: true,\n    }\n  });\n\n  return wipFeatureEnabled \u0026\u0026 \u003cdiv\u003eWIP feature enabled!\u003c/div\u003e;\n}\n```\n\nWhen using `next-flag` in a server-side component, you can also pass an `and` or `or` async function to the `isFeatureEnabled` method options to define extra in-line conditions that must be met for the feature to be enabled.\n\n```ts\n// src/app/page.tsx\n'use server';\n\nexport default async function Page() {\n  const wipFeatureEnabled = await nf.isFeatureEnabled('wip-feature', {\n    and: () =\u003e true,\n  });\n\n  return wipFeatureEnabled \u0026\u0026 \u003cdiv\u003eWIP feature enabled!\u003c/div\u003e;\n}\n\n```\n\n### 🏝️ Multiple environments or branches\n\nBy default `next-flag` will try to read `process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NEXT_PUBLIC_ENV || process.env.NEXT_PUBLIC_STAGE || process.env.VERCEL_ENV || process.env.ENV || process.env.STAGE || process.env.NODE_ENV` to determine the current environment.\n\nYou can customize how the current environment is determined during runtime by passing a `getEnvironment` function to the `NextFlag` constructor.\n\nTo associate a feature with a specific environment, add a subheading to the feature issue with the name of the environment (case-insensitive).\n\n- When using multiple environments, the top-level feature flag will control whether the feature is enabled or disabled.\n- If the top-level feature flag is disabled, the feature will be disabled in all environments.\n- If the top-level feature flag is enabled, then the environment-specific flags will determine whether the feature is enabled.\n\n```markdown\n# 🏁 Feature Flags\n\n## My feature\n\n- [x] Enabled\n\n### Production\n\n- [ ] Enabled\n\n### Preview\n\n- [ ] Enabled\n\n### Development\n\n- [ ] Enabled\n```\n\n### ✅ Getting all features\n\nYou can always get all features by calling the `getFeatures` method. You can also open the `/api/next-flag` route in your browser to see the enabled features as a JSON array.\n\n```ts\nimport { nf } from './api/next-flag';\nimport { getFeatures, isFeatureEnabled } from 'next-flag/client';\n\n// server side\nconst features = await nf.getFeatures();\n// or client side with an HTTP request\nconst features = await getFeatures();\n// check if a feature is enabled\nconst wipFeatureEnabled = await isFeatureEnabled('wip-feature');\n```\n\n### 📦 Deploying a stand-alone next-flag server\n\nYou can deploy the `next-flag` server as a separate NextJS app and use it as a feature flagging service for multiple NextJS apps.\n\n1. Follow the steps above to setup GitHub and create a new NextJS app.\n2. When initializing the `NextFlag` instance, pass multiple projects to the `paths` option and set `standalone` to `true`.\n3. Deploy this NextJS app somewhere...\n4. In a different NextJS app:\n   1. Configure the `.env` file with a `NEXT_PUBLIC_NEXT_FLAG_PROJECT` and `NEXT_PUBLIC_NEXT_FLAG_ENDPOINT`.\n   2. Use `isFeatureEnabled` from the `next-flag/client` package to check if a feature is enabled in a React Server Component.\n   3. Use the `useNextFlag` hook from the `next-flag/react` package to check if a feature is enabled in a React Client Component.\n\n```bash\nNEXT_PUBLIC_NEXT_FLAG_PROJECT=\"project-1\"\nNEXT_PUBLIC_NEXT_FLAG_ENDPOINT=\"https://\u003cYOUR_DOMAIN\u003e/api/next-flag\"\n```\n\n```ts\n// src/app/api/next-flag/index.ts\nimport { NextFlag } from 'next-flag';\nimport { revalidateTag, unstable_cache } from 'next/cache';\n\nexport const nf = new NextFlag({\n  standalone: true,\n  paths: [\n    {\n      project: 'project-1',\n      repository: '\u003cOWNER\u003e/\u003cREPO\u003e',\n      issue: 123,\n    },\n    {\n      project: 'project-2',\n      repository: '\u003cOWNER\u003e/\u003cREPO\u003e',\n      issue: 124,\n    },\n  ],\n  cache: {\n    revalidateTag,\n    unstable_cache,\n  },\n});\n```\n\nWhen running in stand-alone mode with multiple projects, you can pass the `project` option to the `isFeatureEnabled` method to check if a feature is enabled in a specific project.\n\nYou can pass an `environment` option to the `isFeatureEnabled` method to check if a feature is enabled in a specific environment.\n\nThese options will override the default values pulled from the environment variables.\n\n```ts\nimport { isFeatureEnabled, getFeatures } from 'next-flag/client';\n\nawait isFeatureEnabled('wip-feature', {\n  project: 'project-1',\n  environment: 'development',\n});\n```\n\n### ⛔️ Usage without a Webhook\n\nUsing a Github Webhook is optional, but highly recommended. The webhook is responsible for invalidating the NextJS Cache. Without this mechanism, caching of feature flags will be disabled and the feature flags will be fetched on every request.\n\nIf you don't want to use a Webhook simply omit the `NEXT_FLAG_WEBHOOK_SECRET` from the `.env` file.\n\n## 📚 TSDoc\n\n\u003c!-- TSDOC_START --\u003e\n\n## :toolbox: Functions\n\n- [getFeatures](#gear-getfeatures)\n- [isFeatureEnabled](#gear-isfeatureenabled)\n- [useNextFlag](#gear-usenextflag)\n- [NextFlagProvider](#gear-nextflagprovider)\n\n### :gear: getFeatures\n\n| Function      | Type                                                |\n| ------------- | --------------------------------------------------- |\n| `getFeatures` | `(props?: GetFeaturesArgs) =\u003e Promise\u003cGetFeatures\u003e` |\n\n### :gear: isFeatureEnabled\n\n| Function           | Type                                                                                   |\n| ------------------ | -------------------------------------------------------------------------------------- |\n| `isFeatureEnabled` | `(feature: string or string[], options?: IsFeatureEnabledOptions) =\u003e Promise\u003cboolean\u003e` |\n\n### :gear: useNextFlag\n\n| Function      | Type                                                                                                                                                                    |\n| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `useNextFlag` | `(props?: UseNextFlagHookProps) =\u003e { loading: boolean; features: GetFeatures; error: Error or undefined; isFeatureEnabled: (feature: string or string[]) =\u003e boolean; }` |\n\n### :gear: NextFlagProvider\n\n| Function           | Type                                                |\n| ------------------ | --------------------------------------------------- |\n| `NextFlagProvider` | `(props: NextFlagProviderProps) =\u003e Element or null` |\n\n## :wrench: Constants\n\n- [NextFlagContext](#gear-nextflagcontext)\n\n### :gear: NextFlagContext\n\n| Constant          | Type                                |\n| ----------------- | ----------------------------------- |\n| `NextFlagContext` | `Context\u003cGetFeatures or undefined\u003e` |\n\n## :factory: NextFlag\n\n### Methods\n\n- [GET](#gear-get)\n- [isFeatureEnabled](#gear-isfeatureenabled)\n- [getFeatures](#gear-getfeatures)\n- [POST](#gear-post)\n\n#### :gear: GET\n\n| Method | Type                                                       |\n| ------ | ---------------------------------------------------------- |\n| `GET`  | `(req: NextRequest) =\u003e Promise\u003cNextResponse\u003cGetFeatures\u003e\u003e` |\n\n#### :gear: isFeatureEnabled\n\n| Method             | Type                                                                                                                                                                                                                                                                        |\n| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `isFeatureEnabled` | `(feature: string or string[], options?: { and?: (() =\u003e boolean or Promise\u003cboolean\u003e) or undefined; context?: Context or undefined; environment?: string or undefined; or?: (() =\u003e boolean or Promise\u003c...\u003e) or undefined; project?: string or undefined; }) =\u003e Promise\u003c...\u003e` |\n\n#### :gear: getFeatures\n\n| Method        | Type                                                                                                                                        |\n| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `getFeatures` | `(options?: { context?: Context or undefined; environment?: string or undefined; project?: string or undefined; }) =\u003e Promise\u003cGetFeatures\u003e` |\n\n#### :gear: POST\n\n| Method | Type                                                                                                     |\n| ------ | -------------------------------------------------------------------------------------------------------- |\n| `POST` | `(req: NextRequest) =\u003e Promise\u003cNextResponse\u003c{ error: string; }\u003e or NextResponse\u003c{ success: boolean; }\u003e\u003e` |\n\n\u003c!-- TSDOC_END --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimmikeladze%2Fnext-flag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimmikeladze%2Fnext-flag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimmikeladze%2Fnext-flag/lists"}