How to Create a Quote Calculator for Your Website
A quote calculator lets visitors price your services themselves. They pick options, the price updates instantly, and you get qualified leads who already know what they'll pay. No more back-and-forth emails about pricing.
This guide walks through building a cleaning service quote calculator, but the pattern works for any service business: landscaping, photography, auto detailing, consulting. The inputs change, the pricing logic stays the same.
What Makes a Good Quote Calculator
Before writing code, think about what your calculator needs to do:
- Collect the right inputs - What affects your pricing? Square footage, service type, add-ons, frequency?
- Calculate in real-time - Price updates as users make selections. No "calculate" button.
- Show the price prominently - The whole point is instant pricing. Make it visible.
- Handle complexity gracefully - Multiple multipliers, discounts, add-ons. The math can get complicated.
Step 1: Plan Your Pricing Structure
Write down your pricing rules before touching code. For a cleaning service, this might look like:
- Base rate - varies by property type (apartment $80, house $120)
- Size adjustment - $15 per 500 square feet
- Service multiplier - standard 1x, deep clean 1.5x, move-out 1.75x
- Frequency discount - weekly 20% off, bi-weekly 15% off
- Add-ons - inside oven $35, inside fridge $35, windows $45
In code, define these as data structures at the top of your form:
// Define your pricing
const baseRates: Record<string, number> = {
'apartment': 80,
'house': 120,
'condo': 95
};
const cleaningMultipliers: Record<string, number> = {
'standard': 1.0,
'deep': 1.5,
'move-out': 1.75
};
const frequencyDiscounts: Record<string, number> = {
'one-time': 0,
'weekly': 20, // 20% off
'bi-weekly': 15,
'monthly': 10
};This keeps pricing separate from logic. When rates change, you update one place.
Pro tip
Don't hardcode prices in your calculation functions. Define them once at the top, reference them everywhere. You'll thank yourself when prices change.
Step 2: Create the Form Structure
Break your calculator into logical sections. Each section becomes a subform:
// Create sections for your form
const property = form.addSubform('property', {
title: () => 'Property Details'
});
const options = form.addSubform('options', {
title: () => 'Service Options'
});
const summary = form.addSubform('summary', {
title: () => 'Your Quote'
});Three sections is a good default: inputs for what they need, options for how they want it, summary showing the price.
Step 3: Add Input Fields
Now add the fields that collect information. Start with the basics:
property.addRow(row => {
row.addDropdown('type', {
label: 'Property Type',
defaultValue: 'house',
options: [
{ id: 'apartment', name: 'Apartment' },
{ id: 'house', name: 'House' },
{ id: 'condo', name: 'Condo' }
]
});
row.addInteger('sqft', {
label: 'Square Feet',
min: 500,
max: 5000,
defaultValue: 1500
});
});For service options, radio buttons work well when users pick one option from a list:
options.addRow(row => {
row.addRadioButton('cleaningType', {
label: 'Cleaning Type',
defaultValue: 'standard',
orientation: 'vertical',
options: [
{ id: 'standard', name: 'Standard' },
{ id: 'deep', name: 'Deep Clean' },
{ id: 'move-out', name: 'Move-Out' }
]
});
row.addRadioButton('frequency', {
label: 'Frequency',
defaultValue: 'one-time',
orientation: 'vertical',
options: [
{ id: 'one-time', name: 'One-Time' },
{ id: 'weekly', name: 'Weekly' },
{ id: 'bi-weekly', name: 'Bi-Weekly' }
]
});
});For add-ons where users can select multiple, use a checkbox list:
addons.addRow(row => {
row.addCheckboxList('extras', {
label: 'Add-On Services',
orientation: 'vertical',
options: [
{ id: 'insideOven', name: 'Inside Oven (+$35)' },
{ id: 'insideFridge', name: 'Inside Fridge (+$35)' },
{ id: 'windows', name: 'Interior Windows (+$45)' }
]
});
});Want to see a complete example? Check our cleaning calculator template.
Step 4: Write the Pricing Logic
This is where the magic happens. Create functions that calculate prices based on user inputs:
// Calculate base price from inputs
const getBasePrice = () => {
const type = property.dropdown('type')?.value() || 'house';
const sqft = property.integer('sqft')?.value() || 1500;
const baseRate = baseRates[type] || 120;
const sqftCost = Math.floor(sqft / 500) * 15;
return baseRate + sqftCost;
};
// Calculate final price with multipliers and discounts
const getFinalPrice = () => {
const cleaningType = options.radioButton('cleaningType')?.value() || 'standard';
const frequency = options.radioButton('frequency')?.value() || 'one-time';
const basePrice = getBasePrice();
const multiplier = cleaningMultipliers[cleaningType] || 1.0;
const subtotal = basePrice * multiplier;
const discountPercent = frequencyDiscounts[frequency] || 0;
const discount = subtotal * (discountPercent / 100);
return Math.round(subtotal - discount);
};The pattern: get values from fields, look up rates in your pricing data, apply multipliers and discounts.
For add-ons, loop through selected options and sum their prices:
const addonPrices: Record<string, number> = {
'insideOven': 35,
'insideFridge': 35,
'windows': 45
};
const getAddonsTotal = () => {
const selected = addons.checkboxList('extras')?.value() || [];
let total = 0;
for (const addonId of selected) {
total += addonPrices[addonId] || 0;
}
return total;
};Step 5: Display the Price
Add a price display field that calls your calculation function:
summary.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Estimated Price',
computedValue: () => getFinalPrice() + getAddonsTotal(),
alignment: 'center',
variant: 'large',
suffix: () => {
const frequency = options.radioButton('frequency')?.value();
if (frequency === 'weekly') return '/week';
if (frequency === 'bi-weekly') return '/visit';
return '';
}
});
}); The computedValue runs every time any input changes. Users see the price update instantly.
Add a message showing savings for recurring customers:
summary.addRow(row => {
row.addTextPanel('savings', {
computedValue: () => {
const frequency = options.radioButton('frequency')?.value();
const discountPercent = frequencyDiscounts[frequency] || 0;
if (discountPercent > 0) {
return `You save ${discountPercent}% with ${frequency} service!`;
}
return 'Choose recurring service to save up to 20%';
}
});
});This gives users a reason to choose recurring service - they see exactly how much they save.
Common Pricing Patterns
Tiered Pricing
Different rates at different quantities:
const getTieredPrice = (quantity: number) => {
if (quantity >= 100) return quantity * 5; // bulk rate
if (quantity >= 50) return quantity * 7;
if (quantity >= 10) return quantity * 9;
return quantity * 12; // small order rate
};Minimum Order
Enforce a minimum price:
const getFinalPrice = () => {
const calculated = getBasePrice() + getAddonsTotal();
const minimum = 75;
return Math.max(calculated, minimum);
};Package Deals
Discount when buying a bundle:
const getPackageDiscount = () => {
const selected = addons.checkboxList('services')?.value() || [];
if (selected.length >= 3) return 0.15; // 15% off for 3+ services
if (selected.length >= 2) return 0.10; // 10% off for 2 services
return 0;
};Date-Based Pricing
Higher rates for rush jobs or weekends:
const getRushMultiplier = () => {
const date = form.datePicker('serviceDate')?.value();
if (!date) return 1;
const daysUntil = Math.ceil((date.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
if (daysUntil <= 1) return 1.5; // same/next day: 50% rush fee
if (daysUntil <= 3) return 1.25; // within 3 days: 25% rush fee
return 1;
};Testing Your Calculator
Before publishing, test these scenarios:
- Minimum values - smallest possible order. Does the price make sense?
- Maximum values - biggest possible order. No overflow issues?
- All add-ons selected - price should be sum of base + all add-ons
- Discount stacking - if multiple discounts apply, is the math right?
- Default values - when user hasn't touched anything, is there a sensible price?
Pro tip
Use the preview panel while building. Change inputs and verify the price updates correctly. Catch math errors before your customers do.
After the Quote
A quote calculator gets people interested. What happens next?
Collect Contact Info
Add fields for name, email, phone after the quote section. People who've seen their price are more likely to share contact info.
Generate a PDF Quote
FormTs can generate a PDF with the quote details. Users download it, you have a record of what was quoted. See our PDF generation guide.
Send to Your CRM
Use webhooks to send submissions to your CRM, email, or Zapier. The quote details flow into your sales pipeline automatically.
Embedding Your Calculator
Once your calculator works, embed it on your website. See our embedding guide for step-by-step instructions for WordPress, Wix, Shopify, and other platforms.
Common Questions
Can I show prices without the user clicking anything?
Yes. Set default values on your fields and the price calculates immediately. Users see a starting price as soon as the calculator loads, then adjust to their needs.
How do I handle complex pricing with many variables?
Break it into smaller functions. One function for base price, one for add-ons, one for discounts. Then combine them in a final getFinalPrice() function. Easier to debug than one giant calculation.
Can I hide the price until certain fields are filled?
Yes. Use isVisible on the price display field with a condition that checks if required fields have values. The price only appears once users have entered enough information.
How do I format the price with currency symbols?
The PriceDisplay field automatically formats as currency. You can also add a prefix ('$') or suffix ('/month') that updates based on user selections.
Can customers save their quote and come back later?
When they submit the form, FormTs stores the submission. You can send them a link to view their quote, or generate a PDF they can download and keep.