Openfront Convenience

Convenience-store POS, cash, catalog, stock, purchasing, loyalty, restricted sales, prepared food, fuel, lottery, and money services.

Source · Live site · Catalog

Openfront Convenience is an open-source operations app for convenience stores. Staff ring sales at a register, run tills and the store day, keep the catalog and stock, buy from vendors, and run optional fuel, lottery, money-service, and prepared-food work. Customers get a public catalog site with an account page for points and receipts.

It is a Next.js 16 app with a Keystone 6 GraphQL backend and PostgreSQL. One database holds every business that uses the install.

The public site is a catalog with rewards and receipts. Sales are rung at the register. Card payments, fuel, lottery, money services, and accounting exports record references and outcomes that staff or a signed adapter confirm. See Integrations and the dashboard pages starting at Overview.

Start with Getting started.

Where things live

PathWhat it holds
features/keystone/models95 Keystone lists, registered in models/index.ts
features/keystone/mutationsNamed queries and mutations, registered in mutations/index.ts
features/keystone/access.tsPermission keys, tenant filters, store scope
features/platformOperator pages under /dashboard/platform and the onboarding dialog
features/dashboardShared Keystone admin shell, sign-in, generic list and item pages
features/storefrontPublic site screens, data loaders, and server actions
features/integrations/paymentPayment adapters (manual_external, hmac_json_v1)
features/keystone/scriptsOutbox worker, alert worker, runtime seed
docs/schema-map.mdA table of every list, its states, and the page that shows it

Tenancy and permissions

Business is the tenant root. A Store belongs to one business. Almost every other record carries a direct business link, and most also carry a store link. List access rules filter reads by the business and stores the signed-in person can reach.

A User is a sign-in identity. Staff access comes from an Employee row that joins a user to one business, one business-owned Role, and a set of stores. Only active employees count. A role is a set of 47 permission checkboxes, such as canAccessDashboard, canManageCatalog, canReadCash, canManageFuel, canManageApiKeys, canUseAssistant, and canReadAudit. Most areas have a read and a manage permission.

The first user created on an empty database is marked as an approved platform admin. Only a platform admin can create a new business through onboarding.

Machine access uses ApiKey bearer tokens. A key belongs to one business, has a list of scopes, and is limited to chosen stores or all stores. The plaintext token is shown once. Machine keys cannot open the dashboard UI.

Data model

Money is stored as integer cents. Fuel prices use mills per gallon. Rates use basis points or fractions. Quantities use fixed four-decimal values.

  • Tenancy and people: Business, Store, User, Employee, Role, Shift.
  • Registers and cash: Register, RegisterSession, CashMovement.
  • Selling: Cart, CartLine, Sale, SaleLine, Tender, Refund, RefundLine, RefundTenderAllocation, Return, ReturnLine.
  • Catalog: Product, ProductVariant, ProductBarcode, ProductCategory, ProductImage.
  • Pricing and tax: PriceBook, PriceBookEntry, TaxCategory, TaxRate, Promotion, PromotionRule, PromotionRedemption.
  • Inventory: InventoryLocation, StockLevel, InventoryLot, InventoryLedgerEntry, InventoryCount, InventoryCountLine, InventoryTransfer, InventoryTransferLine, InventoryTransferLotAllocation.
  • Purchasing: Vendor, VendorItem, PurchaseOrder, PurchaseOrderLine, Receiving, ReceivingLine.
  • Customers: LoyaltyProgram, LoyaltyAccount, LoyaltyLedgerEntry.
  • Restricted sales: CompliancePolicy, AgeVerification, ComplianceIncident.
  • Prepared food: Recipe, RecipeIngredient, PrepBatch, WasteEvent.
  • Fuel: FuelSite, FuelGrade, FuelTank, FuelTankReading, FuelPump, FuelNozzle, FuelPrice, FuelDelivery, FuelTransaction, FuelReconciliation.
  • Lottery: LotteryGame, LotteryPack, LotteryPackEvent, LotterySettlement.
  • Money services: MoneyServiceProvider, MoneyServiceTransaction, MoneyServiceSettlement.
  • Store-day close: StoreDay, StoreDayLine, BankDeposit.
  • Payments: PaymentProvider, PaymentReconciliation, PaymentReconciliationLine, WebhookEvent.
  • Accounting exports: ExportDestination, ExportBatch, ExportLine.
  • Offline registers: OfflineDevice, OfflineOperationBatch, OfflineOperation.
  • Operations and system: OperationalAlert, AlertEvaluation, OperationalHeartbeat, OutboxEvent, OutboxAttempt, IdempotencyKey, AuditEvent, AuthenticationThrottle, ApiKey.

