> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nonefivem.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature Flags

> Read and manage feature flags with the NoCloud Node.js SDK

A feature flag is a [named, typed value](/cloud/offers/feature-flags). You read it and decide what to do with it.

```typescript theme={null}
if (await cloud.flags.isEnabled("new-hud")) {
  showNewHud();
}

const maxPlayers = await cloud.flags.getNumber("max-players", 32);
```

## How reads work

Reading a flag serves the configuration from memory when it was fetched within the cache window, and goes to the API otherwise — so a read is usually free, and never more than one request per window no matter how often you call it.

<Note>
  The SDK does **not** poll in the background. The refresh happens on the read
  that finds the cache stale, and concurrent reads on a cold cache collapse onto
  a single request.
</Note>

Configure the window when you construct the client:

```typescript theme={null}
const cloud = new NoCloud({
  apiKey: "your-api-key",
  flagsCacheTtlSeconds: 30 // default: 10
});
```

If a refresh fails while a previous configuration is held, that configuration is returned rather than throwing — an API blip must never change the values a running server reads. The cache window restarts in that case too, so an outage costs one request per window instead of one per read.

## Reading values

### Typed readers

Each reader checks the flag's declared type as well as its value, so asking for a number and getting a string is impossible.

```typescript theme={null}
const enabled = await cloud.flags.getBoolean("new-hud", false);
const motd = await cloud.flags.getString("motd", "Welcome");
const maxPlayers = await cloud.flags.getNumber("max-players", 32);
const economy = await cloud.flags.getJson<EconomyConfig>("economy");
```

Without a fallback the return type is `T | undefined`; with one it is `T`.

<Warning>
  A flag that does not exist — or that holds a different type than you asked for,
  or that this runtime may not read — reads as the fallback, or `undefined` when
  you did not pass one. Archiving or deleting a flag in the dashboard can never
  throw on a running server.
</Warning>

### isEnabled

The shorthand for boolean flags. Defaults to `false` rather than `undefined`.

```typescript theme={null}
if (await cloud.flags.isEnabled("new-hud")) {
  showNewHud();
}

// With an explicit fallback
if (await cloud.flags.isEnabled("maintenance-mode", true)) {
  showMaintenanceScreen();
}
```

### Raw and bulk reads

```typescript theme={null}
// Raw value, whatever the type
const value = await cloud.flags.getValue("max-players");

// A flag whole: key, type, value and runtime
const flag = await cloud.flags.getFlag("max-players");

// Every readable flag, whole
const flags = await cloud.flags.getFlags();

// Every readable flag as a plain key/value object
const all = await cloud.flags.getAll();
// { "new-hud": true, "motd": "Welcome", ... }
```

## Runtimes

Reads are made on behalf of a **runtime**, and default to `shared`. A `server` flag is invisible to a `shared` read — it behaves exactly like a flag that does not exist — so a value you are about to send to a player can never be a server-only one by accident.

```typescript theme={null}
// Default: shared — a server-only flag reads as undefined
await cloud.flags.getString("webhook-url"); // undefined

// Say you are the server, and you see everything
await cloud.flags.getString("webhook-url", undefined, { runtime: "server" });
await cloud.flags.isEnabled("god-mode", false, { runtime: "server" });

// Exactly what is safe to relay to a player
const forClient = await cloud.flags.getAll(); // shared flags only
```

<Tip>
  The runtime goes in the options object, which is always the **last** argument —
  after the fallback. Pass `undefined` as the fallback when you want a runtime
  but no fallback.
</Tip>

## Configuration control

<Accordion title="getConfig">
  Returns the flag configuration, from memory when it is still within the cache window and from the API otherwise. This is what every read calls under the hood.

  ```typescript theme={null}
  const config = await cloud.flags.getConfig();

  console.log(config.etag); // "a1b2c3d4e5f6a7b8"
  console.log(config.generatedAt); // "2026-09-19T12:00:00.000Z"
  console.log(config.pollIntervalSeconds); // 20
  console.log(config.flags); // FlagConfigEntry[]
  ```

  **Returns**

  * `Promise<FlagConfigPayload>`

  **Throws**

  * `NoCloudAPIError` — only if the request fails and no configuration is held yet. When one is held, it is returned instead.
</Accordion>

<Accordion title="refresh">
  Fetches the configuration from the API, ignoring the cache window. Unlike `getConfig` this always surfaces a failure, so it is the right call when you want to know whether the API is reachable.

  ```typescript theme={null}
  try {
    await cloud.flags.refresh();
  } catch (error) {
    console.error("Flag API unreachable:", error);
  }
  ```

  **Returns**

  * `Promise<FlagConfigPayload>`

  **Throws**

  * `NoCloudAPIError` — if the API request fails.
</Accordion>

<Accordion title="getCachedConfig">
  Returns the configuration currently held in memory without contacting the API. Synchronous.

  ```typescript theme={null}
  const config = cloud.flags.getCachedConfig();

  if (config === null) {
    console.log("Nothing fetched yet");
  }
  ```

  **Returns**

  * `FlagConfigPayload | null`
</Accordion>

<Accordion title="clearCache">
  Discards the cached configuration so the next read goes back to the API.

  ```typescript theme={null}
  cloud.flags.clearCache();
  ```

  The management methods (`create`, `update`, `delete`) call this for you, so a read straight after a write never serves the value you just replaced.
</Accordion>

### Driving your own refresh loop

