> ## 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.

# Use shipd with AI assistants

> A compact shipd context file for Claude, Cursor, Codex, ChatGPT, and other coding assistants, with the rules and common mistakes they need.

## Overview

Coding assistants know little about shipd. Give them this page and they'll write correct shipd code. It's also available as plain Markdown at `https://docs.useshipd.com/ai.md`.

| Resource | URL | Use it for |
| - | - | - |
| This page | `https://docs.useshipd.com/ai.md` | A compact context file. Paste it into `CLAUDE.md`, `AGENTS.md`, `.cursor/rules`, or a chat. |
| Index | `https://docs.useshipd.com/llms.txt` | A map of every page with one-line summaries. |
| Full docs | `https://docs.useshipd.com/llms-full.txt` | Every page in one file. |
| Any page as Markdown | Append `.md` to its URL | Fetching one page, such as `https://docs.useshipd.com/reference/sdk.md`. Local `mint dev` previews don't serve `.md` URLs; use `/llms.txt` or the page's copy menu there. |
| Control-plane API | `https://api.useshipd.com/api/v1/openapi.json` | OpenAPI 3.1 for the management API. |

The navbar separates Docs, Guides, SDKs, Integrations, API docs, Tutorials, and Changelog. Shared examples have synchronized TypeScript and Go tabs: choose an SDK once to switch the matching examples on that page. Framework-specific examples document the SDK supported by that framework.

For direct application control, [connect an agent through MCP](/operations/connect-an-agent) under **Guides → Operations**. The endpoint is `https://mcp.useshipd.com/mcp`; mutations require idempotency keys and updates require observed versions. Pending changes must be reviewed in the dashboard. [MCP tools and request guarantees](/reference/mcp) live under **API docs → Operations APIs**.

Every page also has a menu to copy it as Markdown or open it in Claude, ChatGPT, or Cursor.

To give your assistant shipd context permanently, add this to your project's `CLAUDE.md`, `AGENTS.md`, or editor rules:

```md theme={null}
This project uses shipd for feature flags. Before writing shipd code, read
https://docs.useshipd.com/ai.md and follow its rules.
```

## shipd context for coding assistants

shipd is a feature flag service. Flags are defined in TypeScript, Go, or version-1 JSON, synced with a shared CLI, and evaluated locally in memory by an SDK.

### Packages

| Package / import | Purpose |
| - | - |
| `github.com/alasti-company/useshipd.com/sdks/go/config` | Go flag authoring, in the same module as the Go SDK. `ShipdConfig` is a named map of `FlagDefinition` structs; exported fields include `Type`, `Default`, and `Description`. Declare a literal directly; `Validate` and `Defaults` validate it. |
| `github.com/alasti-company/useshipd.com/sdks/go` | Go server SDK (Go 1.23+). Definitions use its `config` subpackage (`config.ShipdConfig`); the CLI loads `shipd.config.go` directly; CLI `types gen --language go` generates accessors. `shipd.New` requires `SDKKey`; defaults to the production edge. Polling only. Pass `shipd.EvaluationContext` per evaluation; lifecycle and network methods use `context.Context`; `Identify` only queues telemetry. Use bounded contexts for `Ready` and `Shutdown`; `Shutdown(ctx)` flushes then closes, `Close` doesn't flush. Never set `Overrides` in application code. |
| `shipdit` | Server SDK (Node.js 20+, workers). `createClient`. |
| `shipdit/client` | Browser SDK. `createClient` with `localStorage` cache. |
| `shipdit/client/react` | React bindings. `createShipdReact`. |
| `shipdit/config` | `defineFlags`, used only in `shipd.config.ts`. |
| `@shipdit/cli` | CLI. Binary name is `shipd`. |
| `@shipdit/convex` | Convex component. Mount with `SHIPD_SDK_KEY` and `SHIPD_WEBHOOK_SECRET`; snapshots use the default shipd edge. `SHIPD_EDGE_URL` was removed. `SHIPD_UNSAFE_EDGE_URL` is for shipd development and tests only and must be forwarded in the mount configuration. |

### Go workflow

The CLI reads `shipd.config.go` directly and generates Go accessors by default, unless `typesLanguage` overrides the output language. Define an exported `var Config = config.ShipdConfig{...}` in a self-contained Go file inside your module. The CLI evaluates that file with Go; imported packages and helper declarations in the same file are supported. It does not load sibling Go files, so keep application dependencies in imported packages. Do not define `main` in the definition file. `shipd pull` rewrites the Go definitions and regenerates accessors; review the resulting diff because comments and helper code are replaced. Use generated accessors like `flags.NewCheckout(client, ctx)` and `flags.Defaults()`.

