Skip to main content
In the App Router, Corsair fits the model you already use. One file under app/api is the whole server surface, Server Components read data at render time, and Server Actions handle the writes. Nothing runs on the client that doesn’t have to.

Install

The adapters ship in core corsair; add one @corsair-dev/* package per service you connect. The full list is in Plugins.

Create the instance

Build the instance once. The handler and your read/write code both import it.
corsair.ts
The handler and every .db / .api call on this page import this corsair. multiTenancy: true is what makes corsair.withTenant(id) available, and a tenant.github.* call resolves to the github() entry here. Add a service by installing its @corsair-dev/* package and dropping it into plugins. Database choices and the Hub keys are in Quick Start.

Project layout

Mount the handler

The management API, OAuth callbacks, and Hub’s connect delivery all arrive on the same path, so a single catch-all route handler serves them. Mount it once and you never edit it again.
app/api/corsair/[[...path]]/route.ts
@/server/corsair is your createCorsair({ ... }) instance. The optional-catch-all segment [[...path]] matches /api/corsair and everything under it.

Resolve the tenant

withTenant takes any stable string that identifies the current user or org. You produce it from the auth you already run; Corsair never supplies one. Read it at the top of the component and scope from there.

Read and write

A Server Component reads from .db with no network, so the page renders as fast as any database query. Because it’s already on the server, you call the tenant directly instead of round-tripping through the route.
app/issues/page.tsx
The Server Action writes through .api:
app/issues/actions.ts

Refresh after a write

The .api call above upserts the response into your database, so .db is already current. revalidatePath('/issues') just re-runs the Server Component, and the closed issue is gone from the list, with no cache to invalidate by hand.

Connect a tenant

The read and write paths above assume a tenant has already connected GitHub. That flow is client-side, so create the React hook client once in a "use client" module and call the hooks from any component.
1

Create the client

app/corsair-client.ts
One factory call per app. Every hook is typed against your handler, so useTenants() knows it returns tenants. client is the escape hatch to the vanilla client for imperative calls. The full hook list is on the React Hooks reference.
2

Read what's connected

connections.tsx
useConnectionStatus returns a map of plugin id to connected | missing_credentials | not_connected, so your UI knows what’s live and what still needs a connect flow.
3

Mint a connect link

connect-github.tsx
Hub hosts the consent screen and runs the OAuth handshake. When the user returns, the tokens are already encrypted in your own database. You never saw them, and neither did Hub. Swap "github" for any plugin you configured.

Go green

The first request to /api/corsair registers your delivery URL with Hub and turns the App sync dot in the dashboard header green.

Deploy

Deploy to Vercel as you would any App Router app. The mounted /api/corsair route becomes the delivery URL Hub calls in production, with no separate service to stand up.

Next

Build a dashboard

The full .db read / .api write pattern, with refresh and webhooks.

React hooks

Every hook the client factory returns, with types and examples.

Connect / OAuth

The full connect flow, error codes, and retry.

Multi-tenancy

One flag and every user gets their own data and credentials.