Openfront Rental
Short-term accommodation listings, quotes, reservations, guest trips, host operations, calendar exchange, and payments.
Source · Live site · Catalog
Openfront Rental is a self-hostable app for people who manage short-term accommodation. Guests search stays, get a quote, reserve, sign a stay agreement, and follow a private trip link. Hosts use the dashboard for properties, listings, calendar, pricing, reservations, guests, inbox, tasks, payments, payouts, reports, channels, reviews, and store settings.
It is built with Next.js 16, React 19, Keystone 6, GraphQL, Prisma, and PostgreSQL. To install it, see Getting started.
Card payments use Stripe with sk_test_ and pk_test_ keys. Calendar exchange uses iCalendar text. Outbox events wait for a dispatcher you register in features/keystone/workers/outboxDispatchers.ts.
Concepts
| Concept | Meaning |
|---|---|
| Host organization | The tenant. Host-scoped records are visible through an active HostMembership |
| Property and unit | The building and the bookable AccommodationUnit inside it |
| Listing | The public page for a unit, with bookingMode instant or request |
| Rate plan | Nightly rate, stay limits, notice, window, and a cancellation policy |
| Quote | A StayQuote snapshot of dates, rates, fees, taxes, deposit, and guests |
| Reservation | A stay created from a quote |
| Trip link | A 48-character private token for the guest's trip, agreement, and messages |
Guest and host surfaces
The storefront lives at / and is built from features/storefront. Its main routes are /search, /stays/[slug], /collections, /compare, /checkout/[quoteToken], /trips, /trips/[token], /agreements/[token], /messages/[token], /account, /help, and /policies. See External storefronts for the full table.
The host operator dashboard lives at /dashboard. Product pages are under /dashboard/platform and are built from features/platform. The generic Keystone list and item screens (/dashboard/[listKey]) come from features/dashboard. See External dashboards for the full table.
Models and tenancy
HostOrganization is the tenant root. HostMembership links a user to a host with a role (owner, admin, manager, operations, or finance) and a status (invited, active, suspended). Host-scoped records are visible to a user only through an active membership in the record's host. Users whose role has canManagePeople or canManageRoles are treated as platform administrators and see all hosts.
Role permissions come in read/manage pairs: properties, listings, calendar, pricing, reservations, guests, messages, payments, payouts, reports, channels, and reviews. Other flags are canAccessDashboard, canManageSettings, canManageOnboarding, canManagePeople, canManageRoles, canSeeOtherPeople, and canEditOtherPeople.
Current models in features/keystone/models:
| Area | Models |
|---|---|
| People and tenancy | User, Role, HostOrganization, HostMembership, MarketplaceSettings |
| Properties | Property, PropertyAddress, PropertyOwner, PropertyOwnerAssignment, AccommodationUnit, Bedroom, Bed, Amenity |
| Listings | Listing, ListingMedia, StayCollection, HouseRule, CalendarBlock |
| Pricing and policy | RatePlan, SeasonalRate, FeeDefinition, TaxRule, CancellationPolicy |
| Booking | StayQuote, GuestProfile, GuestParty, Reservation, ReservationAmendment, ReservationLineItem, StayAgreement |
| Money | SecurityDeposit, DamageClaim, Payment, PaymentProvider, PaymentWebhookEvent, Refund, Payout, OwnerStatement, OwnerStatementLine |
| Guest contact and work | Conversation, Message, Review, TaskRule, PropertyTask |
| Channels and jobs | ChannelConnection, ChannelListing, ChannelReservation, ChannelSyncEvent, DataExchangeJob, OutboxEvent |
MarketplaceSettings is a single record for the public store identity: name, tagline, contact details, policy links, currency, locale, timezone, logo, accent color, and whether test bookings are allowed.
Quotes and reservation line items keep a snapshot of the accepted dates, rates, fees, taxes, deposit, guest counts, and currency. Later rate changes do not rewrite them.
Named GraphQL operations
Custom operations are merged in features/keystone/mutations/index.ts. The API is served at /api/graphql.
Public and guest
| Kind | Operations |
|---|---|
| Queries | publicMarketplaceSettings, searchStays, publicListing, publicStayAvailability, publicStayQuote, publicTrip, publicStayAgreement, publicGuestThread, myGuestAccount, guestPasswordResetAvailable |
| Booking | quoteStay, createStayReservation, recoverStayCheckout, requestStayCancellation, signStayAgreement |
| Card payment (test mode) | startGuestStayPayment, completeGuestStayPayment |
| Guest account and messages | registerGuestAccount, updateMyGuestProfile, changeGuestEmail, changeGuestPassword, openAccountTrip, recoverStayTrip, claimStayTrip, submitGuestInquiry, replyToGuestThread, sendTripMessage, submitStayReview |
Public reads use exact slugs or opaque tokens. Trip, agreement, and message links use 48-character hex tokens.
Host
| Area | Operations |
|---|---|
| Reservations | transitionStayReservation, reassignStayReservation, amendConfirmedStay, cancelStayQuote |
| Payments and refunds | initiateStayPayment, captureStayPayment, refundStayPayment |
| Deposits and claims | transitionSecurityDeposit, createStayDamageClaim, transitionDamageClaim |
| Owners | generateOwnerStatement, transitionOwnerStatement, ownerFinanceSummary |
| Tasks | createOperationsTask, transitionPropertyTask, runPropertyTaskRules |
| Calendar | importListingCalendar, exportListingCalendar |
| Reports | hostOperationsReport, exportHostArchive, permittedHostOrganizations, listingReadinessIndex |
| Outbox | deliverOutboxEvents |
| Onboarding | rentalOnboarding, updateOnboardingStatus, redirectToInit |
hostStayCommand(command, input) is the single entry point most dashboard actions use. Its commands are: transition, amend, reassign, listing_allocate, party_amend, party_invites_amend, payment_record_manual, payment_cancel_attempt, refund_create, refund_record_manual, refund_process, refund_decline, refund_assess, deposit, claim_create, claim_transition, agreement_void, agreement_reissue, conversation_reply, conversation_status, conversation_link_revoke, conversation_link_reissue, review_moderate, and catalog_transition. It requires a signed-in user and returns { ok, message, data }.
Generated Keystone CRUD still exists for every model. Reservation, payment, and refund state should change through these commands, not through raw updates.
Onboarding
Onboarding runs from the dashboard. It calls rentalOnboarding with mode set to create (one item at a time), verify, or complete. The caller needs canManageOnboarding. Each item runs in a serializable transaction and is created or reused, so a second run does not duplicate records.
There are three templates:
- Minimal: one home (Pineglass) with its amenities, collection, one guest, and one stay.
- Full: the whole Housekind portfolio from
features/platform/onboarding/lib/seed.json. That is store settings, the Housekind Collective host, amenities, collections, five properties with units and listings, a cancellation policy, house rules, rates, calendar blocks, guests, reservations, tasks, owner statement and payout records, a manual payment provider, a Stripe card provider record, and one availability-only iCalendar connection. - Custom: the full template merged with your own JSON, validated by a schema.
The final step checks readiness for records that onboarding never creates, such as quotes, amendments, damage claims, refunds, webhook events, channel reservations, and outbox events. Those only come from real activity.
npm run seed:runtime is a separate local script. It refuses to run unless DATABASE_URL points to a local database named runtime_rental.
Integrations
Payments
Adapters live in features/integrations/payment:
| Code | What it does |
|---|---|
pp_system_default | Manual. A host records money already received and attests manual refunds. |
pp_stripe_stripe | Stripe card payments in test mode only. Enabled when STRIPE_ENABLED=true and the keys start with sk_test_ and pk_test_. Needs STRIPE_WEBHOOK_SECRET. Card checkout is offered only for test bookings. |
pp_signed_test | Webhook-only provider for testing signed callbacks with PAYMENT_WEBHOOK_SIGNED_TEST_SECRET. It cannot create payments or refunds. |
Webhooks arrive at /api/payment-webhooks/[providerCode]. The route limits body size and read time before verifying the signature.
Calendar channels
Adapters live in features/integrations/channels. Two are installed: direct and ical. The ical adapter imports and exports availability only. It does not sync rates, reservations, or messages, and it has no remote transport: hosts paste calendar text into importListingCalendar and fetch output from exportListingCalendar.
Password reset and guest mail use SMTP (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, SMTP_STORE_LINK). Guest password reset is offered only when host, sender, and store link are set.
Outbox
OutboxEvent records are written for later delivery. npm run outbox:deliver runs the worker, but no dispatcher is installed. Runs report no_dispatcher and leave events as they are until you add one in features/keystone/workers/outboxDispatchers.ts.
AI assistant and MCP
/api/completion powers the dashboard assistant through OpenRouter (OPENROUTER_API_KEY, OPENROUTER_MODEL, optional OPENROUTER_MAX_TOKENS). /api/mcp-transport/[transport] exposes MCP tools that forward the caller's session cookie to GraphQL: listModels, queryData, searchModels, getFieldsForType, lookupInputType, createData, updateData, deleteData, modelSpecificSearch, and stayCommand. stayCommand runs a subset of host commands (transition, conversation_reply, conversation_status, review_moderate, catalog_transition, refund_decline, payment_cancel_attempt, payment_record_manual) and requires confirm: true.