Openfront Dealership

Vehicle inventory, CRM, deals, finance and F&I records, delivery, service, parts, payments, and accounting batches.

Source · Live site · Catalog

Openfront Dealership is a self-hostable vehicle storefront and dealership operations app. Shoppers browse and compare vehicles and send sales, test-drive, trade-in, financing, and service requests. Staff work those requests through inventory, CRM, deal, finance, delivery, service, parts, payment, and accounting desks.

It is built with Next.js 16, React 19, Keystone 6 (GraphQL), PostgreSQL through Prisma, and Tailwind CSS. Start with Getting started.

Financing is intake and record keeping. Lender decisions, compliance checks, contract status, and accounting postings are entered by staff.

Code layout

PathWhat it holds
features/keystone/models71 registered lists (models/index.ts)
features/keystone/mutationsCustom GraphQL queries and commands (mutations/index.ts)
features/keystone/lifecycleState machines, pricing, and the audit/outbox writer
features/platformDealership operator desks under /dashboard/platform
features/dashboardShared Keystone admin shell, sign-in, and generic list pages
features/storefrontPublic site and customer account
features/integrationsPayment, financing, and inventory-channel adapters

Tenancy and access

  • DealerGroup is the tenant root. A Rooftop is one store. Most operating lists carry a rooftop field and are filtered to the rooftops the user can reach.
  • Access comes from the user's Role. Operating permissions can also come from an active DealerGroupMembership (optionally all rooftops) or RooftopMembership, for that scope only. Memberships change only through changeDealerMembership, which writes a MembershipAuditEvent.
  • Some permissions are role-only: people, users, roles, settings, onboarding, API keys, and payments.
  • Leads, deals, approvals, and trade-ins are limited to the assigned salesperson unless the user has canViewAllLeads across all rooftops. Dealer cost needs inventory or financing management across all rooftops.
  • API keys (ApiKey) are owned by a user and limited to scopes: read_inventory, write_inventory, write_leads, write_deals, write_service, read_reports.
  • Most operational lists are command-only. Their Keystone create and update are denied, so writes go through named mutations. Each command writes a DomainAuditEvent and a DomainOutboxEvent in the same transaction, keyed by an idempotency key.

Data model

AreaLists
OrganizationUser, Role, DealerGroup, Rooftop, DealerGroupMembership, RooftopMembership, MembershipAuditEvent, DealershipProfile, ApiKey, AuthenticationThrottle
InventoryMake, Model, Vehicle, VehicleImage, VehicleConditionReport, VehicleHistory, PriceHistory, InventoryAcquisition, InventoryAcquisitionEvent, InventoryStock, InventoryStockEvent, InventoryReconWorkItem, InventoryMerchandisingVersion, InventoryFeedRun, InventoryFeedError
CRMCustomer, CustomerMergeEvent, CustomerHousehold, CustomerContactPreferenceEvent, Lead, LeadAssignmentEvent, LeadRoutingRule, CustomerInteraction, Task, SalesVisit, TestDrive, TradeIn
Deals and financeDeal, DealApproval, FinancingApplication, LenderSubmission, LenderDecision, FinanceProduct, FinanceProductQuote, FinanceMenuVersion, FinanceComplianceCheck, FinanceContractPackage, FinanceFundingCase, FinanceProductCancellation
DeliveryDeliveryCase, WeOweItem
Service and partsServiceAppointment, ServiceOrder, RepairOrderLineItem, ServiceLaborOperation, ServicePackage, PartsInventory, PartsMovement, PartsPurchaseOrder
Payments and accountingPaymentProvider, PaymentTransaction, PaymentRefund, PaymentProviderEvent, AccountingMapping, AccountingBatch, AccountingProviderResponse
Reporting and systemSavedReport, ReportRun, RetentionPolicy, DomainAuditEvent, DomainOutboxEvent

A CRM Customer is separate from the login User. Public submissions find or create a Customer per rooftop by email or phone. A storefront account is linked to Customers only after it confirms its email with confirmCustomerEmailVerification.

Main workflows

Inventory. A unit enters through intakeInventoryVehicle (one step) or startInventoryAcquisition then transitionInventoryAcquisition (appraised, approved, purchased, arrived, reconditioning, frontline) and establishInventoryStock. Staff move stock, record costs, open and close recon work, edit listing content and photos, then submit and approve or reject merchandising. A vehicle shows on the storefront only when its current revision is approved, recon is complete, it is frontline, and its status is available. recordInventoryFeedRun records a feed run. Price changes append PriceHistory.

Leads. Public forms create a Lead and the matching record. Staff can open a lead (openLead), edit it (updateLeadDetails), route it by rule (routeLead) or by hand (transferLeadOwnership), log interactions, add and complete tasks, run cadences, schedule sales visits, and close it won or lost (transitionLead).

Deals. createDealPencil starts a deal at pencil, with tax and doc fee from the dealership profile. Staff save, present, and accept the worksheet (cash, finance, or lease), pick a lender offer for finance or lease, then move the deal through submitted, approved, financed, and delivered, or cancel it. Submitting reserves the vehicle. Delivery marks it sold.

Finance and F&I. Applications are queued with the operator-recorded adapter and decisions are entered with recordLenderDecision. The finance desk records product quotes, the F&I menu and customer choice, compliance checks (identity, OFAC, red-flag, privacy), the contract package, prerequisite review, funding, cash deposits (recordCashDealDeposit), and product cancellations.