Most operational lists have generic create and update turned off. They change only through a named mutation. Sales, sale lines, tenders, refunds, ledger entries, cash movements, readings, audit events, and idempotency keys are written only by the system. docs/schema-map.md in the source repo marks each list as op-only, immutable, or CRUD.

Workflows

These are the main paths the code supports. Each step is a named mutation. Each mutation takes an idempotency key, checks business and store scope, and writes an audit event.

  1. Ring a sale. openEmployeeShift and openRegisterSession with an opening float. createPosCart, then addPosCartLine by barcode or variant. Price comes from the store price book, tax from the store tax rate. applyPromotionToCart, applyLoyaltyRedemptionToCart, and verifyRestrictedSaleAge run where needed. completePosSale takes one or more tenders, posts stock from FIFO lots, posts cash, earns points, and returns a receipt.
  2. Refund or void. refundPosSale refunds some or all lines across the original tenders, records a return, and can restock to the original lot, quarantine, or destroy. voidCompletedPosSale voids a cash sale. Some cases need a manager PIN.
  3. Close the till and the day. recordCashMovement for paid in, paid out, and safe drops. closeRegisterSession with counted cash gives over/short. closeEmployeeShift. generateStoreDayReconciliation and approveStoreDayReconciliation build and lock the day. prepareBankDeposit and transitionBankDeposit track the deposit. generateDailyAccountingExport builds a CSV batch.
  4. Keep the catalog. configureCatalogItem creates or edits a product, variant, barcode, category, and tax category. setRetailPrice publishes a price-book entry. configurePromotion, configureStoreTaxRate, and configureAgeRestrictionPolicy set the rest. Product photos are ProductImage rows stored in S3.
  5. Keep stock right. startInventoryCount, recordInventoryCountLine, transitionInventoryCount, approveInventoryCount. postInventoryAdjustment for corrections, spoilage, waste, and theft. draftInventoryTransfer and transitionInventoryTransfer move stock between stores with lot allocations.
  6. Buy from vendors. configureVendor, configureVendorItem, draftPurchaseOrder, transitionPurchaseOrder (submit, approve, cancel), and receivePurchaseOrder with accepted and rejected quantities, lot numbers, and expiry.
  7. Prepared food. configurePreparedFoodRecipe, startPrepBatch, completePrepBatch (consumes ingredients, makes an output lot), and recordWasteEvent.
  8. Fuel. configureFuelTopology, setFuelPrice, recordFuelTankReading, recordFuelDelivery, authorizeFuelPayment, cancelFuelPaymentAuthorization, completeFuelTransaction, generateFuelReconciliation, approveFuelReconciliation.
  9. Lottery. configureLotteryGame, receiveLotteryPack, activateLotteryPack, recordLotteryPackSale, closeLotteryPack, generateLotterySettlement, approveLotterySettlement.
  10. Money services. configureMoneyServiceProvider, recordExternalMoneyServiceTransaction, voidExternalMoneyServiceTransaction, generateMoneyServiceSettlement, approveMoneyServiceSettlement.
  11. Loyalty. configureLoyaltyProgram, enrollLoyaltyAccount, adjustLoyaltyPoints, expireLoyaltyPoints.
  12. Payments. configurePaymentProvider, generatePaymentReconciliation, approvePaymentReconciliation. Signed provider webhooks arrive at /api/webhooks/payment/[providerId].
  13. Alerts and recovery. evaluateOperationalAlerts and transitionOperationalAlert for cash, fuel, stock, expiry, and settlement variances. registerOfflineDevice, transitionOfflineDevice, and recordOfflineReplayBatch replay offline register work. claimOutboxEvents, completeOutboxEvent, and replayOutboxEvent drive the outbox. createBusinessApiKey and revokeBusinessApiKey manage machine keys.

