Getting started
Clone Openfront Restaurant, configure PostgreSQL, sessions, and S3 storage, apply migrations, create the first admin, run onboarding, and deploy.
This guide sets up one restaurant on your own machine or server. Onboarding creates sample setup records: store settings, menu, modifiers, kitchen stations, floors, sections, tables, and payment provider records. It does not create provider accounts or prove that payments, delivery zones, or staff permissions are ready for service.
Hosted app
The hosted app is at restaurant.openship.org. The operator dashboard is at /dashboard on the same host.
Requirements
- Node.js 20 or newer (
package.jsonsetsengines.nodeto>=20.0.0). - npm. The repository ships a
package-lock.json. - A PostgreSQL database you are allowed to migrate.
DATABASE_URLmust start withpostgresql://orpostgres://. - S3-compatible object storage for menu images. It is required in production.
Install
git clone https://github.com/openshiporg/openfront-restaurant.git
cd openfront-restaurant
npm installThe README mentions cp .env.example .env, but the repository does not include a .env.example file. Create .env yourself.
Environment
features/keystone/runtimeConfig.ts reads the core settings. When NODE_ENV is production, DATABASE_URL, SESSION_SECRET, and all five S3_* values are required and the app will not start without them. Outside production, the code falls back to a local database URL, a development session secret, and placeholder storage values. SESSION_SECRET must be at least 32 characters. The same secret signs the storefront cart proof cookie.
DATABASE_URL=postgresql://user:password@localhost:5432/openfront_restaurant
SESSION_SECRET=replace-with-at-least-32-random-characters # openssl rand -hex 32
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-compatible-endpointOptional settings read by the code:
| Variable | Used for |
|---|---|
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Stripe adapter and webhook signature checks |
NEXT_PUBLIC_STRIPE_KEY (falls back to STRIPE_PUBLISHABLE_KEY in some server code) | Stripe card form in storefront checkout and POS |
NEXT_PUBLIC_PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID | PayPal adapter and webhook verification |
NEXT_PUBLIC_PAYPAL_SANDBOX | PayPal sandbox mode. Sandbox is used unless this is false |
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS | Shared keys for the dashboard AI assistant |
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM | Password reset email, order notifications, and campaigns |
SMTP_STORE_LINK | Base link used in password reset emails |
MAIL_USER | Only checked to log Ethereal test-mail preview links |
PUBLIC_SIGNUPS_ALLOWED | Set to true to open /dashboard/signup |
NEXT_PUBLIC_BACKEND_URL | Points the storefront GraphQL client at another host. Defaults to the current origin |
NEXT_PUBLIC_VIEW_ORDER | Fallback dashboard field view order when the generated file is missing |
Keep .env out of source control.
Migrations and local run
npm run devnpm run dev runs keystone build --no-ui, applies the checked-in migrations with prisma migrate deploy (npm run migrate), and starts Next.js.
To change the schema, run npm run migrate:gen against a disposable database. It rebuilds Keystone, regenerates the dashboard view order, and runs keystone prisma migrate dev. Commit model changes and migrations together.
npm run dev and npm run build both apply migrations to whatever DATABASE_URL points at.
Once running:
- Storefront:
http://localhost:3000/ - Operator dashboard:
http://localhost:3000/dashboard - GraphQL API:
http://localhost:3000/api/graphql
The README lists /pos and /kds routes. Those do not exist. The POS is at /dashboard/platform/pos and the KDS is at /dashboard/platform/kds.
First user
When the database has no users, every request redirects to /dashboard/init. Create the first user there with a name, email, and password. That user gets an Admin role with every permission flag turned on. Then sign in at /dashboard/signin.
Dashboard sign-up at /dashboard/signup stays closed unless PUBLIC_SIGNUPS_ALLOWED=true. Password reset email only works when SMTP_HOST is set.
Onboarding
On a fresh install the dashboard sidebar shows an onboarding card ("Welcome to Openfront Restaurant"). It opens a dialog with three templates:
| Template | What it creates |
|---|---|
| Complete Restaurant Setup | Store settings for "Stack Kitchen", 6 categories, 23 menu items, 11 modifiers, 6 kitchen stations (Grill, Fryer, Salad, Bar, Dessert, Prep), 1 floor, 2 sections (Main Dining, Patio), 8 tables, and 3 payment methods |
| Basic Menu | A small subset: 3 categories, 3 items, 4 modifiers, 3 stations, 1 floor, 1 section, 2 tables, Cash and Credit Card |
| Custom Setup | Your own JSON for each section |
The seed data lives in features/platform/onboarding/lib/seed.json. Records are written through the ensureRestaurantOnboardingRecord mutation, which checks the permission for each record type and only accepts known fields.
The three payment methods map to provider records:
| Onboarding name | Provider record | Code |
|---|---|---|
| Credit Card | Stripe | pp_stripe_stripe |
| Mobile Payment | PayPal | pp_paypal_paypal |
| Cash | Manual / Cash | pp_system_default |
The seeded store has delivery turned off and an empty postal code allowlist. After onboarding, the dialog shows a readiness checklist (menu prices and modifiers, service hours and delivery zone, payment and refund flow, staff permissions). Every item is marked "Not verified". The app does not check them for you.
Onboarding status is stored on the user (not_started, in_progress, completed, dismissed).
Deploy
Scripts in package.json:
| Script | Effect |
|---|---|
npm run build | keystone build --no-ui, npm run migrate, then next build |
npm run migrate | prisma migrate deploy |
npm start | next start |
npm run lint | ESLint |
npm run test:offline | Node test runner over tests/*.test.ts |
The included railway.toml builds with keystone build --no-ui && next build and starts with npm run migrate && next start, because the build step cannot reach an internal database. The README also has a Vercel deploy button that asks for SESSION_SECRET and a Postgres database.
Either way, provision PostgreSQL, S3-compatible storage, and any SMTP and payment credentials yourself. All app processes must share the same database, SESSION_SECRET, and storage. There is no background worker in the source. Notifications, campaigns, webhook retries, and recovery jobs are run by operators from the dashboard.