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

# Build a Plugin

> Scaffold a new Corsair plugin with the generator, then let Claude Code fill in the implementation.

<Tip>
  **Use Claude Code to build your plugin.** Run the steps below in your terminal, then paste the prompt from the guide into Claude Code — it will fill in the real API calls for you.
</Tip>

The fastest way to build a custom plugin is to use the built-in generator. It scaffolds the full directory structure, wires up types, and registers your plugin — then you (or Claude Code) fill in the real API calls.

<Steps>
  <Step title="Fork or clone the Corsair repo">
    Plugins live inside the monorepo alongside the core library.

    ```bash theme={null}
    git clone https://github.com/corsair-dev/corsair.git
    cd corsair
    pnpm install
    ```
  </Step>

  <Step title="Run the plugin generator">
    Pass your plugin name in **PascalCase** — e.g. `Stripe`, `GoogleCalendar`, `HubSpot`.

    ```bash theme={null}
    pnpm run generate:plugin <PluginName>
    ```

    For example:

    ```bash theme={null}
    pnpm run generate:plugin Stripe
    ```

    This creates `packages/stripe/` with the full plugin structure:

    ```
    packages/stripe/
    ├── index.ts
    ├── client.ts
    ├── error-handlers.ts
    ├── package.json
    ├── tsconfig.json
    ├── tsup.config.ts
    ├── endpoints/
    │   ├── index.ts
    │   ├── types.ts
    │   └── example.ts
    ├── webhooks/
    │   ├── index.ts
    │   ├── types.ts
    │   └── example.ts
    └── schema/
        ├── index.ts
        └── database.ts
    ```

    It also automatically registers your plugin in `packages/corsair/core/constants.ts`.
  </Step>

  <Step title="Choose your auth type">
    Open `packages/<yourplugin>/index.ts` and update the auth type to match the API you're integrating with.

    **API key** (most REST APIs):

    ```ts theme={null}
    authType?: PickAuth<'api_key'>;
    ```

    **OAuth 2** (Google, GitHub, etc.):

    ```ts theme={null}
    authType?: PickAuth<'oauth_2'>;
    ```

    Also update the `defaultAuthType` constant and `authConfig` to match:

    ```ts theme={null}
    const defaultAuthType: AuthTypes = 'api_key'; // or 'oauth_2'

    export const stripeAuthConfig = {
        api_key: {
            account: ['one'] as const,
        },
    } as const satisfies PluginAuthConfig;
    ```
  </Step>

  <Step title="Let Claude Code implement it">
    Point Claude Code at the API docs for your integration and let it do the heavy lifting.

    Open Claude Code in the repo root and give it a prompt like:

    ```
    I've generated a Corsair plugin scaffold at packages/stripe/.
    Please implement it using the Stripe API docs at https://docs.stripe.com/api.

    - Update client.ts with the correct base URL and auth headers (Bearer token)
    - Replace the example endpoint with real Stripe endpoints (e.g. list customers, get invoice)
    - Update the schema/database.ts with relevant entity shapes
    - Remove the example webhook and add real Stripe webhook event types
    ```

    Claude Code can read the scaffold, understand the patterns from existing plugins (e.g. `packages/github/`), and produce a working starting point.
  </Step>

  <Step title="Typecheck and build">
    ```bash theme={null}
    cd packages/<yourplugin>
    pnpm typecheck
    pnpm build
    ```

    Fix any type errors, then register the plugin in your app the same way as any other Corsair plugin:

    ```ts src/server/corsair.ts theme={null}
    import { stripe } from '@corsair-dev/stripe';

    export const corsair = createCorsair({
        plugins: [stripe({ key: process.env.STRIPE_API_KEY })],
        database: db,
        kek: process.env.CORSAIR_KEK!,
    });
    ```
  </Step>

  <Step title="Test your plugin">
    The repo includes a ready-made testing sandbox at `demo/testing/`. Add your plugin there and run scripts against it without setting up a new project.

    **1. Add your plugin as a workspace dependency in `demo/testing/package.json`:**

    ```json demo/testing/package.json theme={null}
    {
      "dependencies": {
        "@corsair-dev/stripe": "workspace:*"
      }
    }
    ```

    Then run `pnpm install` from the repo root to link it.

    **3. Register your plugin in the test corsair instance:**

    ```ts demo/testing/src/server/corsair.ts theme={null}
    import { stripe } from '@corsair-dev/stripe';

    export const corsair = createCorsair({
        plugins: [
            // ... existing plugins
            stripe({ key: process.env.STRIPE_API_KEY }),
        ],
        database: sqlite,
        kek: process.env.CORSAIR_KEK!,
    });
    ```

    **4. Write your test in `demo/testing/src/scripts/test-script.ts`:**

    ```ts demo/testing/src/scripts/test-script.ts theme={null}
    import { corsair } from '@/server/corsair';
    import 'dotenv/config';

    const main = async () => {
        const customer = await corsair.stripe.api.customers.get({ id: 'cus_123' });
        console.log(customer);
    };

    main();
    ```

    **5. Run the script:**

    ```bash theme={null}
    cd demo/testing
    pnpm run test
    ```

    <Info>
      **Build watch required**
      In a separate terminal, run the build watcher in your plugin package so changes are picked up immediately:

      ```bash theme={null}
      cd packages/stripe
      pnpm run build --watch
      ```

      Without this, the test sandbox will be running stale compiled output.
    </Info>
  </Step>
</Steps>

***

## Tips

* **Look at existing plugins** for reference — `packages/github/` and `packages/resend/` are good examples at different complexity levels.
* **Auth headers** go in `client.ts` inside the `HEADERS` config object. Most APIs use `Authorization: Bearer <key>` or `Authorization: <key>`.
* **Webhook signature verification** — update the `TODO` in `webhooks/types.ts` with the HMAC logic for your provider.
* **Database entities** — define Zod schemas in `schema/database.ts` for any data you want to cache locally. Leave `entities: {}` empty if you only need live API calls.