### Files

| Path | What it is |
| - | - |
| `shipd.config.ts` | TypeScript flag definitions. The source of truth for which flags exist. |
| `src/generated/flags.ts` | Generated `FlagDefinitions` types. Written by `shipd push`, `shipd pull`, `shipd types gen`. Never edit. |
| `shipd.config.go` | Go flag definitions: `var Config = config.ShipdConfig{...}` in `package main`, no `main` func. Replaces `shipd.config.ts` in Go projects. |
| `internal/flags/flags_gen.go` | Generated Go accessors, variant constants, and `Defaults()` (path set by `typesOut`). Never edit. |
| `.shipd/config.json` | Optional CLI defaults: `org`, `project`, `env`, `typesOut`. |

### Define flags

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

export default defineFlags({
  "new-checkout": { type: "boolean", default: false, description: "Redesigned checkout" },
  "search-variant": {
    type: "variant",
    variants: ["control", "semantic", "hybrid"] as const,
    default: "control",
  },
  "max-retries": { type: "number", default: 3 },
  "checkout-payload": { type: "json", default: { mode: "strict", showPromo: true } },
});
```

```go Go theme={null}
// shipd.config.go
package main

import "github.com/alasti-company/useshipd.com/sdks/go/config"

var Config = config.ShipdConfig{
	"new-checkout":     {Type: "boolean", Default: false, Description: "Redesigned checkout flow", Tags: []string{"checkout"}},
	"search-variant":   {Type: "variant", Default: "control", Variants: []string{"control", "semantic", "hybrid"}},
	"max-retries":      {Type: "number", Default: 3},
	"checkout-payload": {Type: "json", Default: map[string]any{"mode": "strict", "showPromo": true}},
}
```

Then run `shipd push`. It creates missing flags, updates changed definitions, and regenerates TypeScript types or Go accessors. It never deletes flags; `--prune` archives flags missing from the file.

### Evaluate on a server

Streaming requires a WebSocket implementation. Node.js 22+ provides one globally. On Node.js 20, supply a compatible constructor through the `WebSocket` option; otherwise, the client uses polling.

```ts TypeScript theme={null}
// src/lib/shipd.ts
import { createClient } from "shipdit";
import type { FlagDefinitions } from "../generated/flags";

export const shipd = createClient<FlagDefinitions>({
  sdkKey: process.env.SHIPD_SDK_KEY!, // shipd_sdk_server_…
  stream: true, // optional; requires Node.js 22+ or the WebSocket option
  defaults: {
    "new-checkout": false,
    "search-variant": "control",
    "max-retries": 3,
    "checkout-payload": { mode: "strict", showPromo: true },
  },
});

await shipd.ready(); // once at startup; never rejects

shipd.isEnabled("new-checkout", { userId: user.id, attributes: { plan: user.plan } });
shipd.getVariant("search-variant", { userId: user.id }); // "control" | "semantic" | "hybrid"
shipd.getNumber("max-retries");
shipd.getJson("checkout-payload");
```

```go Go theme={null}
// Initialize once at startup; imports: context, log, os, time, and shipd.
client, err := shipd.New(shipd.Options{
    SDKKey: os.Getenv("SHIPD_SDK_KEY"),
    Defaults: map[string]any{
        "new-checkout": false, "search-variant": "control", "max-retries": 3,
        "checkout-payload": map[string]any{"mode": "strict", "showPromo": true},
    },
})
if err != nil { log.Fatal(err) }
defer func() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    if err := client.Shutdown(ctx); err != nil { log.Printf("shipd events: %v", err) }
}()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
if err := client.Ready(ctx); err != nil { log.Printf("using defaults: %v", err) }
cancel()
user := shipd.EvaluationContext{UserID: "usr_8f2k19", Attributes: map[string]any{"plan": "pro"}}
client.IsEnabled("new-checkout", user)
client.GetVariant("search-variant", user)
client.GetNumber("max-retries", user)
client.GetJSON("checkout-payload", user)
```

### Evaluate in React

```ts src/lib/shipd-react.ts theme={null}
"use client";
import { createShipdReact } from "shipdit/client/react";
import type { FlagDefinitions } from "../generated/flags";

export const { ShipdProvider, useFlag, useVariant, useNumber, useJson, useReady } =
  createShipdReact<FlagDefinitions>();
