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:

PathContents
features/keystone/models87 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/queriesNamed GraphQL operations
features/keystone/domainTenancy, lifecycle policies, scopes, onboarding seed, invariants
features/platformOperator dashboard pages
features/storefrontPublic website and customer account
features/portalClient and trade partner portal, invitation acceptance
features/integrations/storageS3-compatible signed upload and download
features/keystone/outboxOutbox webhook drain
features/keystone/mcpMCP 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* and canManage* 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 User but no membership. They see only their own ServiceRequest records 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:write or agents: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 status fields are read-only on generic update. Status changes go through named transition mutations that follow lifecyclePolicies in features/keystone/domain/lifecycle.ts.
  • Commands are idempotent (IdempotencyKey), recorded as AgentAction audit rows, and emit OutboxMessage events.

Workflows

  1. 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.
  2. Win work. Website inquiries create an IntakeSubmission linked 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.
  3. Start a project. createConstructionProject creates 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.
  4. 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.
  5. 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.
  6. Manage change. RFI, change event, change quotes, change order with approval, which posts to the contract and budget.
  7. 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.
  8. Buy materials. Procurement request with lines, order, material receipt, resource log.
  9. 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.
  10. 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.
  11. 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

AreaOperations
Portfolio and onboardingconstructionPortfolio, constructionOnboardingReadiness, redirectToInit
Project workspaceconstructionProjectWorkspace, constructionProjectOptions, constructionProjectOptionSearch, constructionRecordDetail, constructionBidComparison, constructionApprovalSnapshot
RegistersconstructionProcurementRequestRegister, constructionMaterialReceiptRegister, constructionProcurementCommitmentRegister, constructionProcurementBidInvitationRegister, constructionProcurementLienWaiverRegister, constructionResourceLogRegister, constructionResourceActivityReceipt, constructionInspectionRegister, constructionSafetyIncidentRegister, constructionSafetyObservationRegister, constructionSubmittalRegister, constructionRfiRegister
Files and restricted datadocumentVersionDownload, drawingRevisionDownload, safetyIncidentPrivateDetail
PortalconstructionPortalInbox, constructionPortalApprovalSnapshot
Public websitepublicConstructionSite, publicConstructionService, publicConstructionProject, publicIntakeReceipt
Store settings and quote requestsconstructionStoreSettings, constructionCustomerRequests, constructionCustomerRequestCount, constructionCustomerRequest, constructionOperatorRequests

Mutations

AreaOperations
Lifecycle transitionstransitionProjectStatus, transitionBidPackageStatus, transitionEstimateStatus, transitionBudgetChangeStatus, transitionPrimeContractStatus, transitionCommitmentStatus, transitionChangeEventStatus, transitionChangeOrderStatus, transitionRfiStatus, transitionSubmittalStatus, transitionProgressClaimStatus, transitionSafetyIncidentStatus, transitionInspectionStatus, transitionScheduleStatus, transitionScheduleTaskStatus, transitionDailyLogStatus, transitionPunchItemStatus, transitionSafetyObservationStatus, transitionCorrectiveActionStatus, transitionPaymentStatus, transitionRetainageReleaseStatus, transitionLienWaiverStatus, transitionProcurementRequestStatus
Setup and teamonboardConstructionWorkspace, createConstructionProject, addProjectMember, inviteProjectMember, revokeProjectInvitation, reissueProjectInvitation, acceptProjectInvitation, registerTradePartner, updateConstructionStoreSettings
PreconstructionstartBidPackage, inviteBidder, recordBidSubmission, awardBidSubmission, createEstimateWithLine, reviseEstimate
Financials and contractsaddBudgetLine, createBudgetChangeWithLine, recordDirectCost, reversePostedDirectCost, createPrimeContractWithLine, createCommitmentWithLine, openChangeEvent, addChangeEventLine, recordChangeQuote, createChangeOrderWithLine
Execution and documentsraiseRfi, routeRfi, respondToRfi, registerSubmittal, routeSubmittalReview, recordSubmittalReview, registerSpecificationSection, registerDocument, registerDrawing, requestDocumentVersionUpload, completeDocumentVersionUpload, requestDrawingRevisionUpload, completeDrawingRevisionUpload, distributeDocumentVersion, acknowledgeDocumentDistribution
Field and schedulestartDailyLog, addDailyLogEntry, registerFieldPhoto, recordLaborTime, createResourceCatalogItem, recordResourceActivity, reconcileResourceActivityIntent, closeResourceActivityIntent, registerSchedule, registerScheduleTask, linkScheduleDependency, updateScheduleTaskProgress, recordPunchItem
Quality and safetyrecordSafetyObservation, recordSafetyIncident, recordSafetyIncidentPrivateDetails, registerInspectionTemplate, createInspectionWithItem, recordInspectionItemResult, registerCorrectiveAction, recordCorrectiveActionEvidence
Procurement and billingcreateProcurementRequestWithLine, placeProcurementOrder, recordMaterialReceipt, prepareProgressClaim, recordProgressPayment, allocateSettledPayment, reverseSettledPayment, requestRetainageRelease, recordLienWaiverEvidence
ApprovalsconfigureConstructionApprovalPolicy, requestConstructionApproval, decideConstructionApproval
PortalsubmitPortalBid, respondPortalRfi, reviewPortalSubmittal, submitPortalProgressClaim, submitPortalLienWaiver
Public website and customerssubmitPublicIntake, registerConstructionCustomer, commandConstructionRequest (operations request, quote, accept, cancel)
API keys and agentsissueApiCredential, 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

IntegrationWhat the code does
S3-compatible storagefeatures/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
SMTPPassword reset email only. Dashboard users get a /dashboard/reset link; customers and portal users get /account/reset
Outbox webhooknpm 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 CheckoutSTRIPE_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 toolslistModels, 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/models and register them in models/index.ts. Use direct company and project relationships.
  • Add lifecycle transitions in features/keystone/domain/lifecycle.ts and features/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, or features/portal, keep app route files thin, and call the named operations instead of reimplementing permission or lifecycle rules.

On this page