Delivery. manageDeliveryCase schedules, records arrival and the checklist, takes the customer acknowledgment, or cancels. completeDealDelivery delivers the deal. manageWeOweItem tracks promises after the sale.

Service and parts. Appointments move through transitionServiceAppointment. openServiceOrder opens a repair order, which moves estimate, approved, in-progress, completed, invoiced, paid. Staff add, edit, and remove labor, part, and sublet lines, set tax, and run labor operations. Parts have a movement ledger and purchase orders. Receiving a purchase order posts receipt movements.

Payments and accounting. createPaymentCheckout creates a checkout for an approved deal or an invoiced repair order. Webhooks update transactions and refunds. stageAccountingBatch builds a balanced batch from mappings, and manageAccountingBatch records posting, failure, reconciliation, and lock.

Named GraphQL operations

Custom operations are declared in features/keystone/mutations/index.ts.

GroupOperations
Storefront readspublicInventoryVehicles, publicInventoryVehicle, publicInventoryVehicleRecord, publicInventoryCount, storefrontProfile
Public submissionssubmitInquiry, submitTestDrive, submitTradeIn, submitFinancing, submitServiceBooking
Customer accountregisterCustomerAccount, requestCustomerEmailVerification, confirmCustomerEmailVerification, cancelCustomerBooking, rescheduleCustomerBooking, customerRequestHistory, customerRequestDetail, customerPurchases, customerServiceHistory, customerServiceOrder, customerPayments, customerEmailLinkStatus
Workspace readsinventoryWorkspace, crmWorkspace, dealDeskWorkspace, dealWorkspace, dealLenderOffers, financeWorkspace, deliveryWorkspace, fixedOpsWorkspace, accountingWorkspace, operationalReport, auditSearch, domainTimeline, customerDuplicateCandidates
InventoryintakeInventoryVehicle, startInventoryAcquisition, transitionInventoryAcquisition, establishInventoryStock, moveInventoryStock, recordInventoryCost, openInventoryReconWorkItem, transitionInventoryReconWorkItem, updateInventoryMerchandisingDraft, updateInventoryVehicleDetails, submitInventoryMerchandising, decideInventoryMerchandising, recordInventoryFeedRun, addInventoryVehiclePhoto, arrangeInventoryVehiclePhotos, removeInventoryVehiclePhoto
CRMopenLead, updateLeadDetails, transitionLead, recordLeadInteraction, createLeadTask, completeTask, transferLeadOwnership, routeLead, configureLeadRoutingRule, enrollLeadCadence, advanceLeadCadence, manageSalesVisit, updateCustomerContact, updateCustomerCommunicationPreferences, mergeCustomers, linkCustomerHousehold, unlinkCustomerHousehold, updateTestDriveWorkflow, createWalkInTradeIn, recordTradeInAppraisal
Deals and deliverycreateDealPencil, saveDealWorksheet, presentDealWorksheet, acceptDealWorksheet, selectDealLenderOffer, reopenDealWorksheet, setDealTradeIn, transitionDeal, manageDeliveryCase, manageWeOweItem, completeDealDelivery
FinancequeueFinancingApplication, recordLenderSubmissionStatus, recordLenderDecision, openFinanceFollowUp, configureFinanceProduct, recordFinanceProductQuote, presentFinanceMenu, decideFinanceMenu, recordFinanceComplianceCheck, manageFinanceContractPackage, reviewFinancePrerequisites, manageFinanceFundingCase, recordCashDealDeposit, recordFinanceProductCancellation
Service and partstransitionServiceAppointment, openServiceOrder, recordServiceOrderEvidence, addServiceOrderLineItem, updateServiceOrderLineItem, removeServiceOrderLineItem, setServiceOrderTax, transitionServiceOrder, manageLaborOperation, configurePart, recordPartsMovement, managePartsPurchaseOrder
Payments and accountingcreatePaymentCheckout, requestPaymentRefund, reconcilePaymentTransaction, handlePaymentProviderWebhook, configureAccountingMapping, stageAccountingBatch, manageAccountingBatch
Organization and reportingchangeDealerMembership, configureRetentionPolicy, saveOperationalReport, runSavedReport, queueDueSavedReports, ensureDealershipOnboardingRecord, updateActiveUser, redirectToInit

Lists that are not command-only (for example Make, Model, ServicePackage, Role, DealerGroup, Rooftop, DealershipProfile, PaymentProvider, ApiKey, VehicleConditionReport, VehicleHistory) also use Keystone's generated CRUD.

Integrations

BoundaryWhat the code does
Paymentsfeatures/integrations/payment has stripe and manual adapters. Stripe creates Checkout Sessions, refunds, and verifies webhooks with STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET. Webhooks arrive at POST /api/payment-webhooks/{providerCode}. The manual adapter records payments staff take.
FinancingOnly the operator-recorded adapter exists. Queuing an application records it for the finance desk, which enters lender decisions.
Inventory channelspublic-inventory-feed (built in) and manual-marketplace-export (operator-recorded). Both are operator-driven. GET /api/inventory-feed returns listed vehicles as JSON for the served rooftop.
AccountingMappings and batches accept only the operator-recorded provider. Staff record posting results.
EmailSMTP through nodemailer for password reset and customer email verification.
File storageS3-compatible storage for vehicle images.
AI assistant/api/completion uses OpenRouter and calls the app's own MCP endpoint at /api/mcp-transport/http with the user's session cookie. MCP tools: listModels, queryData, searchModels, getFieldsForType, lookupInputType, createData, updateData, deleteData, modelSpecificSearch.

On this page