Plumbing Service Quote Form
A pipe bursts at 2 AM. Water is flooding the basement. Your customer grabs their phone and searches "emergency plumber." They find three options. One says "call for service." One has a generic contact form. Yours shows exactly what emergency service costs and lets them request help immediately. Who gets the job?
Plumbing emergencies don't wait for business hours. Neither should your quote process. A form that captures the problem, shows pricing based on urgency, and collects the right details upfront converts panicked visitors into booked jobs.
This guide covers building quote forms for the core plumbing services: repairs, installations, and emergency calls. Each needs different information and has different pricing models.
Service Type Selection
Start by understanding what they need. The entire form adapts based on this first question.
form.addRow(row => {
row.addDropdown('serviceType', {
label: 'What do you need help with?',
options: [
{ id: 'repair', name: 'Repair / Fix a Problem' },
{ id: 'install', name: 'New Installation' },
{ id: 'maintenance', name: 'Maintenance / Inspection' },
{ id: 'emergency', name: 'Emergency Service' }
],
isRequired: true
});
});Four categories cover most plumbing work: repairs (fixing existing problems), new installations, routine maintenance, and emergencies. Emergency gets its own category because the pricing and urgency are fundamentally different.
Problem Type: Different Flows
What's wrong - or what they want installed - determines the next questions. The options should change based on whether it's a repair or installation.
form.addRow(row => {
row.addDropdown('problemType', {
label: 'What type of problem?',
options: () => {
const service = form.dropdown('serviceType')?.value();
if (service === 'install') {
return [
{ id: 'water-heater', name: 'Water Heater' },
{ id: 'toilet', name: 'Toilet' },
{ id: 'faucet', name: 'Faucet / Sink' },
{ id: 'garbage-disposal', name: 'Garbage Disposal' },
{ id: 'dishwasher', name: 'Dishwasher Connection' },
{ id: 'sump-pump', name: 'Sump Pump' },
{ id: 'water-softener', name: 'Water Softener' },
{ id: 'other', name: 'Other Installation' }
];
}
return [
{ id: 'leak', name: 'Leak / Dripping' },
{ id: 'clog', name: 'Clogged Drain' },
{ id: 'no-hot-water', name: 'No Hot Water' },
{ id: 'low-pressure', name: 'Low Water Pressure' },
{ id: 'running-toilet', name: 'Running Toilet' },
{ id: 'burst-pipe', name: 'Burst / Broken Pipe' },
{ id: 'sewer', name: 'Sewer Issue' },
{ id: 'other', name: 'Other Problem' }
];
},
isRequired: true
});
});For repairs, focus on symptoms customers can identify: leaks, clogs, no hot water. For installations, list the fixtures and systems you install. "Other" catches everything that doesn't fit neatly.
Pro tip
"Burst pipe" and "sewer issue" signal emergencies even if they didn't select emergency service. Consider auto-escalating urgency when these are selected, or showing a warning about water damage.
Problem Location
Where the problem is helps you prepare - and sometimes affects pricing. Basement work might need special equipment. Multiple locations suggest a bigger issue.
form.addRow(row => {
row.addCheckboxList('location', {
label: 'Where is the problem?',
options: [
{ id: 'kitchen', name: 'Kitchen' },
{ id: 'bathroom', name: 'Bathroom' },
{ id: 'basement', name: 'Basement' },
{ id: 'laundry', name: 'Laundry Room' },
{ id: 'outdoor', name: 'Outdoor / Yard' },
{ id: 'multiple', name: 'Multiple Locations' }
],
isVisible: () => {
const service = form.dropdown('serviceType')?.value();
return service === 'repair' || service === 'emergency';
}
});
});Checkbox list allows multiple selections. "Water in the kitchen AND bathroom" tells you more than either alone - possibly a main line issue. This helps your team arrive prepared.
Urgency Tiers
This is where plumbing businesses differentiate. Emergency service costs more - customers expect that. What they want is transparency about what each tier means and costs.
form.addRow(row => {
row.addRadioButton('urgency', {
label: 'How urgent is this?',
options: [
{ id: 'emergency', name: 'Emergency - Water flooding / major leak' },
{ id: 'urgent', name: 'Urgent - Need help today' },
{ id: 'soon', name: 'Soon - Within 2-3 days' },
{ id: 'scheduled', name: 'Flexible - Schedule at convenience' }
],
defaultValue: 'soon',
isVisible: () => {
const service = form.dropdown('serviceType')?.value();
return service !== 'maintenance';
}
});
});Four tiers work well: true emergency (active flooding), urgent (same day), soon (2-3 days), and flexible (scheduled). Price each tier clearly. Someone with water pouring into their basement will pay the emergency fee. Someone with a dripping faucet will happily wait for the cheaper scheduled option.
Property Details
Property type and age affect both diagnosis and pricing. Older homes often have galvanized pipes, different code requirements, and hidden surprises.
const propertySection = form.addSubform('property', {
title: 'Property Details'
});
propertySection.addRow(row => {
row.addRadioButton('propertyType', {
label: 'Property Type',
options: [
{ id: 'house', name: 'Single Family Home' },
{ id: 'condo', name: 'Condo / Townhouse' },
{ id: 'apartment', name: 'Apartment' },
{ id: 'commercial', name: 'Commercial Property' }
],
defaultValue: 'house'
});
});
propertySection.addRow(row => {
row.addDropdown('propertyAge', {
label: 'Approximate Age of Property',
options: [
{ id: 'new', name: 'Less than 10 years' },
{ id: 'medium', name: '10-30 years' },
{ id: 'old', name: '30-50 years' },
{ id: 'very-old', name: 'Over 50 years' },
{ id: 'unknown', name: 'Not sure' }
],
defaultValue: 'unknown'
});
});Commercial properties have different needs than residential - higher capacity, code requirements, business hours considerations. Property age helps estimate what you'll find behind the walls.
Service Call Pricing
For repairs and emergencies, you can't quote the fix until you see the problem. But you can quote the service call based on urgency.
const serviceFees: Record<string, number> = {
'emergency': 199,
'urgent': 129,
'soon': 89,
'scheduled': 69
};
form.addRow(row => {
row.addPriceDisplay('serviceFee', {
label: 'Service Call Fee',
computedValue: () => {
const urgency = form.radioButton('urgency')?.value() || 'soon';
return serviceFees[urgency];
},
isVisible: () => {
const service = form.dropdown('serviceType')?.value();
return service !== 'maintenance';
}
});
});
form.addRow(row => {
row.addTextPanel('feeNote', {
computedValue: () => {
const urgency = form.radioButton('urgency')?.value();
if (urgency === 'emergency') {
return '24/7 emergency service. Fee waived if repair exceeds $500.';
}
return 'Service fee applied toward repair cost if you proceed.';
},
isVisible: () => form.dropdown('serviceType')?.value() !== 'maintenance'
});
});The fee waiver note removes friction - customers don't feel like they're paying twice. The diagnostic fee rolls into the repair cost if they proceed. For small repairs, some plumbers waive it entirely.
Installation Estimates
Installation pricing is more predictable. You can show ranges based on what they want installed.
const installPrices: Record<string, { min: number; max: number }> = {
'water-heater': { min: 800, max: 2500 },
'toilet': { min: 200, max: 500 },
'faucet': { min: 150, max: 400 },
'garbage-disposal': { min: 250, max: 450 },
'dishwasher': { min: 150, max: 300 },
'sump-pump': { min: 500, max: 1200 },
'water-softener': { min: 1000, max: 3000 }
};
form.addRow(row => {
row.addTextPanel('installEstimate', {
computedValue: () => {
const problem = form.dropdown('problemType')?.value();
if (!problem || !installPrices[problem]) return null;
const price = installPrices[problem];
return `Typical range: $${price.min} - $${price.max} (parts + labor)`;
},
isVisible: () => form.dropdown('serviceType')?.value() === 'install'
});
});Use ranges rather than exact prices - installation costs vary based on access, existing plumbing, and specific product choices. The range sets realistic expectations without committing to a price you can't honor.
See more service quote calculators in action.
Service Area Considerations
Plumbing is local. You probably have a service area, and customers outside it waste everyone's time. Consider adding address validation early in the form.
For customers at the edge of your territory, you might charge a travel fee. See our address validation guide for implementing distance-based pricing.
Complete Example
Here's a working plumbing quote form covering the main service types:
export function plumbingQuote(form: FormTs) {
form.setTitle(() => 'Plumbing Service Request');
// Service type
form.addRow(row => {
row.addDropdown('serviceType', {
label: 'What do you need?',
options: [
{ id: 'repair', name: 'Repair / Fix' },
{ id: 'install', name: 'Installation' },
{ id: 'emergency', name: 'Emergency' }
],
isRequired: true
});
});
// Problem type
form.addRow(row => {
row.addDropdown('problemType', {
label: 'Type of Issue',
options: [
{ id: 'leak', name: 'Leak' },
{ id: 'clog', name: 'Clogged Drain' },
{ id: 'no-hot-water', name: 'No Hot Water' },
{ id: 'toilet', name: 'Toilet Issue' },
{ id: 'other', name: 'Other' }
],
isRequired: true
});
});
// Urgency
form.addRow(row => {
row.addRadioButton('urgency', {
label: 'Urgency',
options: [
{ id: 'emergency', name: 'Emergency ($199)' },
{ id: 'today', name: 'Today ($129)' },
{ id: 'scheduled', name: 'Scheduled ($69)' }
],
defaultValue: 'scheduled'
});
});
// Contact info
const contact = form.addSubform('contact', { title: 'Contact Information' });
contact.addRow(row => {
row.addTextbox('name', { label: 'Name', isRequired: true });
row.addTextbox('phone', { label: 'Phone', isRequired: true });
});
contact.addRow(row => {
row.addTextbox('address', { label: 'Service Address', isRequired: true });
});
form.configureSubmitButton({ label: 'Request Service' });
}This handles the core flow: service type, problem identification, urgency selection, and contact details. Add your specific pricing, service area, and you have a working quote calculator.
After the Form
What happens after submission matters as much as the form itself:
- Emergency requests - Call back within minutes, not hours
- Confirmations - Show what they requested and when to expect contact
- Instructions - For emergencies, tell them how to shut off water
- Scheduling - For non-urgent requests, let them pick a time window
The form captures the lead. Your response time converts it to revenue. In plumbing, speed often matters more than price.
Common Questions
Should I show repair prices online?
Show service call fees by urgency - that's what you can quote accurately. For actual repairs, use language like 'most drain clogs: $150-300' to set expectations without committing to prices that vary by situation. Full transparency on diagnostic fees, ranges for common repairs.
How do I handle after-hours emergency requests?
If you offer 24/7 service, make it prominent and price it clearly. If you don't, set expectations: 'Emergency requests after 6 PM will be contacted first thing in the morning.' For true emergencies (flooding, gas smell), consider providing emergency shutoff instructions while they wait.
Should I separate residential and commercial?
Yes, if you serve both. Commercial plumbing has different equipment, pricing, and timing considerations. Either have separate forms or add a property type question early that changes the entire flow. A restaurant's grease trap is nothing like a home clogged drain.
What fields should be required?
Require: service type, problem type, contact info. Optional: photos, detailed description, property details. Every required field is friction. Get the essentials for contact, gather details on the follow-up call. You're capturing a lead, not writing a work order.
How do I handle warranty work?
Add an option in service type or problem description for 'warranty/callback' requests. These need different handling - no service fee, priority scheduling, maybe a different team. Flag them clearly in your submission notifications.