Getting started
Prerequisites
- Node.js ≥ 22.5 (see
.nvmrc) - Yarn 4, managed by Corepack. Run
corepack enableonce; the repo pins the exact version via thepackageManagerfield, so the right Yarn is used automatically. - Docker (for local Postgres)
Setup
Artsy engineers
yarn setup:artsy does steps 1–2 for you: it installs dependencies and pulls a pre-filled .env.local from Citadel (S3), so you can skip straight to step 3. It requires AWS access to the artsy-citadel bucket. Everyone else, follow the manual steps below.
Install dependencies
shyarn installConfigure environment
Copy the example env file and fill in the blanks:
shcp .env.example .env.localYou'll need:
CLIENT_APPLICATION_ID/CLIENT_APPLICATION_SECRET— a Gravity ClientApplication (see Gravity API authentication).NEXTAUTH_SECRET— generate withopenssl rand -base64 32.DATABASE_URL,GRAVITY_URL,NEXTAUTH_URL,PUBLIC_GRAVITY_URL— defaults for local dev are already in.env.example.
The Prisma CLI (
yarn prisma:migrate,yarn prisma:deploy,yarn prisma generate) only reads a plain.envfile, not.env.local— that's a Prisma limitation, not a Next.js one. Next.js itself loads.env.localfine, soyarn devdoesn't need this. Keep both in sync:shcp .env.local .envStart Postgres
shyarn db:up # docker-compose up -dCreate the schema and seed sample data
shyarn prisma:migrate # applies migrations yarn seed # engineers + a weekly "Platform on-call" rotation (with a demo override + swap) and a biweekly "Release Captain" rotationRun the app
shyarn devyarn devfirst verifies your local database has the committed migrations applied — if it's behind, it stops and tells you to runyarn prisma:migrateso you don't run the app against a stale schema. (If Postgres simply isn't running yet it just warns and continues. Useyarn dev:nextto skip the check.)Open http://localhost:3000 and sign in with Artsy. Only users with the Gravity
teamrole can sign in and make changes. You can sign out anytime via the "Log out" button in the top navigation.
Environment variables
| Variable | Purpose |
|---|---|
DATABASE_URL | Postgres connection string. Defaults to host port 5433 (see docker-compose.yml) to avoid clashing with a system-wide Postgres on the standard 5432. |
CLIENT_APPLICATION_ID | Gravity OAuth client id. |
CLIENT_APPLICATION_SECRET | Gravity OAuth client secret. |
GRAVITY_URL | Gravity base URL (server-side auth). |
NEXTAUTH_URL | Canonical URL of this app. |
NEXTAUTH_SECRET | next-auth session/JWT secret. |
PUBLIC_GRAVITY_URL | Gravity URL exposed to the client. |
ORBIT_SERVICE_TOKENS | Optional, comma-separated bearer tokens granting read-only API access to headless clients (e.g. a Slack bot) via Authorization: Bearer <token>, no session required. Empty disables it. |
Authenticating with a different provider
Orbit ships with a single Artsy/Gravity OAuth provider, which needs internal Artsy credentials. If you're running your own instance (e.g. as an open-source contributor), you can authenticate against a different provider instead of — or alongside — Artsy. Auth is next-auth, so any OAuth2/OIDC provider works.
1. Add the provider to the providers array in src/pages/api/auth/[...nextauth].page.ts. Use a next-auth built-in (e.g. GithubProvider / GoogleProvider from next-auth/providers/*) or a custom block mirroring the Artsy one. For example, GitHub:
import GithubProvider from "next-auth/providers/github"
// inside providers: [ ... ]
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
profile(profile) {
return {
id: String(profile.id),
name: profile.name ?? profile.login,
email: profile.email,
image: profile.avatar_url,
// See gotcha (a) below — supply a role the app recognizes.
roles: ["team"],
}
},
})A sign-in button for each configured provider appears on the login screen automatically (the button list is driven by next-auth's getProviders()), so no UI change is needed.
2. Set its credentials as environment variables and register the provider's callback URL with it:
${NEXTAUTH_URL}/api/auth/callback/<provider-id>
# e.g. http://localhost:3000/api/auth/callback/githubTwo gotchas:
- (a) Roles gate everything. The
signIncallback only admits users whoserolesinclude a value from theRoleenum, and RBAC insrc/system/index.tsrequires theteamrole for every action. So your provider'sprofile()must return a recognized role (the example synthesizesroles: ["team"]). Alternatively, add a new role to theRoleenum and grant it in thePERMISSIONSmap — e.g. a read-only role listed only underAction.read. - (b) It's still next-auth.
NEXTAUTH_URLandNEXTAUTH_SECRETmust be set as usual; only theprovidersarray and its credentials change.
Useful scripts
| Script | Does |
|---|---|
yarn dev | Start the dev server. |
yarn build | Production build. |
yarn test | Jest unit test suite. |
yarn e2e | Playwright end-to-end tests. |
yarn type-check | tsc --noEmit. |
yarn lint | ESLint. |
yarn seed | Seed sample data. |
yarn db:up / yarn db:down | Start / stop local Postgres. |
yarn prisma:migrate | Apply migrations (dev). |
Testing
- Unit tests (
yarn test): Jest + Testing Library. The rotation logic has full coverage; API routes are tested withnode-mocks-http. - End-to-end (
yarn e2e): Playwright drives the real UI against mocked API responses, with an injected session so it runs as a signed-in user (the auth flow itself is intentionally not e2e-tested). No database is required.
Having trouble? See Troubleshooting.

