Skip to main content
Remix splits every route into a loader that reads and an action that writes. Corsair splits every plugin into .db for reads and .api for writes. The two models line up one-to-one, so a Corsair-backed route is just Remix with the data source swapped. React Router v7 works identically.

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.

Mount the handler

Route modules receive a standard Request, which is exactly what the adapter needs. Add a splat route and export the pair.
app/routes/api.corsair.$.ts
~/server/corsair is your createCorsair({ ... }) instance. On React Router v7 the file is api.corsair.$.tsx with the same exports.

Resolve the tenant

Both loader and action receive the request, so read the user off your session the way you already do and pass that id to withTenant. Corsair brings no auth of its own; it only needs the resolved id.

Read and write

The loader reads .db and returns it; useLoaderData renders it. No network call happens on navigation, so the route is as fast as a local query.
app/routes/issues.tsx

Refresh after a write

Remix re-runs the page’s loader automatically after an action returns. The .api write already upserted into .db, so that revalidation renders the closed issue out of the list with no manual invalidation and no client state to reconcile.

Connect a tenant

A tenant has to connect GitHub before the loader has anything to read. That flow is client-side, so create the React hook client once and call the hooks from any route or 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 flips the App sync dot in the dashboard header to green.

Deploy

Deploy however you already ship this app. In production the mounted /api/corsair route is the delivery URL Hub calls. The same splat route serves it whether you’re on a Node server or a Web-standard host.

Next

Build a dashboard

The .db read / .api write pattern in full, 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.