Investing in a short-term rental property on platforms like Airbnb or VRBO can be significantly more lucrative than traditional long-term leasing. However, the overhead is higher, and income is variable. To determine if a property is a good investment, you must look beyond the nightly rate and account for occupancy fluctuations and hidden operating costs.
Key Metrics for STR Success
Average Daily Rate (ADR): The average price paid per night. This changes based on weekends, holidays, and local events.
Occupancy Rate: The percentage of available nights that are actually booked. Most successful urban rentals aim for 65-75%.
Revenue Per Available Room (RevPAR): Your ADR multiplied by your occupancy rate. This is the truest measure of a property's earning power.
Operating Expense Ratio: STRs typically have higher expenses (25-45% of revenue) compared to long-term rentals (roughly 10-15%).
Example Calculation
Imagine you have a 2-bedroom condo in a popular tourist destination:
Many new hosts fail to account for "consumables." This includes toilet paper, coffee, laundry detergent, and snacks. Additionally, you should set aside 5% of your revenue for a "damage and maintenance fund" to handle the inevitable wear and tear that comes with high guest turnover.
How to Improve Your Profitability
If your projected profits are low, consider these strategies:
1. Dynamic Pricing: Use software like PriceLabs or Wheelhouse to adjust prices daily based on demand.
2. Direct Bookings: Build a website to avoid the 3-15% platform fees charged by major OTAs.
3. Upselling: Offer early check-ins, equipment rentals (bikes, kayaks), or curated gift baskets for an extra fee.
function calculateSTRProfit() {
// Inputs
var nightlyRate = parseFloat(document.getElementById("nightlyRate").value) || 0;
var occupancyRate = parseFloat(document.getElementById("occupancyRate").value) || 0;
var bookingsMonth = parseFloat(document.getElementById("bookingsMonth").value) || 0;
var platformFeePerc = parseFloat(document.getElementById("platformFee").value) || 0;
var cleaningFeeIn = parseFloat(document.getElementById("cleaningFeeIn").value) || 0;
var cleaningFeeOut = parseFloat(document.getElementById("cleaningFeeOut").value) || 0;
var mortgage = parseFloat(document.getElementById("mortgage").value) || 0;
var utilities = parseFloat(document.getElementById("utilities").value) || 0;
var taxIns = parseFloat(document.getElementById("taxIns").value) || 0;
var maintenance = parseFloat(document.getElementById("maintenance").value) || 0;
// Days in average month
var daysInMonth = 30.41;
var occupiedNights = (occupancyRate / 100) * daysInMonth;
// Revenue Logic
var monthlyRentalIncome = nightlyRate * occupiedNights;
var totalCleaningIn = bookingsMonth * cleaningFeeIn;
var grossRevenue = monthlyRentalIncome + totalCleaningIn;
// Expense Logic
var platformFeeAmount = monthlyRentalIncome * (platformFeePerc / 100);
var totalCleaningOut = bookingsMonth * cleaningFeeOut;
var fixedExpenses = mortgage + utilities + taxIns + maintenance;
var totalExpenses = fixedExpenses + platformFeeAmount + totalCleaningOut;
// Profit Logic
var netMonthlyProfit = grossRevenue – totalExpenses;
var netAnnualProfit = netMonthlyProfit * 12;
// Display Results
document.getElementById("resGrossRental").innerText = "$" + monthlyRentalIncome.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.getElementById("resCleaningIn").innerText = "$" + totalCleaningIn.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.getElementById("resPlatformFees").innerText = "-$" + platformFeeAmount.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.getElementById("resTotalExpenses").innerText = "-$" + totalExpenses.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
var netMonthlyEl = document.getElementById("resNetMonthly");
netMonthlyEl.innerText = "$" + netMonthlyProfit.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
netMonthlyEl.style.color = netMonthlyProfit >= 0 ? "#2e7d32" : "#d93025";
var netAnnualEl = document.getElementById("resNetAnnual");
netAnnualEl.innerText = "$" + netAnnualProfit.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
netAnnualEl.style.color = netAnnualProfit >= 0 ? "#2e7d32" : "#d93025";
document.getElementById("strResults").style.display = "block";
}