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
| Path | What it holds |
|---|---|
features/keystone/models | 95 Keystone lists, registered in models/index.ts |
features/keystone/mutations | Named queries and mutations, registered in mutations/index.ts |
features/keystone/access.ts | Permission keys, tenant filters, store scope |
features/platform | Operator pages under /dashboard/platform and the onboarding dialog |
features/dashboard | Shared Keystone admin shell, sign-in, generic list and item pages |
features/storefront | Public site screens, data loaders, and server actions |
features/integrations/payment | Payment adapters (manual_external, hmac_json_v1) |
features/keystone/scripts | Outbox worker, alert worker, runtime seed |
docs/schema-map.md | A 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.
- Ring a sale.
openEmployeeShiftandopenRegisterSessionwith an opening float.createPosCart, thenaddPosCartLineby barcode or variant. Price comes from the store price book, tax from the store tax rate.applyPromotionToCart,applyLoyaltyRedemptionToCart, andverifyRestrictedSaleAgerun where needed.completePosSaletakes one or more tenders, posts stock from FIFO lots, posts cash, earns points, and returns a receipt. - Refund or void.
refundPosSalerefunds some or all lines across the original tenders, records a return, and can restock to the original lot, quarantine, or destroy.voidCompletedPosSalevoids a cash sale. Some cases need a manager PIN. - Close the till and the day.
recordCashMovementfor paid in, paid out, and safe drops.closeRegisterSessionwith counted cash gives over/short.closeEmployeeShift.generateStoreDayReconciliationandapproveStoreDayReconciliationbuild and lock the day.prepareBankDepositandtransitionBankDeposittrack the deposit.generateDailyAccountingExportbuilds a CSV batch. - Keep the catalog.
configureCatalogItemcreates or edits a product, variant, barcode, category, and tax category.setRetailPricepublishes a price-book entry.configurePromotion,configureStoreTaxRate, andconfigureAgeRestrictionPolicyset the rest. Product photos areProductImagerows stored in S3. - Keep stock right.
startInventoryCount,recordInventoryCountLine,transitionInventoryCount,approveInventoryCount.postInventoryAdjustmentfor corrections, spoilage, waste, and theft.draftInventoryTransferandtransitionInventoryTransfermove stock between stores with lot allocations. - Buy from vendors.
configureVendor,configureVendorItem,draftPurchaseOrder,transitionPurchaseOrder(submit, approve, cancel), andreceivePurchaseOrderwith accepted and rejected quantities, lot numbers, and expiry. - Prepared food.
configurePreparedFoodRecipe,startPrepBatch,completePrepBatch(consumes ingredients, makes an output lot), andrecordWasteEvent. - Fuel.
configureFuelTopology,setFuelPrice,recordFuelTankReading,recordFuelDelivery,authorizeFuelPayment,cancelFuelPaymentAuthorization,completeFuelTransaction,generateFuelReconciliation,approveFuelReconciliation. - Lottery.
configureLotteryGame,receiveLotteryPack,activateLotteryPack,recordLotteryPackSale,closeLotteryPack,generateLotterySettlement,approveLotterySettlement. - Money services.
configureMoneyServiceProvider,recordExternalMoneyServiceTransaction,voidExternalMoneyServiceTransaction,generateMoneyServiceSettlement,approveMoneyServiceSettlement. - Loyalty.
configureLoyaltyProgram,enrollLoyaltyAccount,adjustLoyaltyPoints,expireLoyaltyPoints. - Payments.
configurePaymentProvider,generatePaymentReconciliation,approvePaymentReconciliation. Signed provider webhooks arrive at/api/webhooks/payment/[providerId]. - Alerts and recovery.
evaluateOperationalAlertsandtransitionOperationalAlertfor cash, fuel, stock, expiry, and settlement variances.registerOfflineDevice,transitionOfflineDevice, andrecordOfflineReplayBatchreplay offline register work.claimOutboxEvents,completeOutboxEvent, andreplayOutboxEventdrive the outbox.createBusinessApiKeyandrevokeBusinessApiKeymanage 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:
| Area | Mutations |
|---|---|
| Setup and brand | onboardConvenienceBusiness, configureBusinessBrand, configureRegister, configureInventoryLocation |
| Staff and sessions | setOwnPosPin, openEmployeeShift, closeEmployeeShift, revokeCurrentUserSessions |
| Tills | openRegisterSession, recordCashMovement, closeRegisterSession |
| POS | createPosCart, addPosCartLine, updatePosCartLine, removePosCartLine, transitionPosCart, applyPromotionToCart, completePosSale, refundPosSale, voidCompletedPosSale |
| Catalog and pricing | configureCatalogItem, setRetailPrice, configureStoreTaxRate, configurePromotion |
| Restricted sales | configureAgeRestrictionPolicy, verifyRestrictedSaleAge, transitionComplianceIncident |
| Loyalty | configureLoyaltyProgram, enrollLoyaltyAccount, adjustLoyaltyPoints, applyLoyaltyRedemptionToCart, expireLoyaltyPoints |
| Purchasing | configureVendor, configureVendorItem, draftPurchaseOrder, transitionPurchaseOrder, receivePurchaseOrder |
| Inventory | startInventoryCount, recordInventoryCountLine, postInventoryAdjustment, transitionInventoryCount, approveInventoryCount, draftInventoryTransfer, transitionInventoryTransfer |
| Prepared food | configurePreparedFoodRecipe, startPrepBatch, completePrepBatch, recordWasteEvent |
| Fuel | configureFuelTopology, setFuelPrice, recordFuelTankReading, recordFuelDelivery, authorizeFuelPayment, cancelFuelPaymentAuthorization, completeFuelTransaction, generateFuelReconciliation, approveFuelReconciliation |
| Store day | generateStoreDayReconciliation, approveStoreDayReconciliation, prepareBankDeposit, transitionBankDeposit |
| Exports | configureExportDestination, generateDailyAccountingExport, transitionExportBatch |
| Alerts | evaluateOperationalAlerts, transitionOperationalAlert, recordOperationalHeartbeat |
| Lottery | configureLotteryGame, receiveLotteryPack, activateLotteryPack, recordLotteryPackSale, closeLotteryPack, generateLotterySettlement, approveLotterySettlement |
| Money services | configureMoneyServiceProvider, recordExternalMoneyServiceTransaction, voidExternalMoneyServiceTransaction, generateMoneyServiceSettlement, approveMoneyServiceSettlement |
| Payments | configurePaymentProvider, generatePaymentReconciliation, approvePaymentReconciliation |
| Offline and outbox | registerOfflineDevice, transitionOfflineDevice, recordOfflineReplayBatch, claimOutboxEvents, completeOutboxEvent, replayOutboxEvent |
| API keys | createBusinessApiKey, revokeBusinessApiKey |
| Storefront | updateStorefrontBasket |
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
Ownerrole with every permission, and an active owner employee for the signed-in user; Sales FloorandBack Roominventory locations;- a
Default Retailprice book andStandardandExempttax categories, with a standard tax rate; Register 1;- a
Store Rewardsloyalty program (1 point per dollar, 100 points worth one dollar); - an
External Terminalpayment provider using themanual_externaladapter; - 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 Unleadedgrade, one empty tank, one pump, and one nozzle, all offline with themanual_externalcontroller 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_externalrecords a card or wallet payment that a separate terminal already took. It accepts no webhooks.hmac_json_v1also accepts signed webhooks at/api/webhooks/payment/[providerId]. The request needsx-openfront-timestampandx-openfront-signature: v1=<hex>, an HMAC-SHA256 overtimestamp.body, within five minutes. It handlespayment.captured,payment.failed,refund.completed, andrefund.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.