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
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
.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 standardRequest, 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
Bothloader 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
Theloader 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’sloader 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
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
"github" for any plugin you configured.Go green
/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.