Building a Dynamic Pricing Estimator in WordPress (When “Simple” Isn’t Simple)

Samuel HadsallSamuel Hadsall

I recently wrapped up a project that, on paper, sounded pretty straightforward:

Build a service estimator that collects inputs and calculates pricing.

Cool. Done that before.

Except this one turned into a mix of:

  • Rule-based pricing
  • Multi-step React UI
  • Conditional service flows
  • Custom email templating
  • And a handful of “wait… why is this happening?” bugs

So yeah… not simple.


The Idea vs Reality

The idea was:

  • User selects services
  • Inputs some details
  • Gets a price range
  • Receives a summary email

The reality was:

  • Pricing depended on multiple variables and edge cases
  • Some services followed completely different logic paths
  • The frontend had to maintain state across multiple steps
  • Emails needed to dynamically change based on the estimate

At some point, this stopped being a “form with math” and became a system.


Pricing Logic Got Complicated Fast

This wasn’t a flat-rate setup.

Pricing depended on things like:

  • Square footage (calculated from inputs)
  • Service-specific rules
  • Add-ons (topicals, deodorizer, etc.)
  • Tiered pricing (machine-made vs handmade rugs)

One thing that helped a lot was normalizing data before pricing.

Instead of calculating directly from raw inputs, I added a layer that:

  • Converts dimensions → square footage
  • Maps values to pricing tiers
  • Standardizes everything before it hits pricing logic

Without that, debugging would’ve been a nightmare.

Example

Rug pricing needed to show a range, even if the user selected a specific type.

So even if they chose “handmade,” the UI still needed to display:

Machine-made → Handmade pricing range

That’s the kind of requirement that forces you to rethink how your pricing logic is structured.


React State (and That One Error)

The estimator UI was built as a multi-step React flow.

Everything depended on previous steps, so state management had to be tight.

At one point I hit this gem:

“Rendered more hooks than during the previous render”

If you’ve worked with React, you already know what that means.

I had conditionals affecting when hooks were being called, which broke everything.

Fix

  • Keep hooks consistent across renders
  • Move conditional logic inside components, not around them
  • Avoid changing component structure between renders

Simple in theory… annoying in practice.


Email Templates Needed Logic Too

The project also required dynamic email templates.

Not just token replacement—but actual conditionals.

So I ended up supporting something like:

{{#if is_restoration_only}}

Where it got tricky

  • Parsing conditional blocks
  • Supporting nested logic
  • Avoiding encoding issues (looking at you &&)

That last one caused a few headaches. Saving logic inside HTML fields can get weird fast if you’re not careful with sanitization.


Tables on Mobile… Not Great

Initially, service selections and summaries were built using tables.

Looked fine on desktop.

On mobile, it fell apart.

  • Content got cramped
  • Hard to read
  • Not great for interaction

Fix

  • Switched to div-based layouts
  • Stacked content vertically
  • Focused on readability instead of density

Nothing groundbreaking—just one of those “should’ve done this earlier” moments.


The Weird Bugs

This project had a few of those bugs where everything looks right… but isn’t.

A few highlights:

  • Services saving correctly but not showing up in queries
  • Query params like ?gtm_debug interfering with frontend behavior
  • Line items combining when they shouldn’t

One example:

Topical treatments were being merged into a single line item, when they actually needed to be separate entries for downstream systems.

Not a huge change—but it required rethinking how line items were structured.


What Helped the Most

A few things made a big difference:

  • Normalizing data before applying logic
  • Treating services as atomic units (no implicit combining)
  • Keeping frontend and backend responsibilities clear
  • Adding structure to things early (even if it felt like overkill at the time)

What I’d Do Differently

Looking back:

  • I’d abstract the pricing engine earlier
  • Define stricter schemas for services and inputs
  • Build mobile-first instead of adapting later
  • Add better debugging/logging from the start

Final Thoughts

This project was a good reminder that:

The complexity isn’t always in the code—it’s in the rules.

What starts as “just an estimator” can quickly turn into a system that needs to handle a lot of edge cases and evolving requirements.

And if that system isn’t structured well early on, it gets messy fast.


If you’re working on something similar—estimators, pricing tools, complex forms—spend the extra time upfront on structure.

It’ll save you later.

Back to top