Rentals use three records (appengine storefront/services/rental.service.ts):
| Record | What it is |
|---|---|
sf_rental_config | The house rules: currency, tax rate, default deposit, late fee, fees, availability, terms. One can be the default. |
sf_rental_item | One thing you rent out, by sku, with its prices and its own rules. Anything it leaves blank comes from its config. |
sf_rental | One booking. Its id in every URL is its name, a 10-letter code unless one was given. |
A booking's status is one of pending, confirmed, ready, picked_up, delivered, active, overdue, returned, completed or cancelled. Its deposit has its own status: pending, held, partially_refunded, refunded, forfeited or waived. Its payment status is pending, partial, paid or refunded.
Only the customer's own rentals are theirs to see and cancel. Every other step (confirm, hand over, take back, payments, deposits, books) needs someone signed in to the business.
Ask first
- What is rented? Name, SKU, how many you have, photos, description.
- Prices. Per hour, day, week and/or month. Leave a period blank to not offer it.
- Deposit. None, a fixed amount, or a percent of the rental. Refundable or not.
- Fees. For example cleaning or insurance. Fixed or percent. Which ones the customer can choose.
- Late returns. A rate per hour, day, week or month, a grace period, a cap.
- Availability. Shortest and longest rental, a gap between rentals for cleaning, dates you are closed.
- Handover. Pickup, delivery (and its fee), drop-off, or collection from the customer (and its fee).
- Who may rent. Minimum age, ID check level, which countries or states.
- Terms. The rental agreement the customer must accept.
- Online or counter? Sold on the website, or booked only by staff.
Steps
1. Rental › Config: the house rules
Studio: Storefront › Rental, tab Config (/rental?openConfig=<sk> opens one). Or call_appengine PUT repository/create with datatype: "sf_rental_config", "isNew": true and data (sdk sf-rental-config.ts):
| Field | What it does |
|---|---|
name | Unique. Items point at a config by this name. |
title, description, status | Labels. |
isDefault | Only one config can be the default. An item with no config uses it. |
defaultPricing.currency, defaultPricing.taxRate | Currency, and the tax rate (percent) saved on each booking. |
defaultDeposit | { required, type: fixed | percent, amount, refundable }, for items with no deposit of their own. |
defaultLateFee | { enabled, rate, per, gracePeriod, gracePeriodUnit, maxFee }, for items with no late fee of their own. |
defaultFees[] | { name, amount, type: fixed | percent, optional }. |
defaultAvailability | Same shape as the item's availability (step 2). |
restrictions[] | Same shape as the item's (step 2). |
defaultTerms, requireAgreement | The agreement text. requireAgreement is on unless false. |
An item whose config names a config that doesn't exist gets no config. It does not fall back to the default (getConfig, rental.service.ts 248). Leave config blank to use the default.
Hands on: the config name.
Check: call_appengine GET storefront/rental/config?name=<name> returns it.
2. Rental › Items: the item
Tab Items (/rental?openItem=<sk>). Or PUT repository/create with datatype: "sf_rental_item" (sdk sf-rental-item.ts):
| Field | What it does |
|---|---|
name, slug, sku | sku is 3 to 50 characters and unique. The site page is /rental/<slug or sku>. |
status | active, inactive or maintenance. Only active items are listed. |
config | A config name. Blank: the default config. |
quantity | How many you can rent out at once. |
description, images | For the item page. |
pricing | { currency, hourly, daily, weekly, monthly }. |
deposit | { required, type: fixed | percent, amount, refundable }. |
fees[] | { name, amount, type, optional }. An item fee replaces a config fee with the same name. |
lateFee | { enabled, rate, per: hour | day | week | month, gracePeriod, gracePeriodUnit, maxFee }. |
availability | minDuration/minDurationUnit, maxDuration/maxDurationUnit, bufferTime/bufferTimeUnit, blockedDates[] { startDate, endDate, reason }. |
fulfillment | allowPickup, allowDelivery, deliveryFee, deliveryFeeType: flat | per_mile | tiered, allowDropoff, allowReturnPickup, returnPickupFee. |
pickupLocation, returnLocation | Addresses. |
restrictions[] | { type: age | kyc | location | license | membership | custom, enabled, minAge, maxAge, kycLevel, locationRule, countries, states, message }. |
terms | The agreement, in place of the config's defaultTerms. |
What the restrictions really check (validateRestrictions, rental.service.ts 393):
| Type | Checked |
|---|---|
age | Only when a date of birth is given. |
kyc | A verified customer_kyc record for the email, at kycLevel or above (basic, standard, enhanced, full). |
location | countries / states allowed or blocked. |
license, membership, custom | Not checked. |
Hands on: the item's sku.
Check: GET storefront/rental/items/<sku> returns it, and GET storefront/rental/items lists it when active.
3. Quote a price and check the dates
call_appengine POST storefront/rental/items/<sku>/price with { startDate, endDate, quantity, optionalFees: ["<fee name>"] } (calculatePrice, rental.service.ts 510):
- The cheapest of monthly, weekly and daily is used, with each counted in whole periods, rounded up. Under 24 hours with an hourly price: hourly.
- Fixed fees count once per booking. Percent fees are on the subtotal.
- The deposit is the item's, else the config's. A percent deposit is on the subtotal.
- Tax is the config's
taxRate.
Tested ($50 a day, $250 a week, $10 cleaning, 8% tax, $100 deposit):
| Dates | Quote |
|---|---|
| 3 days | $150 + $10 + 8% tax = $172.80, deposit $100 |
| 3 days, quantity 2 | Subtotal $300. The $10 fee and $100 deposit are not multiplied. |
| 7 days | One week: $250 |
| 8 days | Daily, $400, because weekly rounds up to two weeks ($500) |
| 5 hours, no hourly price | One day |
GET storefront/rental/items/<sku>/availability?startDate=&endDate=&quantity= says yes, or why not (checkAvailability, 322). Tested refusals: "The shortest rental is 1 day", "The longest rental is 14 days", "Closed 2026-12-24 to 2026-12-26: Holiday", and more than you have. The buffer counts in whole calendar days: a 1-day buffer blocked a start 26 hours after the last rental ended. Cancelled and completed rentals don't count.
The price quote doesn't check the shortest or longest rental. Always check availability too.
POST storefront/rental/items/<sku>/validate-restrictions with { email, dateOfBirth?, country?, state? } answers { valid, failed[] }.
Check: read the quote back to the owner: dates, periods, fees, tax, deposit, total.
4. Site settings: the rental pages
Stop: turning on a site feature changes the live site. Ask first.
Rentals have their own site feature, rental. Studio's feature list doesn't show it unless the site already has it; see Site page routing. With it on, the site has (base-app components/rental/RentalRoute.tsx; see Built-in pages):
/rental: every active item;/rental/<slug or sku>: the item page, with dates, quantity, optional fees and Add to Cart.
The customer must tick the agreement when a config applies and either requireAgreement is true, or it isn't false and there are terms. The terms shown are the item's terms, else the config's defaultTerms.
For a custom page, the runtime has window.appmint.rental (see AppMint JS engine on a page).
Check: curl -s -o /dev/null -w "%{http_code}" "<site>/rental" answers 200 and the item page shows the item. (Not tested here: with the feature off, /rental answered 404.)
5. Checkout: the shopper books and pays
Add to Cart puts a cart line with itemType: "rental", the dates, the chosen fees and the agreement. The customer pays through the shop's cart and /checkout like any order; see Take payments on the site.
The server prices the line itself (quoteRentalLine, discount.service.ts). Tested: a cart sent with made-up prices was priced at $150 + $15 fees + $100 deposit + $25 delivery = $290. A flat delivery or collection fee is the item's. A per-mile or tiered fee is never less than the item's base fee. The rental's total at checkout is subtotal + deposit + tax + delivery − discount. Rental discounts (free_deposit, free_delivery, waive_fees) are in Discount codes.
Dates are checked before the order is made (storefront.order.service.ts ~1981). The sf_rental is made after the order. With a card gateway the deposit is recorded as held and the payment recorded on it, using the card payment's reference (~2185).
If the booking can't be made after the order (a restriction fails, for example), the order stands and no rental is made. The error is only logged (storefront.order.service.ts 2234). Check that every paid rental order has its rental (step 6).
The customer sees their rental in My Account › Orders. There is no separate rentals section. See Customer account portal.
Check: the order is paid, and GET storefront/rental?customerEmail=<email> lists the rental.
6. Rental › Rentals: a booking by staff
Tab Rentals (/rental?openId=<sk>). The new-booking form calls POST storefront/rental (websitemint rental-actions.ts):
{
"sku": "<sku>",
"quantity": 1,
"customer": { "firstName": "", "lastName": "", "email": "", "phone": "" },
"startDate": "2026-11-02T10:00:00",
"endDate": "2026-11-05T10:00:00",
"fulfillment": { "type": "pickup" },
"return": { "type": "dropoff" },
"optionalFees": [],
"customerNotes": "",
"notes": ""
}Studio's form can also record a payment or deposit taken at the counter while booking. The booking checks dates (409 with the reason), refuses a name already used (409), runs the restrictions, and saves the price with status pending.
Stop: a booking with an email sends the customer Rental Booked (rental-confirmation). Show who, the item, dates and price, and wait for yes.
Hands on: the rental's name.
Check: GET storefront/rental/<name> shows it pending with its pricing.
7. Rental › Rentals: hand over and take back
Open the rental. The buttons (websitemint rental-fulfillment.tsx 403–430), each a call_appengine PUT storefront/rental/<name>/<step>:
| Button | Step | Status after | Body | Email to the customer |
|---|---|---|---|---|
| Confirm Rental | confirm | confirmed | none | rental-confirmed |
| Mark Ready | ready | ready | none | none |
| Mark Picked Up / Mark Delivered | picked-up / delivered | picked_up / delivered | { actualDate? } | rental-pickedup (both) |
| Record Check-Out | checkout | active | { condition, notes?, photos?, checkedBy } | rental-checkout (Rental Started) |
| Start Rental | active | active | none | none |
| Mark Overdue | overdue | overdue | none | none |
| Mark Returned | dropped-off (or returned) | returned | { actualDate? } | rental-droppedoff |
| Record Check-In | checkin | returned | { condition, notes?, photos?, damages?, checkedBy } | rental-returned |
| Complete Rental | complete | completed | none | none |
condition is excellent, good, fair or poor. notes (PUT …/notes) adds a note and changes nothing else.
Stop before each step with an email: it goes to the customer.
Nothing enforces the order: any step can follow any other, except cancel (step 9). Tested in order: ready, picked up, check-out (active), overdue, returned, check-in, complete.
Lists: GET storefront/rental/status/due-today and GET storefront/rental/status/overdue.
The overdue list shows only rentals still active past their end. One already marked overdue drops off it (getOverdueRentals, 1570). Nothing marks rentals overdue or sends reminders by itself.
Check: the rental's status, and its condition record after check-out and check-in.
8. Late fees
A late fee applies when the item's lateFee is on (enabled, or a rate with enabled unset), else the config's defaultLateFee (rental-late-fee.ts). It counts from the end date plus the grace period, in whole per periods, up to maxFee. It is added as the fee Late return fee, the total goes up, and the payment status is worked out again.
Tested: end Nov 5 10:00, 1 hour grace, $10 a day; returned Nov 7 12:00 (dropped-off with actualDate): $30, total $178.20 → $208.20, payment back to partial.
Record Check-In sets the return time to now and works the late fee out again from it (rental.service.ts 1034, 1047–1050). If you mark it returned with the real time, check in on the same visit, or check the fee after.
9. Payments, deposits and cancelling
Stop before every action here: they move or keep real money. Show the rental, the amount and why.
| Action | Call | Notes |
|---|---|---|
| Record Payment | POST storefront/rental/<name>/payment { amount, method, transactionId? } | method: cash, credit_card, debit_card, check, bank_transfer, other. Payment becomes partial or paid. Tested. |
| Refund payment | PUT …/payment/refund { amount?, reason?, cashReturned? } | Stripe, PayPal and Helcim payments with a transaction id go back through the gateway. Anything else needs cashReturned: true ("Money given back"). |
| Hold the deposit | PUT …/deposit/hold { transactionId, chargeId?, paymentGateway?, method? } | Records the deposit as held. A second hold is refused (409). Tested. |
| Keep part of it | POST …/deposit/deduction { reason, amount } | Up to what is held. Posts the kept money to Books. Tested: more than held is refused ("Deduct between $0.01 and $100.00"). |
| Refund the deposit | PUT …/deposit/refund { amount, skipPaymentGateway?, reason? } | Up to deposit − kept − already refunded. Goes back through the gateway when it was paid online. Emails rental-deposit-refunded. Tested: $100 held, $20 kept, $90 refused, $80 refunded. |
| Cancel | PUT …/cancel { reason, refundDeposit?, refundPayment?, cashReturned? } | Refused once active, returned or completed ("Cannot cancel rental in current status"). Refunds nothing unless asked. Emails rental-cancellation. Tested. |
A customer can cancel their own rental but can't ask for a refund; staff refund it.
10. Books
Post to Books (PUT storefront/rental/<name>/books) posts what was paid, refunded and kept: journal_entry rows (source rental) and sf_transaction rows (invoiceType: rental). Money steps post as they happen; this catches up anything missed. Each posting is keyed <name>:<kind>:<amount>, so posting twice doesn't double it.
Check: the rental's books entries in Books.
Emails a rental sends
All go to the email on the rental (tools/templates/default-email-templates.ts):
| Template | When |
|---|---|
rental-confirmation (Rental Booked) | Booking made, online or by staff |
rental-confirmed | Confirm |
rental-pickedup | Picked up, or delivered |
rental-checkout (Rental Started) | Check-out |
rental-droppedoff | Mark Returned |
rental-returned | Check-in |
rental-deposit-refunded | Deposit refund |
rental-cancellation | Cancel |
Ready, active, overdue, complete, payments, deposit holds and deductions send nothing. Edit the wording in Email and SMS templates.
When it's done
- The config and items exist, and a quote for a sample booking reads right.
- Online:
/rentallists the items and a test booking reaches checkout with the right total. - Each rental's status, deposit status and payment status match what happened.
- Paid, refunded and kept money is in Books.
What it can't do yet
- Tax can differ. The booking saves the config's
taxRate(rental.service.ts589). Checkout charges the site's tax set-up (discount.service.ts~2125). The rental's total can then differ from what the customer paid. - Restrictions are thin. Online and staff bookings pass only the email to the check (729). An age rule never fails then. A location rule that allows only listed countries refuses every booking. Licence, membership and custom aren't checked.
- Stored but not used: the config's
cancellationpolicy, opening days and hours, lead times, cut-offs, delivery radius, and thenotificationslists. Fees and deposits don't scale with quantity. - No reminders and no automatic overdue. Run the lists in step 7.
- No rentals section in My Account. Customers see them inside Orders.
Don'ts
- Don't send the customer emails, refund, keep a deposit or cancel without a yes.
- Don't turn on the
rentalsite feature without a yes. - Don't point an item at a config name that doesn't exist.
- Don't trust a quote alone: check availability as well.
- Don't record a cash refund until the money has been handed back.
- Don't check in after marking returned with a real time without checking the late fee.