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

# Convex example

> A complete Convex backend that mounts the shipd component and evaluates flags in queries.

## Overview

A complete Convex backend that mounts the `@shipdit/convex` component and evaluates `new-checkout` and `search-variant` in an authenticated query. The component stores the snapshot in Convex, so evaluations are reactive and add no network call.

## What's in it

* `convex/convex.config.ts` mounts one component instance named `shipd` for one shipd environment.
* `convex/lib/shipd.ts` wraps the instance with typed defaults.
* `convex/storefront.ts` authorizes the caller, then evaluates both flags for that user.
* A signed webhook from shipd triggers a refresh when a snapshot is published. A one-minute poll is the fallback.

```text theme={null}
convex-example/
├── package.json
├── shipd.config.ts
└── convex/
    ├── convex.config.ts
    ├── http.ts
    ├── storefront.ts
    └── lib/
        └── shipd.ts
```

`convex/_generated/` is created by `npx convex dev`.

## Files

```json package.json theme={null}
{
  "name": "convex-example",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "convex dev",
    "deploy": "convex deploy",
    "flags:diff": "shipd diff",
    "flags:push": "shipd push"
  },
  "dependencies": {
    "@shipdit/convex": "^0.2.0",
    "convex": "^1.44.0"
  },
  "devDependencies": {
    "@shipdit/cli": "^0.3.2",
    "shipdit": "^0.3.0",
    "typescript": "^5.9.0"
  }
}
```

`shipdit` is a dev dependency here because only `shipd.config.ts` imports it. Convex functions use `@shipdit/convex`.

```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"],
  },
});
```

```ts convex/convex.config.ts theme={null}
import shipd from "@shipdit/convex/convex.config.js";
import { defineApp } from "convex/server";
import { v } from "convex/values";

const app = defineApp({
  env: {
    SHIPD_SDK_KEY: v.string(),
    SHIPD_WEBHOOK_SECRET: v.string(),
  },
});

// The component serves its webhook at `${httpPrefix}/webhook`.
app.use(shipd, {
  name: "shipd",
  httpPrefix: "/shipd",
  env: {
    SHIPD_SDK_KEY: app.env.SHIPD_SDK_KEY,
    SHIPD_WEBHOOK_SECRET: app.env.SHIPD_WEBHOOK_SECRET,
  },
});

export default app;
```

```ts convex/http.ts theme={null}
import { httpRouter } from "convex/server";

// Your own HTTP routes go here. The shipd component registers its webhook route itself.
export default httpRouter();
```

The wrapper takes a flag map with variants as a `readonly` tuple. Keep it in sync with `shipd.config.ts`.

```ts convex/lib/shipd.ts theme={null}
import { Shipd } from "@shipdit/convex";

import { components } from "../_generated/api";

type Flags = {
  "new-checkout": { type: "boolean"; default: false };
  "search-variant": {
    type: "variant";
    default: "control";
    variants: readonly ["control", "semantic", "hybrid"];
  };
};

// Returned before the first snapshot syncs, and for missing or mistyped flags.
export const shipd = new Shipd<Flags>(components.shipd, {
  "new-checkout": false,
  "search-variant": "control",
});
```

```ts convex/storefront.ts theme={null}
import { v } from "convex/values";

import { query } from "./_generated/server";
import { shipd } from "./lib/shipd";

export const flags = query({
  args: {},
  returns: v.object({
    newCheckout: v.boolean(),
    search: v.union(v.literal("control"), v.literal("semantic"), v.literal("hybrid")),
  }),
  handler: async (ctx) => {
    // Component functions cannot see ctx.auth. Authorize here, before evaluating.
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      throw new Error("Unauthenticated");
    }

    const context = { userId: identity.subject };

    return {
      newCheckout: await shipd.isEnabled(ctx, "new-checkout", context),
      search: await shipd.getVariant(ctx, "search-variant", context),
    };
  },
});
```

## Run it

**1. Push the flags**

```bash theme={null}
pnpm install
pnpm exec shipd login       # approve org → project → environment in the browser
pnpm exec shipd push
```

**2. Start Convex**

```bash theme={null}
npx convex dev
```

This creates the deployment and generates `components.shipd`. Leave it running.

**3. Set environment values**

In a second terminal, use a **server** SDK key for the environment you pushed to. Set a placeholder secret for now.

```bash theme={null}
npx convex env set SHIPD_SDK_KEY shipd_sdk_server_...
npx convex env set SHIPD_WEBHOOK_SECRET replace-after-dashboard-setup
```

**4. Create the webhook destination**

In the shipd dashboard, open the project's **Settings → Integrations**. Under **Convex**, choose the environment and enter `https://<your-deployment>.convex.site/shipd/webhook`. Click **Create destination**, copy the signing secret, and replace the placeholder:

```bash theme={null}
npx convex env set SHIPD_WEBHOOK_SECRET <signing-secret>
```

Click **Test delivery**.

**5. Call the query**

```bash theme={null}
npx convex run storefront:flags --identity '{"subject":"usr_8f2k19"}'
# { newCheckout: false, search: "control" }
```

Add a rule to `new-checkout` in the dashboard and click **Save environment**. The webhook refreshes the component and subscribed clients re-render with the new value.

## Adapt it

* Mount one named instance per shipd environment (for example `shipdDevelopment` and `shipdProduction`), each with its own SDK key, `httpPrefix`, and webhook secret.
* Add attributes to the context (`plan`, `organizationId`) from your own tables after authorization.
* Use `shipd.evaluate(ctx, key, context)` to get the reason and snapshot version, and `shipd.getStatus(ctx)` to check sync health.
* Call `shipd.syncNow(ctx)` from an authorized action to force a refresh.

## Next

* [Convex SDK guide](/sdks/convex)
* [SDK keys](/concepts/keys)
* [SDK reference](/reference/sdk)


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