Catering Business Quote Calculator
Catering quotes are complicated. Guest count, menu tier, service style, bar packages, dietary requirements, equipment rentals - every choice affects the final price. A calculator that handles all this math instantly converts website visitors into qualified leads who already know what to expect.
The key to catering pricing is per-person math. Most costs scale directly with guest count, but you also need minimums, tiered pricing, and add-ons that stack. Here's how to build a calculator that handles all of it.
Guest Count and Event Type
Start with the basics: how many people and what kind of event. These two fields drive everything else.
const eventSection = form.addSubform('event', {
title: 'Event Details'
});
eventSection.addRow(row => {
row.addInteger('guestCount', {
label: 'Number of Guests',
isRequired: true,
min: 10,
max: 500,
placeholder: 'Minimum 10 guests'
});
});
eventSection.addRow(row => {
row.addDropdown('eventType', {
label: 'Event Type',
isRequired: true,
options: [
{ id: 'corporate', name: 'Corporate Event' },
{ id: 'wedding', name: 'Wedding Reception' },
{ id: 'birthday', name: 'Birthday Party' },
{ id: 'holiday', name: 'Holiday Party' },
{ id: 'other', name: 'Other' }
]
});
});Setting a minimum guest count (10 in this example) prevents quotes for events too small to be profitable. The event type can later influence pricing - weddings often have higher expectations than birthday parties.
Menu Packages
Most caterers offer tiered menus: a basic option, a mid-range, and premium choices. Each tier has a base per-person price that forms the foundation of the quote.
const menuSection = form.addSubform('menu', {
title: 'Menu Selection'
});
menuSection.addRow(row => {
row.addDropdown('menuTier', {
label: 'Menu Package',
isRequired: true,
options: [
{ id: 'essential', name: 'Essential ($25/person)' },
{ id: 'classic', name: 'Classic ($45/person)' },
{ id: 'premium', name: 'Premium ($75/person)' },
{ id: 'luxury', name: 'Luxury ($120/person)' }
]
});
});
const menuTier = menuSection.dropdown('menuTier');
// Show cuisine options based on tier
menuSection.addRow(row => {
row.addCheckboxList('cuisineStyles', {
label: 'Cuisine Styles',
isVisible: () => !!menuTier?.value(),
options: () => {
const tier = menuTier?.value();
const base = [
{ id: 'american', name: 'American' },
{ id: 'italian', name: 'Italian' },
{ id: 'mexican', name: 'Mexican' }
];
if (tier === 'premium' || tier === 'luxury') {
return [
...base,
{ id: 'asian', name: 'Asian Fusion' },
{ id: 'mediterranean', name: 'Mediterranean' }
];
}
if (tier === 'luxury') {
return [
...base,
{ id: 'asian', name: 'Asian Fusion' },
{ id: 'mediterranean', name: 'Mediterranean' },
{ id: 'french', name: 'French' }
];
}
return base;
},
max: () => menuTier?.value() === 'luxury' ? 3 : 2
});
});Notice how cuisine options change based on the selected tier. Essential packages might offer three choices, while luxury packages unlock more exotic options and allow selecting more cuisines.
Pro tip
Include pricing in the option labels ("Premium ($75/person)"). Customers appreciate knowing costs upfront without having to calculate or guess.
Service Style
How food is served affects staffing costs. Buffet is cheapest, plated service requires more servers, and food stations need attendants.
menuSection.addRow(row => {
row.addRadioButton('serviceStyle', {
label: 'Service Style',
isRequired: true,
options: [
{ id: 'buffet', name: 'Buffet Style' },
{ id: 'plated', name: 'Plated Service (+$10/person)' },
{ id: 'stations', name: 'Food Stations (+$15/person)' },
{ id: 'family', name: 'Family Style (+$8/person)' }
],
orientation: 'vertical'
});
});Radio buttons work well here since customers can only choose one service style. The pricing delta is shown directly in the option label.
Dietary Accommodations
Modern events need dietary options. Some are standard (vegetarian, gluten-free), others require special sourcing and add cost (kosher, halal).
menuSection.addRow(row => {
row.addCheckboxList('dietaryNeeds', {
label: 'Dietary Accommodations Needed',
options: [
{ id: 'vegetarian', name: 'Vegetarian Options' },
{ id: 'vegan', name: 'Vegan Options' },
{ id: 'glutenFree', name: 'Gluten-Free Options' },
{ id: 'dairyFree', name: 'Dairy-Free Options' },
{ id: 'kosher', name: 'Kosher (+$5/person)' },
{ id: 'halal', name: 'Halal (+$5/person)' }
],
orientation: 'vertical'
});
});CheckboxList lets customers select multiple dietary needs. The pricing logic will add per-person charges for options that cost extra.
Bar Service and Add-ons
Bar service is often the second-biggest line item after food. Offer packages from beer-and-wine to premium open bar, plus food add-ons like appetizers and dessert stations.
const extrasSection = form.addSubform('extras', {
title: 'Additional Services'
});
extrasSection.addRow(row => {
row.addCheckbox('barService', {
label: 'Bar Service'
});
});
const barService = extrasSection.checkbox('barService');
extrasSection.addRow(row => {
row.addDropdown('barPackage', {
label: 'Bar Package',
isVisible: () => barService?.value() === true,
options: [
{ id: 'beer-wine', name: 'Beer & Wine ($15/person)' },
{ id: 'standard', name: 'Standard Open Bar ($25/person)' },
{ id: 'premium', name: 'Premium Open Bar ($40/person)' },
{ id: 'cash', name: 'Cash Bar (no charge)' }
]
});
});
extrasSection.addRow(row => {
row.addCheckboxList('addons', {
label: 'Add-ons',
options: [
{ id: 'appetizers', name: 'Passed Appetizers (+$8/person)' },
{ id: 'dessert', name: 'Dessert Station (+$12/person)' },
{ id: 'latenight', name: 'Late Night Snacks (+$10/person)' },
{ id: 'coffee', name: 'Coffee & Tea Service (+$4/person)' }
],
orientation: 'vertical'
});
});The bar package dropdown only appears when bar service is checked. This keeps the form clean - customers who don't want bar service don't see irrelevant options.
See more quote calculators in our gallery.
Equipment Rentals
Many events need tables, chairs, linens, and tableware. Some caterers include basics, others charge separately. Equipment rentals add per-person costs that scale with guest count.
extrasSection.addRow(row => {
row.addCheckboxList('equipment', {
label: 'Equipment Rentals',
options: [
{ id: 'tables', name: 'Tables & Chairs ($8/person)' },
{ id: 'linens', name: 'Premium Linens ($5/person)' },
{ id: 'china', name: 'China & Glassware ($6/person)' },
{ id: 'tent', name: 'Tent (quoted separately)' }
],
orientation: 'vertical'
});
});For items that can't be priced per-person (like tents), indicate "quoted separately" and handle those in follow-up communication.
The Pricing Logic
Here's where it all comes together. We need to calculate a per-person total, then multiply by guest count.
const guestCount = eventSection.integer('guestCount');
const menuTier = menuSection.dropdown('menuTier');
const serviceStyle = menuSection.radioButton('serviceStyle');
const dietaryNeeds = menuSection.checkboxList('dietaryNeeds');
const barService = extrasSection.checkbox('barService');
const barPackage = extrasSection.dropdown('barPackage');
const addons = extrasSection.checkboxList('addons');
const equipment = extrasSection.checkboxList('equipment');
// Menu tier pricing
const menuPrices: Record<string, number> = {
'essential': 25,
'classic': 45,
'premium': 75,
'luxury': 120
};
// Service style upcharges
const servicePrices: Record<string, number> = {
'buffet': 0,
'plated': 10,
'stations': 15,
'family': 8
};
// Bar package pricing
const barPrices: Record<string, number> = {
'beer-wine': 15,
'standard': 25,
'premium': 40,
'cash': 0
};
// Add-on pricing
const addonPrices: Record<string, number> = {
'appetizers': 8,
'dessert': 12,
'latenight': 10,
'coffee': 4
};
// Equipment pricing
const equipmentPrices: Record<string, number> = {
'tables': 8,
'linens': 5,
'china': 6,
'tent': 0
};
const totalPerPerson = form.computedValue(() => {
let perPerson = menuPrices[menuTier?.value() || 'essential'] || 0;
// Service style
perPerson += servicePrices[serviceStyle?.value() || 'buffet'] || 0;
// Dietary upcharges
const dietary = dietaryNeeds?.value() || [];
if (dietary.includes('kosher')) perPerson += 5;
if (dietary.includes('halal')) perPerson += 5;
// Bar service
if (barService?.value() && barPackage?.value()) {
perPerson += barPrices[barPackage.value()!] || 0;
}
// Add-ons
const selectedAddons = addons?.value() || [];
for (const addon of selectedAddons) {
perPerson += addonPrices[addon] || 0;
}
// Equipment
const selectedEquipment = equipment?.value() || [];
for (const item of selectedEquipment) {
perPerson += equipmentPrices[item] || 0;
}
return perPerson;
});
const totalPrice = form.computedValue(() => {
const guests = guestCount?.value() || 0;
return totalPerPerson() * guests;
});The logic is straightforward: start with the menu tier price, add service style upcharge, add any dietary upcharges, add bar package if selected, add all selected add-ons, add all selected equipment. Multiply by guest count.
Displaying the Quote
Show the breakdown clearly: per-person cost, subtotal, service fee, and grand total. A sticky pricing section keeps the estimate visible as customers scroll.
const pricingSection = form.addSubform('pricing', {
title: 'Your Estimate',
sticky: 'bottom'
});
pricingSection.addRow(row => {
row.addPriceDisplay('perPerson', {
label: 'Per Person',
computedValue: () => totalPerPerson(),
currency: '$',
decimals: 2
});
});
pricingSection.addRow(row => {
row.addPriceDisplay('subtotal', {
label: 'Subtotal',
computedValue: () => totalPrice(),
currency: '$',
decimals: 2,
suffix: () => {
const guests = guestCount?.value();
return guests ? ' for ' + guests + ' guests' : '';
}
});
});
// Service fee (18%)
pricingSection.addRow(row => {
row.addPriceDisplay('serviceFee', {
label: 'Service Fee (18%)',
computedValue: () => totalPrice() * 0.18,
currency: '$',
decimals: 2
});
});
pricingSection.addRow(row => {
row.addPriceDisplay('total', {
label: 'Estimated Total',
computedValue: () => totalPrice() * 1.18,
currency: '$',
decimals: 2,
variant: 'large'
});
}); The sticky: 'bottom' keeps the pricing section pinned to the bottom of the viewport until the customer scrolls past it. They always see how their choices affect the total.
Pro tip
Always include service fees in the estimate. Surprising customers with an 18-20% fee after they've mentally committed to a price creates friction and erodes trust.
Order Minimums
Catering has fixed costs that don't scale with guest count: delivery, setup, kitchen prep. Order minimums ensure profitability. Show a warning when the current total falls below the minimum for the selected tier.
// Minimum order requirements
const guestCount = eventSection.integer('guestCount');
const menuTier = menuSection.dropdown('menuTier');
const minimums: Record<string, number> = {
'essential': 1000,
'classic': 1500,
'premium': 2500,
'luxury': 5000
};
eventSection.addRow(row => {
row.addTextPanel('minimumWarning', {
label: '',
computedValue: () => {
const tier = menuTier?.value();
const guests = guestCount?.value() || 0;
const minimum = minimums[tier || 'essential'];
const currentTotal = totalPrice();
if (currentTotal < minimum) {
const needed = minimum - currentTotal;
return 'Minimum order: $' + minimum +
'. Add $' + needed.toFixed(0) + ' to meet minimum.';
}
return '';
},
isVisible: () => {
const tier = menuTier?.value();
const minimum = minimums[tier || 'essential'];
return totalPrice() < minimum;
}
});
});This shows customers exactly how much more they need to add to meet the minimum. It's better than a hard block - small events can still add services to reach the threshold.
What Makes Catering Calculators Work
A few patterns make these calculators effective:
Per-person clarity: Show the per-person cost alongside the total. "$75 per person for 50 guests = $3,750" is easier to evaluate than just "$3,750".
Visible pricing: Put prices in option labels. Don't make customers guess or calculate. "Premium Open Bar ($40/person)" beats "Premium Open Bar".
Progressive disclosure: Show bar packages only when bar service is selected. Show cuisine options only after menu tier is chosen. Keep the form manageable.
Instant feedback: Every selection updates the total immediately. Customers can experiment with options and see exactly how each choice affects price.
Realistic estimates: Include service fees, minimums, and realistic pricing. An estimate that's $2,000 lower than the actual quote damages trust more than it generates leads.
Beyond the Calculator
The calculator gives an estimate, but catering quotes often need customization. Add fields for:
- Event date and time (affects availability and pricing for peak seasons)
- Venue address (delivery fees, kitchen access)
- Special requests (custom menus, themed presentations)
- Contact information for follow-up
The calculator qualifies the lead and sets expectations. Your sales team can then refine the quote with event-specific details.
Common Questions
How do I handle seasonal pricing?
Add event date to your form and adjust pricing based on the month. Peak wedding season (May-October) might have a 10-15% premium. You can either show this in the estimate or note 'peak season pricing may apply' and adjust in follow-up.
Should I show exact prices or ranges?
Exact prices work better for standard packages. For custom menus or unique requests, show 'starting at' prices or ranges. The goal is setting realistic expectations - underquoting to get leads backfires when the real quote comes in higher.
How do I handle events that need a custom quote?
For very large events (500+ guests) or unusual requests, show a message like 'Events of this size require a custom quote' and collect contact information. Some complexity is better handled by your sales team than automated.
What about delivery and setup fees?
You can add a flat delivery fee, a distance-based fee (using the address field), or include delivery in your per-person pricing. Many caterers include delivery within a certain radius and charge for longer distances.