Named GraphQL operations

All custom operations are declared in features/keystone/mutations/index.ts. GraphQL is served at /api/graphql.

Queries: storefrontCatalog, storefrontBasket, storefrontHistory, storefrontRewards, conveniencePublicStorefront, conveniencePlatformProjection, conveniencePlatformNavigation, convenienceReportingSummary, posSaleReceipt, posOperationOutcome, downloadExportArtifact, assistantCapability, redirectToInit.

Mutations, by area:

AreaMutations
Setup and brandonboardConvenienceBusiness, configureBusinessBrand, configureRegister, configureInventoryLocation
Staff and sessionssetOwnPosPin, openEmployeeShift, closeEmployeeShift, revokeCurrentUserSessions
TillsopenRegisterSession, recordCashMovement, closeRegisterSession
POScreatePosCart, addPosCartLine, updatePosCartLine, removePosCartLine, transitionPosCart, applyPromotionToCart, completePosSale, refundPosSale, voidCompletedPosSale
Catalog and pricingconfigureCatalogItem, setRetailPrice, configureStoreTaxRate, configurePromotion
Restricted salesconfigureAgeRestrictionPolicy, verifyRestrictedSaleAge, transitionComplianceIncident
LoyaltyconfigureLoyaltyProgram, enrollLoyaltyAccount, adjustLoyaltyPoints, applyLoyaltyRedemptionToCart, expireLoyaltyPoints
PurchasingconfigureVendor, configureVendorItem, draftPurchaseOrder, transitionPurchaseOrder, receivePurchaseOrder
InventorystartInventoryCount, recordInventoryCountLine, postInventoryAdjustment, transitionInventoryCount, approveInventoryCount, draftInventoryTransfer, transitionInventoryTransfer
Prepared foodconfigurePreparedFoodRecipe, startPrepBatch, completePrepBatch, recordWasteEvent
FuelconfigureFuelTopology, setFuelPrice, recordFuelTankReading, recordFuelDelivery, authorizeFuelPayment, cancelFuelPaymentAuthorization, completeFuelTransaction, generateFuelReconciliation, approveFuelReconciliation
Store daygenerateStoreDayReconciliation, approveStoreDayReconciliation, prepareBankDeposit, transitionBankDeposit
ExportsconfigureExportDestination, generateDailyAccountingExport, transitionExportBatch
AlertsevaluateOperationalAlerts, transitionOperationalAlert, recordOperationalHeartbeat
LotteryconfigureLotteryGame, receiveLotteryPack, activateLotteryPack, recordLotteryPackSale, closeLotteryPack, generateLotterySettlement, approveLotterySettlement
Money servicesconfigureMoneyServiceProvider, recordExternalMoneyServiceTransaction, voidExternalMoneyServiceTransaction, generateMoneyServiceSettlement, approveMoneyServiceSettlement
PaymentsconfigurePaymentProvider, generatePaymentReconciliation, approvePaymentReconciliation
Offline and outboxregisterOfflineDevice, transitionOfflineDevice, recordOfflineReplayBatch, claimOutboxEvents, completeOutboxEvent, replayOutboxEvent
API keyscreateBusinessApiKey, revokeBusinessApiKey
StorefrontupdateStorefrontBasket

