Blog /
Build a quote calculator your client can drop into a sales page
I want to talk about a specific kind of project that, in my experience, freelancers underprice and dread.
A client emails you Tuesday morning: “can you put a calculator on our site?” Maybe a project-cost calculator. Maybe a savings estimator. Maybe a “how much will this cost depending on which plan you pick” widget that’s basically a glorified switch statement with some pretty CSS. The math is one paragraph. The actual building of the thing is two hours.
But the building of the thing is never the part that takes the time.
The part that takes the time is: where does it live? Build a Gutenberg block? Now you have a build pipeline, a JSX file, a deprecation strategy for when the client’s content gets re-saved, and a sidebar-options panel to design. Embed an iframe to a CodePen? Now the client’s branding doesn’t apply, the styling fights the CodePen frame, and the URL’s gone if CodePen ever changes its embed contract. Hand the client a static HTML file? Where do they put it? “Upload it to your hosting” is a sentence that ends a lot of freelancer engagements badly.
So you swallow the integration work, charge a few hundred dollars more than you’d like, and the calculator becomes a bigger engagement than the math ever justified.
The DSGo path is shorter. The calculator is a single self-contained HTML file. The client gets a URL on their own domain. You hand them a .zip, they upload it once, done.
This post walks through examples/quicktool, which is a WCAG color-contrast checker, but the shape is identical for any calculator-shaped tool.
What you build
A single-page interactive tool. Inputs, outputs, no server calls, no bridge calls (or one or two if the calculation needs the visitor’s name or a list of products from the site). The calculator runs entirely in the browser inside a sandboxed iframe. The bundle is one HTML file plus optionally a stylesheet and a small script tag at the bottom.
The manifest
{
"manifest_version": 1,
"id": "quicktool",
"name": "Color contrast checker",
"description": "A drop-in single-HTML tool that checks WCAG color contrast.",
"version": "0.1.0",
"entry": "index.html",
"isolation": "iframe",
"display": { "modes": ["page"], "default": "page" },
"permissions": { "read": [], "write": [] },
"runtime": { "sandbox": "strict", "external_origins": [] }
}
Notice what’s not there.
permissions.read is empty. external_origins is empty. The app asks for nothing. The install dialog the client sees is one line:
This app does not request any permissions.
That is exactly the right shape for a freelancer deliverable. The client gets a calculator. The calculator asks for nothing. There is nothing to be nervous about. The dialog is the most reassuring possible install dialog and it’s accurate. Compare against the typical “install this Gutenberg-block plugin” prompt, which lists about fifteen capabilities your client will not understand and approve anyway out of resignation. Zero permissions is a compelling product feature, even if the only person who notices is the client clicking Approve.
The HTML
It’s a single file. Markup, styles, and a small <script> block at the bottom. The example bundle is a single fully-self-contained file (the contrast checker has a deliberately retro VT323-styled UI, but it’s all inline). You can read the whole thing in three minutes. Copy it, change the math, change the labels, change the styles, and you’ve shipped a different calculator.
The handful of patterns that show up across most calculators:
<form id="form">
<label>Project type
<select name="type">
<option value="landing">Landing page</option>
<option value="ecom">E-commerce site</option>
<option value="custom">Custom build</option>
</select>
</label>
<label>Pages <input type="number" name="pages" min="1" value="5"></label>
<label>Timeline (weeks) <input type="number" name="weeks" min="1" value="4"></label>
<button type="submit">Get estimate</button>
</form>
<output id="result"></output>
<script>
const PRICES = { landing: 800, ecom: 1500, custom: 2500 };
const RUSH_MULTIPLIER = 1.4;
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
const base = PRICES[data.type] * Number(data.pages);
const total = Number(data.weeks) < 3 ? base * RUSH_MULTIPLIER : base;
document.getElementById('result').textContent =
'Estimated:
That’s the entire calculator. Pure HTML, no build step, no framework. If you want to use the bridge to pull in the site’s product list or the current visitor’s email, that’s a one-line addition. For most quote calculators, you don’t need any of that.
Why this is different from a Gutenberg block
A Gutenberg block is the obvious place a WordPress engineer’s instinct goes. A block is the wrong shape for this work. A block locks you into:
- A build pipeline (
@wordpress/scripts, JSX, sometimes Sass).
- Server-side render reconciliation if the block needs interactivity, because the post is rendered to a string in PHP and then hydrated in JS, and any mismatch is a console warning the client doesn’t understand.
- A way to express the block’s options that the editor’s sidebar can render. (Inspector controls,
useBlockProps, controlled input handlers. None of it is hard, all of it is busywork.)
- Backwards-compatibility for older saved posts when you change the block. Block deprecations are a real maintenance load, especially across plugin updates.
A DSGo App skips all of that because the unit of distribution is the bundle, not a block-registered React component. You ship HTML. The client uploads HTML. Updates ship as a new HTML bundle. The block-editor’s deprecation system never has to know.
If the calculator needs to live inside a post (between paragraphs of marketing copy), the block embed surface is the right answer (covered in post 25, gated on shipping). If the calculator is its own page, an /apps/{slug} URL is shorter and simpler.
The freelancer billing model
This is the part the calculator example is really about. As a freelancer, you charge for the calculator, not the WordPress integration. The client pays a flat fee, you write the HTML, you hand them a .zip, they upload it once, and you both move on. Maintenance is “if you want changes, that’s a new engagement.” The DSGo runtime is doing the integration work for free, and you keep the margin.
If the client’s site doesn’t have DSGo Apps installed, the install is a 30-second prerequisite. The plugin itself is free. You’re not asking them to pay for anything but the calculator you wrote.
The pricing-strategy thing this unlocks: you can quote calculators at $200-500 each instead of bundling them into a $5000 site engagement, because the deliverable surface is small enough to defend a small invoice. This is a structural problem in the freelance market: the calculator should cost $300, but the integration overhead pushes it to $1500, and clients balk at $1500 for “a thing that does some math,” so the engagement either dies or expands into a “redesign the whole sales page” project that nobody wanted. Removing the integration overhead means the calculator can be priced at what it’s actually worth, and clients say yes.
Smaller, more frequent invoices for narrowly-scoped work tend to retain margin better than big engagements that absorb scope creep. They also build a relationship: the client comes back next quarter with another small ask, and you build a stable line of micro-engagements instead of one quarterly battle.
Deploy as the client
Two paths. The freelancer’s preference depends on whether the client is a “give me a .zip” client or a “do it for me” client.
Hand them a zip.
cd quote-calculator
zip -r quote-calculator.zip .
Email them the zip. They go to their wp-admin, Apps → Add new → Upload, and the calculator is live at /apps/quote-calculator/ in 90 seconds. This is the “send a file, write an invoice” workflow. It scales because the client doesn’t need you to do anything they couldn’t do.
Deploy it for them.
npx designsetgo apps login --site https://theirsite.com
npx designsetgo apps deploy --build
You need an application password from their site. Most clients are happy to generate one and DM it to you; it’s revocable from Users → Profile → Application Passwords so they can pull access whenever the engagement ends. This is the “I’ll handle it” workflow. It scales differently: you’re trading client autonomy for fewer support emails (“how do I unzip a file”) and a tighter feedback loop on iterations.
I’d default to “hand them a zip” for a one-shot deliverable and “deploy for them” for a relationship where you expect to ship updates. Both work; pick based on the engagement, not the technical convenience.
What’s deliberately out of scope
A short list:
- A back-end. The calculator runs in the browser. If you need to capture the visitor’s quote in a database, that’s a different app, or you wire it into an email send via
dsgo.email.send. Most quote tools don’t need the captured submission; the visitor’s email arrives because they were going to fill out your contact form anyway.
- Multi-step wizards with persistence between sessions. Possible (
dsgo.storage.user.*), but if the calculator gets that complex, you’re billing a different engagement.
- Anything that needs a server-side cron. Out of v1 scope. Calculators are stateless.
Where to go next
- The bridge cookbook for the patterns you’d reach for if the calculator needs a touch of WordPress data.
- The pricing-page tutorial for an inline-mode example, which is the right call for calculators that are a real public page rather than a tool.
- The next post: building a recipe ranker that uses the AI bridge.
Nealey
+ total.toLocaleString();
});
</script>
That’s the entire calculator. Pure HTML, no build step, no framework. If you want to use the bridge to pull in the site’s product list or the current visitor’s email, that’s a one-line addition. For most quote calculators, you don’t need any of that.
Why this is different from a Gutenberg block
A Gutenberg block is the obvious place a WordPress engineer’s instinct goes. A block is the wrong shape for this work. A block locks you into:
- A build pipeline (
@wordpress/scripts, JSX, sometimes Sass). - Server-side render reconciliation if the block needs interactivity, because the post is rendered to a string in PHP and then hydrated in JS, and any mismatch is a console warning the client doesn’t understand.
- A way to express the block’s options that the editor’s sidebar can render. (Inspector controls,
useBlockProps, controlled input handlers. None of it is hard, all of it is busywork.) - Backwards-compatibility for older saved posts when you change the block. Block deprecations are a real maintenance load, especially across plugin updates.
A DSGo App skips all of that because the unit of distribution is the bundle, not a block-registered React component. You ship HTML. The client uploads HTML. Updates ship as a new HTML bundle. The block-editor’s deprecation system never has to know.
If the calculator needs to live inside a post (between paragraphs of marketing copy), the block embed surface is the right answer (covered in post 25, gated on shipping). If the calculator is its own page, an /apps/{slug} URL is shorter and simpler.
The freelancer billing model
This is the part the calculator example is really about. As a freelancer, you charge for the calculator, not the WordPress integration. The client pays a flat fee, you write the HTML, you hand them a .zip, they upload it once, and you both move on. Maintenance is “if you want changes, that’s a new engagement.” The DSGo runtime is doing the integration work for free, and you keep the margin.
If the client’s site doesn’t have DSGo Apps installed, the install is a 30-second prerequisite. The plugin itself is free. You’re not asking them to pay for anything but the calculator you wrote.
The pricing-strategy thing this unlocks: you can quote calculators at $200-500 each instead of bundling them into a $5000 site engagement, because the deliverable surface is small enough to defend a small invoice. This is a structural problem in the freelance market: the calculator should cost $300, but the integration overhead pushes it to $1500, and clients balk at $1500 for “a thing that does some math,” so the engagement either dies or expands into a “redesign the whole sales page” project that nobody wanted. Removing the integration overhead means the calculator can be priced at what it’s actually worth, and clients say yes.
Smaller, more frequent invoices for narrowly-scoped work tend to retain margin better than big engagements that absorb scope creep. They also build a relationship: the client comes back next quarter with another small ask, and you build a stable line of micro-engagements instead of one quarterly battle.
Deploy as the client
Two paths. The freelancer’s preference depends on whether the client is a “give me a .zip” client or a “do it for me” client.
Hand them a zip.
Email them the zip. They go to their wp-admin, Apps → Add new → Upload, and the calculator is live at /apps/quote-calculator/ in 90 seconds. This is the “send a file, write an invoice” workflow. It scales because the client doesn’t need you to do anything they couldn’t do.
Deploy it for them.
You need an application password from their site. Most clients are happy to generate one and DM it to you; it’s revocable from Users → Profile → Application Passwords so they can pull access whenever the engagement ends. This is the “I’ll handle it” workflow. It scales differently: you’re trading client autonomy for fewer support emails (“how do I unzip a file”) and a tighter feedback loop on iterations.
I’d default to “hand them a zip” for a one-shot deliverable and “deploy for them” for a relationship where you expect to ship updates. Both work; pick based on the engagement, not the technical convenience.
What’s deliberately out of scope
A short list:
- A back-end. The calculator runs in the browser. If you need to capture the visitor’s quote in a database, that’s a different app, or you wire it into an email send via
dsgo.email.send. Most quote tools don’t need the captured submission; the visitor’s email arrives because they were going to fill out your contact form anyway. - Multi-step wizards with persistence between sessions. Possible (
dsgo.storage.user.*), but if the calculator gets that complex, you’re billing a different engagement. - Anything that needs a server-side cron. Out of v1 scope. Calculators are stateless.
Where to go next
- The bridge cookbook for the patterns you’d reach for if the calculator needs a touch of WordPress data.
- The pricing-page tutorial for an inline-mode example, which is the right call for calculators that are a real public page rather than a tool.
- The next post: building a recipe ranker that uses the AI bridge.
Nealey