Openfront Construction
Construction project controls, field operations, safety, contracts, billing, a public contractor website, and a client and trade partner portal.
Source · Live site · Catalog
Openfront Construction is a self-hostable application for general and specialty contractors. It has three surfaces:
- an operator dashboard for company setup, pursuits, quote requests, and per-project tools (preconstruction, financials, contracts, procurement, RFIs and submittals, field, schedule, quality and safety, documents, billing, reports, team, audit);
- a public contractor website with services, past projects, inquiry forms, and customer quote requests;
- a project portal where invited clients, designers, subcontractors, and vendors work on their own items.
Start with Getting started.
Stack
Next.js 16 (App Router, React 19), Keystone 6 with GraphQL, Prisma, PostgreSQL. Password accounts with database-backed sessions. API keys for agents and integrations use Authorization: Bearer ofc_live_….
Code layout:
| Path | Contents |
|---|---|
features/keystone/models | 87 lists, grouped by bounded context (core, crm, public, service-requests, preconstruction, financials, contracts, execution, resources, safety, procurement, billing, governance, agents) |
features/keystone/mutations, features/keystone/queries | Named GraphQL operations |
features/keystone/domain | Tenancy, lifecycle policies, scopes, onboarding seed, invariants |
features/platform | Operator dashboard pages |
features/storefront | Public website and customer account |
features/portal | Client and trade partner portal, invitation acceptance |
features/integrations/storage | S3-compatible signed upload and download |
features/keystone/outbox | Outbox webhook drain |
features/keystone/mcp | MCP tools used by the dashboard AI chat |
docs/schema-map.md in the repository lists every model with its purpose, creator, and states.
Tenancy and access
- Company is the tenant. Every business record belongs to one company. Most also belong to a Project.
- Project-owned records store a derived company key. Hooks derive it from the connected project and reject relationships that cross companies or projects.
- Permissions live on Role (40+
canRead*andcanManage*checkboxes). A user gets them through CompanyMembership (company-wide, optionally all projects) and ProjectMembership (one project, access level viewer, contributor, or manager). - A ProjectMembership also has a portal type: internal, owner/client, designer, subcontractor, or vendor. External members can be linked to a TradePartner as their portal organization. They use
/portal, not the dashboard. - Website customers have a
Userbut no membership. They see only their ownServiceRequestrecords under/account. - ApiCredential stores hashed API keys with scopes. AgentIdentity and ScopeGrant bind an automated actor to exact company and project scopes such as
financials:writeoragents:execute.
Data model
All 87 lists, by area:
- Identity and access: User, AuthSession, Role, Company, CompanyMembership, ProjectMembership, AccountInvitation, ApiCredential, AgentIdentity, ScopeGrant, AgentAction, IdempotencyKey, OutboxMessage.
- Company master data: Project, TradePartner, ProjectLocation, CostCode, BudgetCode, Equipment, Material.
- Pursuits and website: ConstructionLead, ConstructionOpportunity, PublicSiteProfile, PublicService, PublicProject, IntakeSubmission, ServiceRequest, ServiceRequestEvent.
- Preconstruction: BidPackage, BidInvitation, BidSubmission, BidLineItem, Estimate, EstimateVersion, EstimateLineItem.
- Financials: Budget, BudgetLineItem, BudgetChange, BudgetChangeLineItem, DirectCost.
- Contracts and change: PrimeContract, Commitment, ContractLineItem, ChangeEvent, ChangeEventLineItem, ChangeQuote, ChangeOrder, ChangeOrderLineItem.
- Execution and documents: RFI, RFIResponse, SpecificationSection, Submittal, SubmittalReview, Drawing, DrawingRevision, Document, DocumentVersion, DocumentDistribution, DocumentAcknowledgement, DailyLog, DailyLogEntry, Schedule, ScheduleTask, ScheduleDependency, PunchItem, LaborEntry, ResourceLog.
- Quality and safety: SafetyIncident, SafetyObservation, InspectionTemplate, Inspection, InspectionItem, CorrectiveAction.
- Procurement: ProcurementRequest, ProcurementRequestLine, MaterialReceipt, MaterialReceiptLine.
- Billing: ProgressClaim, ProgressClaimLineItem, Payment, PaymentAllocation, PaymentReversal, RetainageRelease, LienWaiver.
- Governance: ApprovalPolicy, ApprovalRequest, ApprovalDecision.
Rules that apply across the model:
- Money is a BigInt in minor units with a currency code. Budget, contract, claim, and payment totals are recalculated by hooks and workflows, not typed in.
- Estimate versions, drawing revisions, and document versions are kept as separate records, so issued revisions are not overwritten.
- Lifecycle
statusfields are read-only on generic update. Status changes go through named transition mutations that followlifecyclePoliciesinfeatures/keystone/domain/lifecycle.ts. - Commands are idempotent (
IdempotencyKey), recorded asAgentActionaudit rows, and emitOutboxMessageevents.
Workflows
- Set up the company. Onboarding creates the company, roles, memberships, first project, cost and budget codes, budget, approval policies, and public profile. Then build the trade partner directory, cost code library, and equipment and material catalog.
- Win work. Website inquiries create an
IntakeSubmissionlinked to a lead and opportunity. Opportunities move from qualifying through go/no-go, estimating, submitted, and negotiating to won, lost, or declined. Signed-in customers can request a quote for a published service; staff price it; the customer accepts or cancels. - Start a project.
createConstructionProjectcreates the project, the creator's membership, a "GEN" cost and budget code, the budget and its line, and the approval policies. Add budget codes and locations in the Cost structure tool. Invite the team. - Buy out. Bid package, invitations (visible in the portal), submissions and lines, bid leveling, then award. Award creates a commitment. Add the prime contract with lines.
- Control cost. Budget lines, budget changes with approval, direct costs (post and reverse), labor time, and resource logs. Job cost and forecast appear in Reports.
- Manage change. RFI, change event, change quotes, change order with approval, which posts to the contract and budget.
- Build. RFIs and responses, submittals and review steps, drawings and documents with versions, distributions and acknowledgements, daily logs and entries, field photos, schedules, tasks, and dependencies.
- Buy materials. Procurement request with lines, order, material receipt, resource log.
- Quality and safety. Observation to corrective action. Incident (with restricted private details) to investigation to corrective action. Inspection template to inspection to items to punch item or corrective action.
- Get paid and pay out. Progress claim (owner claims from the prime contract; subcontractor claims from a commitment, also via the portal), approval, payment, allocation, retainage release, lien waiver. Reverse a settled payment when needed.
- Close and audit. CSV report export. Audit trail of actions, outbox messages, and idempotency keys. API key issue and revoke.
Approvals: budget changes, prime contracts, change orders, progress claims, and retainage releases can require an ApprovalRequest under the project's ApprovalPolicy. The requester must assign a different eligible person; self-approval is rejected.
Agent work follows request, review, outcome. requestAgentAction binds an operation, target, destination status, company, optional project, active agent identity, and matching scope grant. Execution goes through the normal domain transition, which rechecks the grant.
Named GraphQL operations
All operations below are defined in features/keystone/mutations/index.ts.
Queries
| Area | Operations |
|---|---|
| Portfolio and onboarding | constructionPortfolio, constructionOnboardingReadiness, redirectToInit |
| Project workspace | constructionProjectWorkspace, constructionProjectOptions, constructionProjectOptionSearch, constructionRecordDetail, constructionBidComparison, constructionApprovalSnapshot |
| Registers | constructionProcurementRequestRegister, constructionMaterialReceiptRegister, constructionProcurementCommitmentRegister, constructionProcurementBidInvitationRegister, constructionProcurementLienWaiverRegister, constructionResourceLogRegister, constructionResourceActivityReceipt, constructionInspectionRegister, constructionSafetyIncidentRegister, constructionSafetyObservationRegister, constructionSubmittalRegister, constructionRfiRegister |
| Files and restricted data | documentVersionDownload, drawingRevisionDownload, safetyIncidentPrivateDetail |
| Portal | constructionPortalInbox, constructionPortalApprovalSnapshot |
| Public website | publicConstructionSite, publicConstructionService, publicConstructionProject, publicIntakeReceipt |
| Store settings and quote requests | constructionStoreSettings, constructionCustomerRequests, constructionCustomerRequestCount, constructionCustomerRequest, constructionOperatorRequests |
Mutations
| Area | Operations |
|---|---|
| Lifecycle transitions | transitionProjectStatus, transitionBidPackageStatus, transitionEstimateStatus, transitionBudgetChangeStatus, transitionPrimeContractStatus, transitionCommitmentStatus, transitionChangeEventStatus, transitionChangeOrderStatus, transitionRfiStatus, transitionSubmittalStatus, transitionProgressClaimStatus, transitionSafetyIncidentStatus, transitionInspectionStatus, transitionScheduleStatus, transitionScheduleTaskStatus, transitionDailyLogStatus, transitionPunchItemStatus, transitionSafetyObservationStatus, transitionCorrectiveActionStatus, transitionPaymentStatus, transitionRetainageReleaseStatus, transitionLienWaiverStatus, transitionProcurementRequestStatus |
| Setup and team | onboardConstructionWorkspace, createConstructionProject, addProjectMember, inviteProjectMember, revokeProjectInvitation, reissueProjectInvitation, acceptProjectInvitation, registerTradePartner, updateConstructionStoreSettings |
| Preconstruction | startBidPackage, inviteBidder, recordBidSubmission, awardBidSubmission, createEstimateWithLine, reviseEstimate |
| Financials and contracts | addBudgetLine, createBudgetChangeWithLine, recordDirectCost, reversePostedDirectCost, createPrimeContractWithLine, createCommitmentWithLine, openChangeEvent, addChangeEventLine, recordChangeQuote, createChangeOrderWithLine |
| Execution and documents | raiseRfi, routeRfi, respondToRfi, registerSubmittal, routeSubmittalReview, recordSubmittalReview, registerSpecificationSection, registerDocument, registerDrawing, requestDocumentVersionUpload, completeDocumentVersionUpload, requestDrawingRevisionUpload, completeDrawingRevisionUpload, distributeDocumentVersion, acknowledgeDocumentDistribution |
| Field and schedule | startDailyLog, addDailyLogEntry, registerFieldPhoto, recordLaborTime, createResourceCatalogItem, recordResourceActivity, reconcileResourceActivityIntent, closeResourceActivityIntent, registerSchedule, registerScheduleTask, linkScheduleDependency, updateScheduleTaskProgress, recordPunchItem |
| Quality and safety | recordSafetyObservation, recordSafetyIncident, recordSafetyIncidentPrivateDetails, registerInspectionTemplate, createInspectionWithItem, recordInspectionItemResult, registerCorrectiveAction, recordCorrectiveActionEvidence |
| Procurement and billing | createProcurementRequestWithLine, placeProcurementOrder, recordMaterialReceipt, prepareProgressClaim, recordProgressPayment, allocateSettledPayment, reverseSettledPayment, requestRetainageRelease, recordLienWaiverEvidence |
| Approvals | configureConstructionApprovalPolicy, requestConstructionApproval, decideConstructionApproval |
| Portal | submitPortalBid, respondPortalRfi, reviewPortalSubmittal, submitPortalProgressClaim, submitPortalLienWaiver |
| Public website and customers | submitPublicIntake, registerConstructionCustomer, commandConstructionRequest (operations request, quote, accept, cancel) |
| API keys and agents | issueApiCredential, revokeApiCredential, requestAgentAction, reviewAgentAction, recordAgentActionObservation, recordAgentActionOutcome (records an observation only) |
Each command checks the session or API key, permission, company and project, expected state, reason where required, and idempotency key.
Integrations
| Integration | What the code does |
|---|---|
| S3-compatible storage | features/integrations/storage/s3.ts signs SigV4 PUT, GET, and HEAD URLs (60 to 900 seconds) for document versions and drawing revisions. Completion verifies the SHA-256 checksum and object metadata. Fails closed when S3_* values are missing |
| SMTP | Password reset email only. Dashboard users get a /dashboard/reset link; customers and portal users get /account/reset |
| Outbox webhook | npm run outbox:work drains OutboxMessage rows to OUTBOX_WEBHOOK_URL with an HMAC-SHA256 signature in x-openfront-signature-sha256 and the event type in x-openfront-event. Failed messages retry up to OUTBOX_MAX_ATTEMPTS, then move to dead_letter |
| Stripe Checkout | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_KEY, NEXT_PUBLIC_SITE_URL. Installed per company at /dashboard/platform/payment-providers. Pays customer quote deposits and owner progress claims by card or US bank account. Webhook: /api/payment-webhooks/[providerId] |
| OpenRouter AI chat | /api/completion uses the signed-in user's session cookie and the MCP tools at /api/mcp-transport/[transport] |
| MCP tools | listModels, queryData, searchModels, getFieldsForType, lookupInputType, modelSpecificSearch. Reads are bounded to 100 rows per page and 500 rows total fanout |
Project invitations return a one-time /accept-invitation?token=… link to the operator, who shares it with the invitee.
Extension paths
- Add lists in the matching file under
features/keystone/modelsand register them inmodels/index.ts. Use direct company and project relationships. - Add lifecycle transitions in
features/keystone/domain/lifecycle.tsandfeatures/keystone/mutations/lifecycle.ts. Keep the status field read-only. - Add agent scopes in
features/keystone/domain/scopes.ts. - Add provider adapters behind narrow mutations. Store provider references and checksums, not credentials.
- Build screens in
features/platform,features/storefront, orfeatures/portal, keepapproute files thin, and call the named operations instead of reimplementing permission or lifecycle rules.