```

```tsx theme={null}
import { createClient } from "shipdit/client";

const client = createClient<FlagDefinitions>({
  sdkKey: import.meta.env.VITE_SHIPD_SDK_KEY, // shipd_sdk_client_…
  stream: true,
  defaults: { "new-checkout": false, "search-variant": "control" },
});

<ShipdProvider client={client}><App /></ShipdProvider>;

client.identify({ userId: user.id, traits: { plan: user.plan } }); // after login
client.reset(); // on logout

const on = useFlag("new-checkout");
```

### Evaluation model

* The SDK downloads a **snapshot** of one environment from `https://edge.useshipd.com`, keeps it in memory, and refreshes it every 30 seconds or on a WebSocket notification.
* `isEnabled`, `getVariant`, `getNumber`, and `getJson` make no network requests and never throw.
* Value order: no snapshot or unknown key → `defaults[key]`; flag disabled → fallthrough, skipping rules; first matching rule → its serve; otherwise → fallthrough.
* **Evaluation context** is `{ userId?, attributes?, segmentIds? }`. Per-call context overrides `createClient({ context })`. In browsers (`shipdit/client`), `identify()` traits sit between the two; server `identify()` only records an event and never changes context.
* Percentage rollouts hash the flag key and `userId`, or a chosen attribute. A missing bucket attribute makes the condition fall through.
* Targeting rules, rollouts, and enabled state are set in the dashboard, not in `shipd.config.ts` or `shipd.config.go`.

### Credentials

| Credential | Prefix | Used for | Never use it for |
| - | - | - | - |
| Server SDK key | `shipd_sdk_server_` | `shipdit` and the Go SDK on servers | Browsers, the CLI |
| Client SDK key | `shipd_sdk_client_` | `shipdit/client` in browsers | The CLI |
| Management API key | `shipd_mgmt_` | The CLI on a laptop (`shipd login` creates it) | SDKs |
| Service account token | `shipd_sa_` | The CLI in CI (`SHIPD_API_KEY`) | SDKs |

### Rules for assistants

1. Import `defineFlags` from `shipdit/config`, never from `shipdit`.
2. Import `createClient` from `shipdit` on servers and from `shipdit/client` in browsers.
3. Type the client with the generated `FlagDefinitions`: `createClient<FlagDefinitions>(…)`. Never write flag types by hand.
4. Pass a `defaults` entry for every flag. Use the safe value (usually "off").
5. Create one client per process and reuse it. Never create a client per request.
6. `await shipd.ready()` once at startup, before serving traffic that reads flags.
7. On servers, pass `{ userId, attributes }` per call. Server `identify()` only records an event; it never sets evaluation context.
8. Call `createShipdReact` once, in a `"use client"` module, and use hooks from that same call.
9. Add or change flags by editing `shipd.config.ts` (Go: `shipd.config.go`) and running `shipd push`. Don't create flags in the dashboard for code-managed projects.
10. Don't pass `endpoint` or `streamEndpoint`; they were removed. Use `stream: true` for realtime updates. For a custom stream gateway, preserve its URL with `overrides.streamEndpoint` and `stream: true`. Reserve `overrides.unsafeEndpoint` for development and tests; never use it in production.

### Rules for Go

1. Import the SDK as `shipd "github.com/alasti-company/useshipd.com/sdks/go"` and definitions from its `config` subpackage. One `go get github.com/alasti-company/useshipd.com/sdks/go` installs both.
2. Define flags in `shipd.config.go`, never `shipd.config.ts`. Run `shipd types gen` (Node.js 22.12+ and `@shipdit/cli`) to write accessors.
3. Call generated accessors such as `flags.NewCheckout(client, ctx)` instead of `client.IsEnabled("new-checkout", ctx)`, and pass `Defaults: flags.Defaults()` to `shipd.New`.
4. Create one `*shipd.Client` per process with `shipd.New` and share it across goroutines.
5. Call `Ready` once at startup with a bounded context. It returns the fetch error as soon as the first fetch fails; log it and serve defaults.
6. Pass a `shipd.EvaluationContext` on every evaluation. `Identify` only queues telemetry.
7. On shutdown, call `Shutdown(ctx)` with a bounded context. It attempts to flush pending events and closes even on delivery failure; check or log its returned error. `Close` does not flush. Never set `Overrides.UnsafeEndpoint` in application code.
8. JSON flags return `any` and numbers are `float64`; convert at the call site.
9. In tests, use an `HTTPClient` that fails, `DisablePolling`, and `DisableEvents`. To test targeting rules, pass snapshot JSON as `Bootstrap`.