`pollIntervalSeconds` on the payload is what the API advises. The SDK does not act on it, so use it if you want a loop of your own:

```typescript theme={null}
async function watchFlags() {
  for (;;) {
    const config = await cloud.flags.refresh();
    await new Promise((resolve) =>
      setTimeout(resolve, config.pollIntervalSeconds * 1000)
    );
  }
}
```

## Managing flags

These methods hit the management endpoints directly and require `FLAGS_WRITE` (except `list`, `get`, `getQuota` and `getAuditLog`, which need only `FLAGS_READ`).

<Accordion title="list">
  Lists the organization's flags.

  ```typescript theme={null}
  const { results, quota, total, page, totalPages } = await cloud.flags.list({
    page: 1,
    limit: 20,
    search: "hud",
    includeArchived: false
  });
  ```

  **Parameters**

  * options: `ListFlagsOptions` — `page`, `limit`, `search`, `includeArchived` (all optional)

  **Returns**

  * `Promise<FeatureFlagListResponse>` — a page of flags plus your flag allowance
</Accordion>

<Accordion title="create">
  Creates a flag. The type and value are paired in TypeScript, so the compiler rejects a `number` flag holding a string.

  ```typescript theme={null}
  const flag = await cloud.flags.create({
    key: "new-hud",
    name: "New HUD",
    description: "Rolls out the redesigned heads-up display",
    type: "boolean",
    value: true,
    runtime: "shared"
  });

  // A secret must be created as server-only
  await cloud.flags.create({
    key: "webhook-url",
    name: "Alert webhook",
    type: "string",
    value: "https://discord.com/api/webhooks/...",
    runtime: "server"
  });
  ```

  **Returns**

  * `Promise<FeatureFlag>`

  **Throws**

  * `NoCloudAPIError` — including when the organization is at its flag allowance, or the key is already taken.
</Accordion>

<Accordion title="get">
  Fetches a single flag by its ID.

  ```typescript theme={null}
  const flag = await cloud.flags.get(flagId);
  ```

  <Note>
    To read a flag's *value* by key, use `getValue` and the typed readers — they
    serve from the cached configuration instead of making a request per flag.
  </Note>

  **Returns**

  * `Promise<FeatureFlag>`
</Accordion>

<Accordion title="update">
  Renames, archives, or sets a flag's value.

  ```typescript theme={null}
  // Flip a boolean
  await cloud.flags.update(flagId, { type: "boolean", value: false });

  // Archive — servers stop seeing it, the key stays reserved
  await cloud.flags.update(flagId, { archived: true });

  // Close a flag to clients
  await cloud.flags.update(flagId, { runtime: "server" });

  // Rename
  await cloud.flags.update(flagId, { name: "New HUD (beta)" });
  ```

  <Warning>
    Changing the value means passing its type alongside it. Keys are immutable and
    cannot be updated.
  </Warning>

  **Returns**

  * `Promise<FeatureFlag>`

  **Throws**

  * `NoCloudAPIError` — including when the flag is locked by the organization's allowance.
</Accordion>

<Accordion title="delete">
  Permanently deletes a flag.

  ```typescript theme={null}
  await cloud.flags.delete(flagId);
  ```

  **Returns**

  * `Promise<void>`
</Accordion>

<Accordion title="getQuota">
  Fetches the organization's flag allowance.

  ```typescript theme={null}
  const quota = await cloud.flags.getQuota();

  console.log(`${quota.used} / ${quota.max} flags used`);

  if (quota.locked > 0) {
    console.warn(`${quota.locked} flag(s) locked — subscribe to edit them`);
  }
  ```

  **Returns**

  * `Promise<FeatureFlagQuota>` — `max`, `used`, `subscribed`, `locked`
</Accordion>

<Accordion title="getAuditLog">
  Fetches the flag audit log — who changed what, and when.

  ```typescript theme={null}
  const { results } = await cloud.flags.getAuditLog({
    page: 1,
    limit: 20,
    flagId // optional: one flag's history
  });

  for (const entry of results) {
    console.log(entry.createdAt, entry.actorType, entry.action, entry.flagKey);
  }
  ```

  **Returns**

  * `Promise<PaginatedResult<FeatureFlagAuditEntry>>` — newest first
</Accordion>

## Types

```typescript theme={null}
type FeatureFlagType = "boolean" | "string" | "number" | "json";
type FeatureFlagRuntime = "server" | "shared";

interface FlagConfigEntry {
  key: string;
  type: FeatureFlagType;
  value: JsonValue;
  runtime: FeatureFlagRuntime;
}

interface FlagConfigPayload {
  etag: string;
  generatedAt: string;
  pollIntervalSeconds: number;
  flags: FlagConfigEntry[];
}

interface FeatureFlag {
  id: string;
  key: string;
  name: string;
  description: string | null;
  type: FeatureFlagType;
  value: JsonValue;
  runtime: FeatureFlagRuntime;
  archived: boolean;
  locked: boolean;
  createdAt: string;
  updatedAt: string;
}

interface FeatureFlagQuota {
  max: number;
  used: number;
  subscribed: boolean;
  locked: number;
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Feature Flags" icon="flag" href="/cloud/offers/feature-flags">
    Types, runtimes, limits and the audit log
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/cloud/sdks/nodejs/error-handling">
    Handle `NoCloudAPIError` gracefully
  </Card>

  <Card title="API Reference" icon="book-open" href="/cloud/api-reference/flags/config">
    The REST endpoints behind this module
  </Card>
</CardGroup>
