Openfront Barbershop

Services, barbers and chairs, booking, walk-in queue, POS, customers, payroll, inventory, and reports for one shop or several.

Source · Live site · Catalog

Openfront Barbershop is a self-hosted Next.js and Keystone app for a barbershop. Customers browse services and barbers, book a time, join the walk-in line, and see their visits and balances. Staff run the booking board, walk-in queue, POS checkout, customer records, packages and memberships, barber schedules, payroll, retail stock, policies, and reports from /dashboard/platform.

Start with Getting started and Deployment. Each dashboard area has its own page, starting with the booking board. Task guides are under How-to guides. The API is in the API reference.

Online payment is a Stripe Checkout for booking deposits, available when STRIPE_SECRET_KEY is set and Stripe is set up on the Payments page. Counter checkout takes cash, card (recorded by staff), or a prepaid package visit. Retail products, packages, and memberships are sold at the counter.

Tenancy

Shop is the tenant. Almost every merchant record has a shop relation. Reads and writes are scoped to the caller's active shop (features/keystone/access.ts).

  • A ShopMembership links a User to a Shop with a role: owner, manager, front_desk, barber, or viewer. Status moves invited → active → suspended or revoked. Memberships are not deleted, and each shop keeps at least one active owner.
  • The membership role narrows the user's global Role flags inside that shop. For example, a barber membership allows appointments, queue, customers, and customer notes, but not services, staff, payments, or reports. A viewer can open the dashboard and view reports.
  • selectActiveShop switches the active shop. The selector is on the Store settings page.
  • If a dashboard user has no shop, the platform pages show a shop access panel instead. Instance admins (users with both canManagePeople and canManageRoles) can take over an existing shop with claimShopOwnership. Everyone else needs an owner or manager to add them.
  • The storefront shows exactly one shop: the single active shop with isPublic set. If none or more than one is public, public booking is unavailable.

Data model

The schema has 49 Keystone lists in features/keystone/models. The repository's docs/schema-map.md describes each one.

AreaLists
Shop and accessShop, ShopSettings (legacy; superseded by Shop fields), ShopMembership, User, Role
Service menuServiceCategory, Service, ServicePackage, ServicePackageItem, MembershipPlan
Team and timeBarber, BarberChair, BarberSchedule, ScheduleException, StaffShift, PractitionerCredential
BookingsBookingPolicy, Appointment, AppointmentChangeEvent, AppointmentLineItem, NoShowPolicy, NoShowEvent, WalkInQueueEntry, WaitlistEntry, GuestBookingCommand
CustomersCustomer, CustomerNote, GroomingNote, WaiverForm, CustomerPackage, PackageUsage, MembershipEvent
MoneyCheckoutTransaction, CashRefund, PaymentProvider, PaymentSession, PaymentReconciliation, PaymentWebhookEvent, Tip, Commission, CommissionPayout
RetailProduct, Supplier, InventoryLot, InventoryMovement, ProductSaleLine
SystemLifecycleOutboxEvent

Many state-bearing lists cannot be created or updated through generic CRUD. They change only through named commands. Those commands take an idempotency key, write a ledger row where one exists, and record an event in LifecycleOutboxEvent.

Key lifecycles:

  • Appointment: requested → confirmed → checked_in → in_chair → completed. completed is reached only through a paid checkout. cancelled and no_show are terminal. Check-in and seating happen only on the visit's shop day. A no-show can be marked only after the start time plus the policy grace period.
  • Walk-in: waiting → texted → in_chair → served, or left. Seating goes through seatWalkInQueueEntry, which creates an appointment. Waiting entries from earlier days close as left.
  • Waitlist: waiting → notified → booked or expired.
  • Commission: projected → approved → paid, or voided.
  • CheckoutTransaction: open → paid → partially_refunded or refunded, or voided.
  • CustomerPackage: active ↔ paused → used_up, expired, or cancelled.
  • PractitionerCredential: active ↔ suspended → revoked or expired. Credentials are only enforced for services marked credentialRequired.
  • BookingPolicy: versions move active → retired. Each booking stores a snapshot of the policy in force.

Named GraphQL operations

These custom operations are defined in features/keystone/mutations/index.ts. Generated Keystone CRUD exists for lists that allow it.

