Getting started with Scheduling
Clone the source, configure PostgreSQL, sessions, secrets, and storage, apply migrations, create the first admin, run onboarding, and deploy.
Live demo
A hosted demo runs at scheduling.openship.org. Use it to see the public booking pages and the operator dashboard before you install anything. Demo data is fictional.
Requirements
- Node.js 20.9 or later (
engines.nodeis>=20.9.0; the README targets Node.js 22). - A PostgreSQL database you are allowed to migrate.
- npm, using the checked-in
package-lock.json.
Clone and install
git clone https://github.com/openshiporg/openfront-scheduling.git
cd openfront-scheduling
npm ciEnvironment
Copy .env.example to .env and replace every placeholder. The minimum for local development is a database and a session secret:
DATABASE_URL=postgresql://user:password@localhost:5432/openfront_scheduling
SESSION_SECRET=replace-with-output-of-openssl-rand-hex-32In local development, several feature secrets fall back to a key derived from SESSION_SECRET. Set them explicitly before any shared or production use, and do not reuse one value for several of them:
| Variable | Used for |
|---|---|
BOOKING_MANAGE_SECRET | Signing guest booking management links (at least 32 bytes) |
RATE_LIMIT_SECRET | Public request rate-limit buckets (at least 32 bytes) |
TEAM_INVITATION_SECRET | Team invitation tokens (at least 32 bytes) |
MACHINE_TOKEN_PEPPER | Hashing API keys and OAuth client secrets |
ACCOUNT_EMAIL_CHANGE_SECRET | Customer email-change confirmation tokens |
SCHEDULING_PUBLIC_URL | Public HTTPS origin used in emails, invitations, manage links, and MCP. Falls back to NEXTAUTH_URL, then RAILWAY_PUBLIC_DOMAIN |
STOREFRONT_TEAM_SLUG | Optional. Which team the public storefront shows; defaults to the oldest active team with public booking turned on |
PUBLIC_SIGNUPS_ALLOWED | true opens the dashboard sign-up page; keep false unless you want open registration |
TRUSTED_PROXY_HOPS | Number of trusted proxy hops for reading client IPs from forwarded headers (0 to 10) |
Object storage (S3)
Keystone is configured with an S3-compatible image store named my_images. It reads these variables, with test defaults when unset:
S3_BUCKET_NAME=your-bucket
S3_REGION=your-region
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_ENDPOINT=https://your-s3-endpointNo current list has an image or file field that uses this store (team logos are sanitized inline SVG), so storage is optional today. The production configuration rules in features/keystone/lib/productionConfig.ts expect all of the first four once any S3 variable is set, and an HTTPS S3_ENDPOINT.
Optional integrations
Email (SMTP), calendar OAuth, credential encryption keys, SMS, and AI chat have their own variables. They are listed in Integrations and boundaries. Keep every provider secret out of source control.
Migrations and local run
The repository has 44 checked-in Prisma migration directories under migrations/.
npm run migrate # prisma migrate deploy
npm run dev # keystone build --no-ui, npm run migrate, next devnpm run dev and npm run build both run npm run migrate against DATABASE_URL. npm run migrate:gen runs prisma migrate dev, which can reset a database. Use a disposable development database for migration generation and never point these scripts at a database you cannot rebuild.
After start-up:
- Public booking pages:
/ - Operator dashboard:
/dashboard - GraphQL API:
/api/graphql
First admin and onboarding setup
- On an empty database, visit
/dashboard/initand create the first user. That user gets anAdminrole with dashboard, scheduling, onboarding, people, and role permissions. - Open the dashboard. The onboarding dialog calls
bootstrapSchedulingOnboarding, which requires thecanManageOnboardingrole flag and an idempotency key. - Pick a mode:
- Demo creates the fictional Northstar Studio team from
features/platform/onboarding/lib/seed.json: two members, a weekly schedule, two event types, three booking questions, a routing form with two rules, an embed, a workflow reminder, a notification subscription, a webhook subscription, a manual payment provider, a placeholder Google calendar connection inneeds_reauth, and two sample bookings. - Merchant creates your own team from a business name, slug, IANA time zone, owner display name, and SVG logo. Sample data is opt-in.
- Demo creates the fictional Northstar Studio team from
- Re-running the same request replays the result rather than creating duplicates.
- Connect calendars, conferencing, and payment providers from
/dashboard/platform/integrationsbefore you enable event types that depend on them.
Source checks
npm test # tsx --test tests/*.test.ts
npm run lint # eslint .
npm run audit:relationsPassing checks cover only the paths they exercise. They do not prove provider credentials, delivery, or payment behavior in your environment.
Deployment
The app is one Next.js server plus PostgreSQL. Optional pieces are SMTP, S3-compatible storage, and the external providers you choose.
- Build:
railway.tomluseskeystone build --no-ui && next build. The packagebuildscript also runsnpm run migrate; the Railway config moves migration to start time because the build environment cannot reach the database. - Start:
railway.tomlrunsnpm run migrate && next start. Alternatively,npm run start:production(scripts/start-production.sh) applies migrations, runs a scheduling outbox loop everySCHEDULING_WORKER_INTERVAL_SECONDS(default 30), and starts Next.js. - Background work: in production,
instrumentation.tsalso runs the scheduling outbox inside the Next.js server every 30 seconds. It processes webhooks, reminders, calendar writes and syncs, payments, conferencing, workflows, data jobs, invitations, analytics, and expiries. Workers claim rows with locks, so several replicas can run side by side.npm run worker:scheduling-outboxruns a single pass. - Environment: set
DATABASE_URL,SESSION_SECRET,BOOKING_MANAGE_SECRET,RATE_LIMIT_SECRET,TEAM_INVITATION_SECRET,SCHEDULING_PUBLIC_URL(HTTPS),SMTP_STORE_LINK(HTTPS base for password reset links), andSMTP_HOST,SMTP_PORT,SMTP_FROM(plusSMTP_USERandSMTP_PASSWORDif your server needs auth) so invitation, reminder, and reset emails can be sent.productionConfig.tslists these rules, but current source does not call its check at start-up, so verify the environment yourself. Add calendar, payment, and conferencing keys only for providers you use. In production, calendar, payment, and conferencing credential encryption refuses to run without its dedicated key. - Postgres and S3: use a managed PostgreSQL instance with backups. Configure S3 only if you add file or image fields.
The README also includes a Vercel deploy button that prompts for DATABASE_URL and SESSION_SECRET. On a serverless host, run migrations as a separate step and run the outbox worker somewhere long-lived, because the in-process loop needs a persistent Node.js server.
Openfront Scheduling
Self-hosted booking pages and team scheduling with event types, availability, bookings, routing forms, payments, calendars, workflows, and embeds.
Scheduling architecture and workflows
Schema by domain, booking, payment, team, routing, and workflow lifecycles, named GraphQL commands, and access control.