Generated Keystone CRUD still exists for lists that allow it, such as Store, Role, Employee, and User. The staff page creates people through createUser and createEmployee.

Onboarding

On first sign-in the dashboard shows an onboarding dialog. It submits onboardConvenienceBusiness. The form starts blank, or from a starter store filled in from features/platform/onboarding/lib/seed.json. It asks for business name and code, timezone, store name and code, currency, address, phone, standard tax percent, whether the store sells fuel, whether to add starter products, and whether the storefront is draft or published.

One run creates, in one transaction:

  • the business and its first store, set as the storefront store;
  • an Owner role with every permission, and an active owner employee for the signed-in user;
  • Sales Floor and Back Room inventory locations;
  • a Default Retail price book and Standard and Exempt tax categories, with a standard tax rate;
  • Register 1;
  • a Store Rewards loyalty program (1 point per dollar, 100 points worth one dollar);
  • an External Terminal payment provider using the manual_external adapter;
  • with starter products on: five categories (Cold Drinks, Snacks & Pantry, Coffee Bar, Prepared Food, Everyday Essentials) and 18 products, each with one variant, a barcode, and a price-book entry. Starter variants track stock but allow negative stock until real stock is received;
  • with fuel on: a fuel site, one Regular Unleaded grade, one empty tank, one pump, and one nozzle, all offline with the manual_external controller key.

The run sets the user's onboarding status to completed. A replay with the same idempotency key returns the first result. "Skip for now" marks onboarding dismissed.

Integrations

Payments. Two adapters are registered:

  • manual_external records a card or wallet payment that a separate terminal already took. It accepts no webhooks.
  • hmac_json_v1 also accepts signed webhooks at /api/webhooks/payment/[providerId]. The request needs x-openfront-timestamp and x-openfront-signature: v1=<hex>, an HMAC-SHA256 over timestamp.body, within five minutes. It handles payment.captured, payment.failed, refund.completed, and refund.failed. Duplicate event IDs are replayed, not re-applied.

Both adapters require a short-lived signed authority proof for each non-cash tender and refund. The signing key is read from an env://NAME credential reference, and the value must be at least 32 characters. The adapters verify proofs and webhooks; the card network call itself happens in your terminal or processor.

Credential references. Payment, money-service, export, and fuel configuration take env:// or secret-manager references. Inline secret-like values are rejected.

Fuel, lottery, money services, exports. These record what an operator or an outside system confirms. Accounting exports produce a CSV artifact (up to 5 MiB) that staff download from the Exports page. Staff mark delivery to a destination with transitionExportBatch.

Outbox worker. npm run worker:outbox claims outbox events with a machine key and posts them, HMAC-signed, to an HTTPS sink. It reads OPENFRONT_GRAPHQL_URL, OPENFRONT_API_TOKEN, OUTBOX_BUSINESS_ID, OUTBOX_SINK_URL, and OUTBOX_SIGNING_VALUE (32+ characters).

Alert worker. npm run worker:alerts calls evaluateOperationalAlerts. It reads OPENFRONT_GRAPHQL_URL, OPENFRONT_API_TOKEN, and optional thresholds ALERT_CASH_VARIANCE_THRESHOLD_CENTS, ALERT_FUEL_VARIANCE_THRESHOLD_GALLONS, and ALERT_EXPIRY_WINDOW_DAYS.

Email. Password reset email uses SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, and SMTP_STORE_LINK.

Assistant. The dashboard assistant at /api/completion uses OpenRouter (OPENROUTER_API_KEY, OPENROUTER_MODEL, optional OPENROUTER_MAX_TOKENS) and needs the canUseAssistant permission. It talks to a read-only MCP endpoint at /api/mcp-transport/http with four tools: listModels, searchModels, getFieldsForType, and queryData (scalar fields only, at most 50 rows). It cannot run mutations.

On this page