Getting started with Pharmacy
Clone the source, configure PostgreSQL, sessions, and S3 storage, apply migrations, create the first user, run onboarding, and deploy.
Requirements
- Node.js 20 or later (
package.jsonenginesis>=20.0.0). The README recommends Node.js 22 for Next.js 16. - npm. The repository ships a
package-lock.json. - A PostgreSQL database you are allowed to migrate.
- S3-compatible object storage. It is required when
NODE_ENV=production.
Hosted app
The hosted app is at pharmacy.openship.org.
Install
git clone https://github.com/openshiporg/openfront-pharmacy.git
cd openfront-pharmacy
npm install
cp .env.example .envEnvironment
features/keystone/index.ts will not start without DATABASE_URL and SESSION_SECRET. It also rejects a session secret shorter than 32 characters. The five S3 values fall back to development placeholders outside production. In production they are required.
DATABASE_URL=postgresql://user:password@127.0.0.1:5432/openfront_pharmacy
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-endpointThe code also reads these optional settings:
| Variable | Used for |
|---|---|
SHADOW_DATABASE_URL | Prisma shadow database, used when generating migrations |
PUBLIC_SIGNUPS_ALLOWED | Set to true to open the dashboard sign-up page (/dashboard/signup). Off by default |
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, SMTP_STORE_LINK | Password-reset email and links in emails |
NEXT_PUBLIC_STRIPE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Stripe payment adapter and the storefront card step |
NEXT_PUBLIC_PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, NEXT_PUBLIC_PAYPAL_SANDBOX | PayPal payment adapter (see Payment providers) |
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS | Dashboard AI chat (/api/completion) |
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, and starts Next.js. The migrations/ folder holds 23 migrations: seven inherited from the starter and sixteen Pharmacy migrations.
To change the schema, run npm run migrate:gen against a disposable database. It rebuilds the schema, regenerates the dashboard view order, and runs keystone prisma migrate dev. Commit model changes and their migrations together.
npm run dev applies migrations to whatever DATABASE_URL points at.
Once running:
- Storefront:
/ - Customer account:
/account - Operator dashboard:
/dashboard - GraphQL API:
/api/graphql
The package defines dev, build, start, lint (ESLint), migrate, migrate:gen, and release:migrate. It has no test or typecheck script. The README also lists npm run worker:outbox, but that script is not in package.json.
First user
While the database has no users, every request redirects to /dashboard/init. The first account created there gets an Admin role with all 19 permissions. Later accounts never get that role automatically.
Customer accounts are created on the storefront through registerPharmacyCustomer. That mutation refuses to run while no users exist, so the first user must come from /dashboard/init.
Give other staff only the permissions their job needs. A role with dashboard access does not make someone a pharmacist. Licensure is checked by your pharmacy. The app stores the evidence an operator records.
Onboarding
The dashboard shows onboarding cards to users with canManageOnboarding until onboarding is completed or dismissed. The dialog runs runPharmacyOnboarding with one of two templates:
| Template | What it creates |
|---|---|
Basic Setup (minimal) | Organization "Cedar & Elm Pharmacy", one location, owner membership for you, the "Pay at pickup" provider (installed), one supplier, two OTC products with opening-stock lots, and four integration provider records (eRx, Wholesaler, Insurance, Tax), all unconfigured |
Complete Setup (full) | Everything above, plus a Stripe provider (not installed), a second supplier, nine products in total (eight OTC and one prescription item, "Amoxicillin 500 mg Capsules"), one drug reference, three sample patients, and two unverified prescribers |
Onboarding also records a cash policy with a 0% tax rate, reviewed by you. It creates no prescriptions.
Onboarding runs only on a new install. It fails if you already have an active membership, or if the seed organization or location code already exists. If you already own the seed organization, it marks onboarding complete and returns the existing counts. setPharmacyOnboardingStatus only changes your own onboarding status.
Before dispensing
The Credential review page (/dashboard/platform/credentials) shows a setup checklist. Dispensing needs all of these:
- A pharmacist credential review on a staff membership (
recordPharmacyStaffCredentialReview). - A prescriber with an NPI and a credential review (
recordPharmacyPrescriberCredentialReview). - A drug reference review that marks the drug active and non-controlled (
recordPharmacyDrugReferenceReview). - A recorded cash and sales-tax policy (
recordPharmacyCashPolicy). - A stocked prescription product.
- A fill policy for the organization (
recordPharmacyFillPolicy).
Next: Prepare to dispense, then Deployment.