### Common mistakes

```ts Wrong theme={null}
import { defineFlags } from "shipdit";
```

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

```ts Wrong theme={null}
// Server key in a browser bundle
createClient({ sdkKey: process.env.NEXT_PUBLIC_SHIPD_SERVER_KEY!, … });
```

```ts Right theme={null}
// Client key in the browser, server key only in server code
import { createClient } from "shipdit/client";
createClient({ sdkKey: process.env.NEXT_PUBLIC_SHIPD_SDK_KEY!, … }); // shipd_sdk_client_…
```

```ts Wrong theme={null}
// Network call per check
const res = await fetch("https://edge.useshipd.com/sdk/v1/snapshot", …);
```

```ts Right theme={null}
// In-memory check against the SDK's snapshot
shipd.isEnabled("new-checkout", { userId });
```

```ts Wrong theme={null}
app.get("/checkout", async (req, res) => {
  const shipd = createClient({ … }); // new client per request
  await shipd.ready();
});
```

```ts Right theme={null}
// src/lib/shipd.ts exports one client, created at startup
app.get("/checkout", (req, res) => {
  shipd.isEnabled("new-checkout", { userId: req.user.id });
});
```

```ts Wrong theme={null}
type Flags = { "new-checkout": boolean }; // hand-written
createClient<Flags>({ … });
```

```ts Right theme={null}
import type { FlagDefinitions } from "./generated/flags";
createClient<FlagDefinitions>({ … });
```

**Don't:**

* Call the edge API directly to evaluate flags.
* Put a server SDK key, management API key, or service account token in browser code.
* Use a management API key or service account token as an SDK key, or an SDK key with the CLI.
* Edit `src/generated/flags.ts` by hand.
* Assume `shipd push` deletes flags missing from `shipd.config.ts`. It leaves them; `--prune` archives them.
* Run `shipd push --prune` without reading the plan. It archives every flag not in the file.
* Put targeting rules or rollout percentages in `shipd.config.ts`. They're dashboard state.
* Forget that `default` in `shipd.config.ts` is the fallthrough of the environment you push to. `shipd push` resets a fallthrough changed in the dashboard back to it. Update `default` (or run `shipd pull`) first.
* Assume disabling a flag turns it off. A disabled flag serves its fallthrough. To turn a feature off, set the fallthrough to `false`.
* Use `--force` to get past a conflict. Run `shipd pull`, re-apply the edit, and push again.
* Treat `shipd push` exit code `5` as success. The change is waiting for approval.
* Rely on percentage rollouts without supplying their bucket attribute (`userId` by default).
* Import `shipdit/client/react` in server code.

**Do:**

* Supply `defaults` for every flag.
* Evaluate locally with the SDK.
* Commit `src/generated/flags.ts` along with `shipd.config.ts` (Go: `flags_gen.go` along with `shipd.config.go`).
* Run `shipd diff --exit-code` in pull request CI, and `shipd push --ci` on merge with a service account token in `SHIPD_API_KEY`.
* Set `SHIPD_API_URL=https://api.useshipd.com` in CI. Without a saved login, the CLI defaults to `http://localhost:8000`.
* Await `shipd.close()` on shutdown. In Go, drain active handlers first, then call `Shutdown(ctx)` with a bounded context and check its error.

### CLI cheat sheet

```bash theme={null}
npm install -g @shipdit/cli
shipd login                      # device login; pick org, project, environment
shipd diff                       # show what push would change
shipd push                       # apply shipd.config.ts or .go, regenerate types
shipd push --ci                  # non-interactive (CI)
shipd pull                       # overwrite shipd.config.ts or .go from shipd
shipd types gen                  # regenerate types or Go accessors only
```

| Exit code | Meaning |
| - | - |
| `0` | Success, or no drift |
| `1` | Error |
| `2` | Drift (`diff --exit-code`) |
| `3` | Conflict: someone changed the flag since you pulled |
| `4` | Unlinked flags (`diff --exit-code`) |
| `5` | Accepted but waiting for approval; not live yet |

## Where to look next

* [SDK reference](/reference/sdk) · [CLI reference](/reference/cli) · [`shipd.config.ts` reference](/reference/config)
* [Core concepts](/core-concepts) · [Glossary](/reference/glossary)
* [Examples](/examples/node): complete apps to adapt


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.