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

# Define flags in code

> Author feature flags in shipd.config.ts and sync them with shipd push.

## Overview

Flags (toggles) live in `shipd.config.ts`. You review the file in a PR, then `shipd push` applies it. There is no lockfile — the server is the remote, CAS versions prevent clobbering.

This guide follows Maya adding Fieldkit's checkout and search flags.

## The file

```ts shipd.config.ts theme={null}
import { defineFlags } from "shipdit/config";

export default defineFlags({
  "new-checkout": {
    type: "boolean",
    default: false,
    description: "Redesigned checkout flow",
    tags: ["checkout"],
  },
  "search-variant": {
    type: "variant",
    variants: ["control", "semantic", "hybrid"] as const,
    default: "control",
    description: "Search ranking experiment",
    tags: ["search", "experiment"],
  },
  "max-retries": {
    type: "number",
    default: 3,
    description: "Max retries for outbound API calls",
  },
  "checkout-payload": {
    type: "json",
    default: { mode: "strict", showPromo: true },
    description: "Checkout UI payload",
  },
});
```

<Accordion title="What we run on useshipd.com">
  ```ts packages/flags/shipd.config.ts theme={null}
  import { defineFlags } from "shipdit/config";

  export default defineFlags({
    "rules-editor": {
      type: "variant",
      variants: ["v1", "v2"] as const,
      default: "v1",
      description: "Flag rules editor on the flag page",
    },
    "audit-log": {
      type: "boolean",
      default: false,
      description: "Hide audit log ui on organization settings page",
      tags: ["ui", "audit", "enterprise"],
    },
  });
  ```
</Accordion>

## Types of flags

| `type`    | Default           | Read with                   |
| --------- | ----------------- | --------------------------- |
| `boolean` | `true` / `false`  | `isEnabled` / `useFlag`     |
| `variant` | one of `variants` | `getVariant` / `useVariant` |
| `number`  | number            | `getNumber` / `useNumber`   |
| `json`    | any JSON value    | `getJson` / `useJson`       |

Variant `default` must be a member of `variants` — TypeScript rejects `"search-variant": { default: "v3" }`.

Optional on every flag: `description`, `tags`, `deprecated: true` (lifecycle `deprecated`).

## Sync

```bash theme={null}
shipd diff --project web-app --env production
shipd push --project web-app --env production
```

```text theme={null}
Plan:
  + create flag "new-checkout"
  + create flag "search-variant"
  + create flag "max-retries"
  + create flag "checkout-payload"
```

Confirm unless you pass `--yes` or `--ci` (`CI=1` / `SHIPD_CI=1`).

Then typegen writes `src/generated/flags.ts` (override with `.shipd/config.json` → `typesOut`, or `shipd types gen -o`).

## Pull vs push

| Command           | What it does                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| `shipd pull`      | Overwrites `shipd.config.ts` from the server **and** runs typegen. Comments are not preserved. |
| `shipd push`      | Applies local definitions, then typegen.                                                       |
| `shipd diff`      | Prints the plan. `--exit-code` → `2` on drift.                                                 |
| `shipd types gen` | Typegen only.                                                                                  |

<Warning>
  `shipd pull` is a full rewrite. Commit first.
</Warning>

## What is global vs per environment

| Field                                        | Scope                             |
| -------------------------------------------- | --------------------------------- |
| type, variants, tags, lifecycle, description | Project-global (all environments) |
| default / fallthrough                        | The `--env` you target            |

Definition creates/updates/archives require `flag.production.toggle`. Default-only edits on non-production use `flag.state.update`.

`managedBy` (`code` or `dashboard`) is last-touch attribution, not a lock. `--adopt` is a deprecated no-op.

## Remotes you did not list

`shipd push` leaves remote-only flags alone. `--prune` archives them (never hard-deletes).

```bash theme={null}
shipd push --prune --yes
```

Stale versions return **409** (exit `3`). `shipd pull` then push, or `--force` (audited).

## Next

* [Evaluate flags](/guides/evaluate-flags)
* How-to: [boolean](/how-to/create-a-boolean-flag) · [variant](/how-to/create-a-variant-flag) · [number and JSON](/how-to/create-number-and-json-flags)
* [Config reference](/reference/config) · [CLI reference](/reference/cli)
