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
| Path | What it holds |
|---|---|
features/keystone/models | 71 registered lists (models/index.ts) |
features/keystone/mutations | Custom GraphQL queries and commands (mutations/index.ts) |
features/keystone/lifecycle | State machines, pricing, and the audit/outbox writer |
features/platform | Dealership operator desks under /dashboard/platform |
features/dashboard | Shared Keystone admin shell, sign-in, and generic list pages |
features/storefront | Public site and customer account |
features/integrations | Payment, financing, and inventory-channel adapters |
Tenancy and access
DealerGroupis the tenant root. ARooftopis one store. Most operating lists carry arooftopfield and are filtered to the rooftops the user can reach.- Access comes from the user's
Role. Operating permissions can also come from an activeDealerGroupMembership(optionally all rooftops) orRooftopMembership, for that scope only. Memberships change only throughchangeDealerMembership, which writes aMembershipAuditEvent. - 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
canViewAllLeadsacross 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
DomainAuditEventand aDomainOutboxEventin the same transaction, keyed by an idempotency key.
Data model
| Area | Lists |
|---|---|
| Organization | User, Role, DealerGroup, Rooftop, DealerGroupMembership, RooftopMembership, MembershipAuditEvent, DealershipProfile, ApiKey, AuthenticationThrottle |
| Inventory | Make, Model, Vehicle, VehicleImage, VehicleConditionReport, VehicleHistory, PriceHistory, InventoryAcquisition, InventoryAcquisitionEvent, InventoryStock, InventoryStockEvent, InventoryReconWorkItem, InventoryMerchandisingVersion, InventoryFeedRun, InventoryFeedError |
| CRM | Customer, CustomerMergeEvent, CustomerHousehold, CustomerContactPreferenceEvent, Lead, LeadAssignmentEvent, LeadRoutingRule, CustomerInteraction, Task, SalesVisit, TestDrive, TradeIn |
| Deals and finance | Deal, DealApproval, FinancingApplication, LenderSubmission, LenderDecision, FinanceProduct, FinanceProductQuote, FinanceMenuVersion, FinanceComplianceCheck, FinanceContractPackage, FinanceFundingCase, FinanceProductCancellation |
| Delivery | DeliveryCase, WeOweItem |
| Service and parts | ServiceAppointment, ServiceOrder, RepairOrderLineItem, ServiceLaborOperation, ServicePackage, PartsInventory, PartsMovement, PartsPurchaseOrder |
| Payments and accounting | PaymentProvider, PaymentTransaction, PaymentRefund, PaymentProviderEvent, AccountingMapping, AccountingBatch, AccountingProviderResponse |
| Reporting and system | SavedReport, 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.
| Group | Operations |
|---|---|
| Storefront reads | publicInventoryVehicles, publicInventoryVehicle, publicInventoryVehicleRecord, publicInventoryCount, storefrontProfile |
| Public submissions | submitInquiry, submitTestDrive, submitTradeIn, submitFinancing, submitServiceBooking |
| Customer account | registerCustomerAccount, requestCustomerEmailVerification, confirmCustomerEmailVerification, cancelCustomerBooking, rescheduleCustomerBooking, customerRequestHistory, customerRequestDetail, customerPurchases, customerServiceHistory, customerServiceOrder, customerPayments, customerEmailLinkStatus |
| Workspace reads | inventoryWorkspace, crmWorkspace, dealDeskWorkspace, dealWorkspace, dealLenderOffers, financeWorkspace, deliveryWorkspace, fixedOpsWorkspace, accountingWorkspace, operationalReport, auditSearch, domainTimeline, customerDuplicateCandidates |
| Inventory | intakeInventoryVehicle, startInventoryAcquisition, transitionInventoryAcquisition, establishInventoryStock, moveInventoryStock, recordInventoryCost, openInventoryReconWorkItem, transitionInventoryReconWorkItem, updateInventoryMerchandisingDraft, updateInventoryVehicleDetails, submitInventoryMerchandising, decideInventoryMerchandising, recordInventoryFeedRun, addInventoryVehiclePhoto, arrangeInventoryVehiclePhotos, removeInventoryVehiclePhoto |
| CRM | openLead, updateLeadDetails, transitionLead, recordLeadInteraction, createLeadTask, completeTask, transferLeadOwnership, routeLead, configureLeadRoutingRule, enrollLeadCadence, advanceLeadCadence, manageSalesVisit, updateCustomerContact, updateCustomerCommunicationPreferences, mergeCustomers, linkCustomerHousehold, unlinkCustomerHousehold, updateTestDriveWorkflow, createWalkInTradeIn, recordTradeInAppraisal |
| Deals and delivery | createDealPencil, saveDealWorksheet, presentDealWorksheet, acceptDealWorksheet, selectDealLenderOffer, reopenDealWorksheet, setDealTradeIn, transitionDeal, manageDeliveryCase, manageWeOweItem, completeDealDelivery |
| Finance | queueFinancingApplication, recordLenderSubmissionStatus, recordLenderDecision, openFinanceFollowUp, configureFinanceProduct, recordFinanceProductQuote, presentFinanceMenu, decideFinanceMenu, recordFinanceComplianceCheck, manageFinanceContractPackage, reviewFinancePrerequisites, manageFinanceFundingCase, recordCashDealDeposit, recordFinanceProductCancellation |
| Service and parts | transitionServiceAppointment, openServiceOrder, recordServiceOrderEvidence, addServiceOrderLineItem, updateServiceOrderLineItem, removeServiceOrderLineItem, setServiceOrderTax, transitionServiceOrder, manageLaborOperation, configurePart, recordPartsMovement, managePartsPurchaseOrder |
| Payments and accounting | createPaymentCheckout, requestPaymentRefund, reconcilePaymentTransaction, handlePaymentProviderWebhook, configureAccountingMapping, stageAccountingBatch, manageAccountingBatch |
| Organization and reporting | changeDealerMembership, 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
| Boundary | What the code does |
|---|---|
| Payments | features/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. |
| Financing | Only the operator-recorded adapter exists. Queuing an application records it for the finance desk, which enters lender decisions. |
| Inventory channels | public-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. |
| Accounting | Mappings and batches accept only the operator-recorded provider. Staff record posting results. |
| SMTP through nodemailer for password reset and customer email verification. | |
| File storage | S3-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. |