Public and account queries: getPublicStorefrontSettings, getPublicServiceCatalog, getPublicBarbers, getPublicBarberWeek, getPublicProducts, getPublicMembershipOffers, getPublicBookingEligibility, getPublicPaymentOptions, checkBarberAvailability, getPublicAppointment, getPublicQueue, getPublicQueueEntry, getPublicWalkInStatus, accountBookings, accountBalances, exportMyAccountData.

Public and account mutations: createOnlineBooking, recoverGuestBooking, createAccountBooking, recoverAccountBooking, managePublicAppointment (cancel or reschedule, with a fee preview), signPublicWaiver, startDepositCheckout, confirmDepositCheckout, createPublicQueueEntry.

Dashboard projections: platformBooking, platformQueue, platformCustomers, platformPos, platformReports, platformServices, platformStaff, platformOperations, platformTeam, activeShopStorefrontSettings, claimableShops.

Dashboard commands:

AreaOperations
Shop and teamselectActiveShop, claimShopOwnership, addShopMemberByEmail, updateActiveShopStorefrontSettings, runBarbershopOnboarding
AppointmentscreateStaffAppointment, manageStaffAppointment, transitionAppointment, markAppointmentNoShow
Walk-ins and waitlistcreateStaffQueueEntry, assignWalkInQueueEntry, transitionQueueEntry, seatWalkInQueueEntry, manageWaitlist
Checkout and paymentscheckoutAppointment, refundCashCheckout, recordProductSale, returnProductSaleLine, ensureDefaultPaymentProvider, initiateAppointmentPaymentSession, captureAppointmentPaymentSession, refundAppointmentPaymentSession, cancelAppointmentPaymentSession, reconcilePaymentSession
CustomersaddCustomerNote, setCustomerStatus, setCustomerConsent, issueCustomerWaiver, manageMembership (purchase, purchase package, renew, pause, resume, cancel)
StaffmanageStaffShift, manageScheduleException, linkBarberAccount, setPractitionerCredentialStatus
Payroll and stocktransitionCommission, payApprovedCommissions, receiveInventoryLot, recordInventoryMovement

Main workflow

  1. Set up the shop: name, timezone, hours, brand color, and publication on Store settings.
  2. Build the menu: categories, services (price, duration, deposit, barbers, licensed flag, waiver), packages, and membership plans.
  3. Add barbers, chairs, and weekly hours. Add shifts, time off, and shop closures.
  4. Publish a booking policy version and set a no-show policy.
  5. Customers book online or join the walk-in line. Staff can also book or add walk-ins from the dashboard.
  6. On the day, staff confirm, check in, and seat. Walk-ins are seated into an appointment.
  7. POS checkout takes payment (cash, card recorded by staff, or a package visit), records tips and line items, and creates projected commissions.
  8. Payroll approves and pays commissions per barber. Inventory receives deliveries and records waste and count corrections.
  9. Reports show revenue, appointments, walk-ins, and barber and service performance for a date range.

Integrations

  • Payments: two adapters are registered in features/integrations/payment. manual is offline (pay in person) and is the default provider (pp_manual). stripe uses Stripe Checkout for booking deposits only, needs STRIPE_SECRET_KEY, and confirms payment by reading the Checkout Session back from Stripe. Stripe deposits are confirmed by reading the Checkout Session back from Stripe. POST /api/payment-webhooks/[providerCode] serves providers that register a webhook handler. checkoutAppointment rejects a processor payment session at the counter.
  • Email: SMTP sends password reset emails, booking and status emails, and reminders about a day before a visit. The reminder sweep runs on ordinary traffic (storefront queue and dashboard projections), at most every five minutes per server. Without SMTP_HOST, SMTP_USER, and SMTP_PASSWORD, emails are not sent.
  • Images: service, barber, and product images use S3-compatible storage configured with the S3_* variables.
  • AI assistant and MCP: /api/completion and /api/mcp-transport/[transport] return 403 unless ENABLE_AI_TOOLS=true. The chat also needs OPENROUTER_API_KEY and OPENROUTER_MODEL. The MCP tools are generic Keystone tools (listModels, queryData, searchModels, getFieldsForType, lookupInputType, createData, updateData, deleteData, modelSpecificSearch). They run with the caller's session cookie.

On this page