Category: Articles

  • AEO for Form & Quote Pages: How to Get Your Prices Into AI Answers

    AEO for Form & Quote Pages: How to Get Your Prices Into AI Answers

    More and more buyers never reach your pricing page the old way. They ask ChatGPT, Perplexity, Google’s AI Overviews, Claude, or Gemini a question — “how much does a custom roller banner cost?”, “who does made-to-measure shutters and what’s the price range?” — and act on the answer they’re given. If your price lives only inside an interactive calculator or behind a “request a quote” form, none of those engines can see it. You’ve optimised for humans who arrive at the page, and made yourself invisible to the layer that increasingly decides whether they arrive at all.

    This is what Answer Engine Optimization (AEO) fixes. And pages built around a form, calculator, or quote request are exactly the pages that get it wrong most often.

    SEO, GEO, AEO — the one-line version

    • SEO gets you ranked in a list of blue links.
    • GEO (Generative Engine Optimization) is about being used as a source by generative search.
    • AEO is the practical core of both: structuring your content so an engine can extract a clean, correct, self-contained answer and attribute it to you.

    For a pricing or quote page, the “answer” the engine wants is simple: what does this cost, and how do I get it? Your job is to make that answer readable by a machine that will never click a dropdown.

    The problem: your calculator is a black box to AI

    A live price calculator is a genuinely great experience for a human. They pick a size, choose a material, set a quantity, and watch the total update. But think about what an answer engine’s crawler actually receives when it reads that page:

    • It cannot select “2m × 1m”, so it never triggers the price.
    • It often cannot run the JavaScript that computes the number at all.
    • It sees a form, some empty fields, and — if you’re not careful — no price anywhere in the text.

    So when someone asks an AI “how much is a 2-metre banner from [you]?”, the engine has nothing to quote. It either skips you or, worse, guesses. The interactive tool that converts so well for on-page visitors is a dead end for the audience deciding which businesses to even mention.

    The fix is not to remove the calculator. It’s to stop making the calculator the only place the answer exists.

    The core principle: one page, two audiences

    The interactive form is for the human. A crawlable, plain-language version of the same answer is for the engine. A well-optimised page serves both, on the same URL.

    Everything below is a way to add the machine-readable answer around your form without hurting the human experience.

    Tactic 1 — Put representative prices in actual text

    Even if the exact price is configurable, you almost always have a defensible, crawlable version of it:

    • “Starting from” pricing“Roller banners start at £45 for the 850mm × 2000mm size.”
    • Tiers or ranges“Most orders fall between £45 and £180 depending on size, finish, and quantity.”
    • Worked examples“A popular configuration — 1m × 2m, premium film, qty 1 — comes to about £89.”

    Put this as real body text near the form, in a heading-led section like “How much does it cost?”. This single change is the highest-leverage thing you can do: it gives the engine a number to attribute to you, and it reassures the human before they start configuring.

    Because a CraftForms form is built from native Gutenberg blocks, you add this pricing summary the same way you’d add any paragraph or heading — right above or beside the form, in the same editor. There’s no separate “form app” boxing the content out of the page.

    Tactic 2 — Add a real FAQ next to the form

    Answer engines love self-contained question/answer pairs, because that’s the shape of the thing they’re trying to return. A short FAQ under your quote form does double duty — it deflects the same questions your sales inbox gets, and it feeds the engine clean Q&A:

    • “How is the price calculated?”
    • “Do you offer bulk / trade pricing?”
    • “What’s the turnaround time?”
    • “Can I get a fixed quote before ordering?”

    Write the answer in the first sentence, plainly, then elaborate. Don’t bury the answer in a paragraph of preamble — the engine (and the reader) wants it up front.

    To make that FAQ machine-readable as an answer, pair it with FAQPage structured data. CraftForms doesn’t emit schema itself — you generate it with your SEO plugin (Yoast, Rank Math, SEOPress all have an FAQ block/feature) or the core FAQ pattern. The point is that it sits on the same page as the form, and because the form is native content, nothing about the form stops the schema from applying to the page.

    Tactic 3 — Describe the offer with Product / Offer / HowTo schema

    Beyond FAQ, two more schema types help a pricing/quote page get quoted:

    • Product + Offer — for a configurable product, mark up the base offer (lowPrice / priceRange, currency, availability). This gives generative engines a structured price to cite.
    • HowTo — if your page walks the buyer through configuring and ordering, a HowTo outline mirrors the steps an engine likes to summarise.

    Again: this markup comes from your SEO/schema tooling, not from CraftForms. CraftForms’ contribution is that the page is normal WordPress content — so any schema block or SEO plugin treats it like any other post, with none of the “the form is a separate embedded app” problems that break markup on other builders.

    Tactic 4 — Keep the page fast and server-rendered

    Answer engines favour pages that are quick to fetch and whose meaning is present in the delivered HTML, not assembled later by heavy scripts. This is where the architecture of your form matters:

    • Native Gutenberg, not a bolted-on widget. CraftForms forms are built from core block infrastructure that’s already on the page, so a form/quote page doesn’t drag in a multi-hundred-kilobyte third-party configurator bundle. Lighter page, better Core Web Vitals, faster crawl.
    • Don’t gate the answer behind JS-only interaction. The live total is computed in the browser for responsiveness (and re-validated server-side at submit, so the charged price is always correct) — but the representative pricing, the FAQ, and the description from the tactics above are plain server-rendered content that any crawler gets on first load.
    • The Info block for on-page context. Use it to show the live figure to humans; use body text (Tactic 1) to state the crawlable figure for engines. Same page, both covered.

    The recurring complaint about configurators — “the one I tried completely killed my page speed” — is an AEO problem too: a slow, script-heavy page is both a worse human experience and a worse candidate for AI answers.

    Tactic 5 — Structure the page so it’s easy to extract

    • One clear <h1> stating what the page is (“Custom Roller Banner Pricing & Instant Quote”).
    • Descriptive sub-headings that match real questions (“How much does it cost?”, “Get an instant quote”, “Bulk & trade pricing”).
    • Short paragraphs, one idea each. Lists for tiers and options.
    • The plain-language answer above the interactive tool, not below it.

    An engine skims structure first. If your headings are the questions people ask, you’ve done half its work for it.

    Putting it together in CraftForms

    A worked flow for a “custom product quote” page, entirely in the block editor:

    1. Heading + intro<h1> and a two-line summary of what you make and roughly what it costs.
    2. “How much does it cost?” section — representative “from / range” prices in text (Tactic 1).
    3. The CraftForms quote form — options, quantity, and an Info block showing the live total as the visitor configures. Server-side price validation keeps the real charge honest.
    4. A short FAQ — 4–6 real questions, answer-first (Tactic 2).
    5. Schema — add FAQPage (and Product/Offer if relevant) via your SEO plugin.
    6. Ship it fast — because it’s all native blocks, the page stays light and fully crawlable.

    If you run a “Request a Quote” style page (no price shown, enquiry only), Tactic 1 matters even more: give the engine a range even when the exact figure is quote-only, or you’re invisible for every “how much does X cost” question in your category.

    How to tell if it’s working

    • Ask the engines directly. Query ChatGPT, Perplexity, Google AI Overviews, and Gemini with your real buyer questions and see whether you’re mentioned — and whether the price they quote is yours and correct.
    • Watch referral traffic from AI sources in analytics (they show up as their own referrers and are growing).
    • Re-test after each change; AEO is iterative, and engines re-crawl on their own schedule.

    The honest caveats

    • AEO doesn’t replace SEO — it’s the layer on top. You still need the page to be indexable and useful.
    • Schema is a signal, not a guarantee. Marking up a price doesn’t force an engine to quote it, and engines can still paraphrase or get it wrong. Keep the plain-text answer accurate as your first line of defence.
    • CraftForms is the form + speed + crawlable-content half of this. The structured-data half is your SEO plugin’s job — this article is deliberately clear about which is which.
    • Don’t fake precision. If pricing genuinely varies, publish an honest range rather than a fake exact number an engine might quote back at a customer.

  • How CraftForms Stops WordPress Form Spam — Without a CAPTCHA

    How CraftForms Stops WordPress Form Spam — Without a CAPTCHA


    TL;DR

    • CraftForms never shows a CAPTCHA. Instead, every form quietly watches how it was filled in: mouse movement, focus events, keystrokes vs. paste, and how naturally the timing plays out.
    • A submission that doesn’t look human — no mouse movement, no focus, no typing, filled out far faster than a person physically could — gets blocked right there in the browser, with a plain “Spam submission detected” message. No redirect, no lecture, no “prove you’re not a robot.”
    • A real visitor who interacts with the form at all — clicks a field, types a character, even just engages with the submit button — never comes close to being flagged. The check is invisible to them by design.
    • I run this on several of my own WordPress sites, including my highest-traffic one, and haven’t had a single spam submission come through since turning it on. That’s not a lab result — it’s just what’s happened in production.
    • Being honest about the limits: this is a client-side heuristic, not a server-side firewall. It’s a very effective first line of defense against the form-spam bots that actually exist in the wild — but it isn’t the same claim as “unbeatable,” and this post says so plainly.

    If you’ve ever added a CAPTCHA to a WordPress form, you already know the trade-off nobody advertises: it stops some bots, and it also makes some real visitors leave. Google’s own reCAPTCHA v2 checkbox occasionally escalates into a grid of blurry street signs. Mobile users hate it. Screen-reader users have a genuinely bad time with it. And you’re trusting a third-party script with performance and privacy implications, just to protect a contact form.

    CraftForms takes a different bet: don’t ask the visitor anything. Watch how the form actually gets filled in, and let the behavior answer the question instead.

    Cf captchaless

    How spam bots actually fill out a form

    Almost every automated form-spam tool — from a crude script to a headless-browser bot — shares a set of tells, because they’re optimizing for volume, not for looking human:

    • They don’t move the mouse. There’s no pointer to move; the values are set directly.
    • They don’t focus fields in sequence. A human tabs or clicks through a form field by field; a bot just writes into the DOM or POSTs a payload.
    • They don’t type. Values appear instantly — either injected all at once, or dumped in via a single paste-like operation.
    • They’re fast. A form that takes a human 20–60 seconds to read and fill gets “submitted” in under a second by a script, sometimes before the page has even finished rendering.

    None of that requires a CAPTCHA to detect. It just requires paying attention.

    What CraftForms actually watches

    Every CraftForms form tracks a handful of signals for the life of that page load — all client-side, all invisible, nothing rendered on screen:

    • Mouse movement — did the pointer move at all?
    • Focus events — did any field actually receive focus?
    • Keystrokes vs. paste — did the visitor type, paste, or neither?
    • Timing — how long between the form appearing and the submit firing, and did submit happen before the form had even finished rendering?
    • Input-change pattern — a lot of fields changing with very few keystrokes typed looks like autofill or a scripted fill, not a person.

    CraftForms also accounts for legitimate edge cases a naive detector would punish — like a form with sensible defaults already filled in, where a real visitor just glances at it and clicks submit without typing anything. Genuine physical interaction with the form is always read as a human signal, however it shows up.

    A typical WordPress form with a reCAPTCHA checkbox widget sitting under the submit button, next to a CraftForms form with nothing below the fields but the submit button itself
    Cf captchaless

    The score, and what crosses the line

    Each of those signals feeds into a single behavioral score for the submission, weighted by how strongly it points to “not human.” No single signal is treated as proof on its own — it’s the combination that matters, which is exactly why a person absent-mindedly filling out a form never gets close to a false positive, while a script that never touches the page like a human does trips several signals at once, immediately.

    Cross the threshold, and the submission gets blocked right there in the browser, before it’s ever sent to the server. The visitor (or, more accurately, the bot) sees a plain “Spam submission detected.” message. Nothing about the score, the breakdown, or the mechanism is exposed anywhere in the page — the internals aren’t something we publish, for the same reason a lock manufacturer doesn’t publish pin depths.

    A real person filling out a real form essentially never triggers this. They move the mouse. They click into a field. They type something, even one character. Any one of those alone rules out an entire category of “this looks like a bot.”

    Real-world results: I run this on my own sites

    I don’t just build this — I use it. I manage several WordPress sites that run CraftForms, including my main site, which gets a meaningful amount of traffic. Every one of them uses this exact behavioral check, with no CAPTCHA anywhere in the form. Since turning it on, none of them has received a spam submission. Not “spam is down” — zero.

    I’d rather be precise than impressive here: this isn’t a controlled study, and I’m not claiming it stops literally every bot that could ever exist. It’s a report from actually running the thing, on real forms, exposed to real internet traffic, for a while now.

    Being honest about what this is — and isn’t

    This is a genuinely useful post specifically because it doesn’t oversell the mechanism, so here’s the honest version:

    • It’s a behavioral check, evaluated as the form is filled out. A basic spam bot that fills a visible <form> — which describes the overwhelming majority of what actually hits WordPress sites — never gets past it, because it never produces human-shaped behavior in the first place.
    • It’s not a substitute for edge-level protection. It’s not a WAF, and it doesn’t claim to be. If you’re running a very high-traffic, public-facing form that you expect to become a specific target — not just incidental spam traffic — pair this with protection at the edge (Cloudflare Turnstile, a security plugin’s rate limiting, or your host’s WAF). CraftForms’ behavioral check is the friction-free first layer, not the last line of defense for a form under active, targeted attack.

    For the form volume most WordPress sites actually deal with — generic spam bots crawling for any submittable form — this is the whole story, and it’s the reason you can skip the CAPTCHA entirely.

    Try it

    There’s nothing to configure. Every CraftForms form ships with this on by default — no toggle to find, no threshold to tune, no third-party script to add. Install CraftForms, build the form, publish it — the spam filtering is already running before you’ve thought about it once.


    Summary

    1. CraftForms scores form submissions using behavioral signals — mouse movement, focus, keystrokes vs. paste, and timing — entirely in the browser and entirely invisible to the visitor.
    2. Cross the threshold and the submission is blocked before it’s ever sent, with a plain message; nothing about the scoring mechanism is ever shown.
    3. Legitimate edge cases — like a form with sensible defaults where someone just clicks submit — are accounted for, so real visitors are never unfairly penalized.
    4. In real production use across several of my own sites — including my highest-traffic one — this alone has kept spam at zero.
    5. It’s a behavioral first layer, not a server-side firewall: excellent against the generic bots that make up most form spam, but pair it with edge-level protection on a form under deliberate, targeted attack.
  • Headless WordPress + a Static Frontend, Explained (and Where Forms & Prices Fit)

    Headless WordPress + a Static Frontend, Explained (and Where Forms & Prices Fit)

    If you’ve ever described your setup as “headless WordPress with a static frontend” and watched another developer’s eyes narrow, you know this conversation:

    “So you’re headless? Or is it just a frontend CMS? How does your static site actually get the data — is it prebuilt, or is it making API requests? I’m not sure what ‘the data comes from a CMS and I prebuild everything’ even means.”

    Those are good questions, and the confusion is common. The terms get used loosely, and “headless” hides a couple of very different data-flow choices underneath it. This post untangles them — and then shows where forms and live prices fit, which is the part most explainers skip.


    Coupled vs headless: two jobs, one tool or two

    A traditional WordPress site does two jobs at once:

    1. Stores and manages content — posts, pages, products, media, users.
    2. Renders the pages — a theme + PHP turn that content into HTML on every request.

    That’s the coupled model. It’s what people sometimes loosely call a “frontend CMS”: WordPress is both the database and the thing your visitors’ browsers talk to.

    Headless splits those two jobs apart. WordPress keeps job #1 — it’s your content and data store, reachable over its REST or GraphQL API — and something else entirely does job #2. A separate frontend (Astro, Hugo, Next.js, plain HTML) pulls the content and renders the pages. Visitors never touch WordPress directly; they load a fast static site, and WordPress sits quietly in the back as the source of truth.

    So “headless” just means: WordPress is the backend, a separate frontend is the face.


    How does the frontend get the data? Two models — usually a hybrid

    This is the part that trips people up, because “headless” doesn’t answer it by itself. There are two ways a decoupled frontend can get its data, and most real sites use both.

    1. Prebuilt at build time (static generation). Your generator (Astro, Hugo, Next in SSG mode) calls the WordPress API once, at build time, pulls all the content, and bakes it into finished HTML pages. Those pages are then served as flat files from a CDN — no WordPress, no database, no PHP in the request path. This is what “I’m prebuilding pages for everything” means. It’s blisteringly fast and dirt cheap to host. The trade-off: the pages are a snapshot. Content changes in WordPress don’t appear until you rebuild.

    You don’t need a separate framework to land in this same model, either — WordPress can do its own building. Builderius builds the site visually, inside Gutenberg, and exports it straight to static HTML/CSS/JS, so WordPress is the CMS and the generator at once (that’s the stack behind the Builderius + CraftForms walkthrough linked below). Simply Static comes at it from the other direction: point it at a WordPress site you already have, running whatever theme, and it crawls every page and writes out the flat-file equivalent. Both land in exactly the same place as Astro/Hugo/Next in SSG mode — content baked in at build/export time — just with WordPress doing the rendering itself instead of an external generator calling its API.

    2. Runtime API requests. The page loads, and then JavaScript in the browser fetches live data from an API and fills part of the page in. This is for the bits that must be current — the parts you can’t afford to have go stale between builds.

    For a real-world example of this model taken to its full extent — not a dynamic island on an otherwise static page, but an entire frontend built this way — see setting up WordPress as a scalable headless CMS for a knowledge-base project: one WordPress backend exposes custom REST endpoints (a /sitemap/ listing and a /get_item/ content lookup) that five separately branded React frontends call live, rather than baking content into each at build time. It’s a good illustration of why you’d reach for pure runtime fetching instead of the hybrid below: one CMS, several frontends, content that has to stay current across all of them without rebuilding five sites every time something changes.

    Almost every good headless site is a hybrid: the bulk of the page is prebuilt and static (fast, cheap, cached at the edge), with a few dynamic islands that fetch live data at runtime. You don’t choose one model for the whole site — you choose per piece of content, based on how fresh it needs to be.

    That reframing is the whole game: what actually has to be live, and what can be frozen? For most sites, the answer is “95% can be frozen.” The interesting question is what to do with the other 5%.


    Where forms and prices fit

    A form — especially a product configurator with a live price — is the textbook dynamic island. It’s the exact 5% that can’t be baked into a static file:

    • A contact or booking form needs to submit somewhere and store the result. A flat HTML page has no server to receive it.
    • A product price that depends on the options a customer picks has to be computed as they pick, and it has to reflect current pricing — not whatever the price was the last time you rebuilt.
    • A checkout or payment step needs a server that can be trusted with the final number — the browser can display a total, but it can’t be the thing that charges a card or confirms a booking slot is still free.

    “Form” ends up covering a wide range here — a two-field contact form and a multi-step product configurator with live pricing and payment are the same shape of dynamic island, just with more happening inside it.

    This is precisely what CraftForms is built for in a headless setup. The shape looks like this:

    • The page is prebuilt and static. Title, images, copy, specs — all frozen, all served from the edge, instant.
    • The form is a runtime island. You drop a tiny placeholder into the static page. On load, a small script (embed.js) fetches the live form from your WordPress backend (GET /craftforms/v1/embed/{key}) and boots it in place. Because it’s fetched fresh every visit, editing fields or prices in WordPress needs no rebuild.
    • The price computes in the browser. As the customer changes options, the price recalculates instantly — locally, with no network call.
    • The backend is touched only on submit. When they finally submit, the request goes to WordPress, which re-verifies everything and stores it.

    Nothing about the form lives in the static build, so the frontend stays a pile of flat files and the form still behaves like a real, live application.


    Why it’s always fast

    Here’s the framing that makes this click — and it’s the reason developers keep arriving at exactly this setup once they see it work:

    There are no backend calls while the customer is using the form. They open a product page (already instant — it’s static). They toggle options, change quantities, add extras — and the price updates on every change with zero round-trips, because the math runs in their browser. The only time the backend is hit is the final submit.

    Compare that to a server-rendered configurator, where every option change is a request to the server, a database read, a recompute, and a response — a little spinner on every click, and a page that’s only as fast as your slowest query and the user’s latency. In the static + runtime-island shape, the slow part (the server) is out of the interaction loop entirely. It does almost nothing per pageview: it answers one embed request when the form loads, and one submission when the customer is done.

    Static page + client-side price + submit-only backend = a configurator that feels instant, on hosting that costs almost nothing.


    The dual price engine (how the price stays honest)

    “Compute the price in the browser” should set off an alarm: anything in the browser can be edited. If the price is only calculated client-side, a user can open devtools and submit whatever total they like.

    CraftForms runs the price formula on both sides:

    • Client-side, in JavaScript (a port of Symfony’s Expression Language), for the instant feedback that makes a static page feel live.
    • Server-side, in a PHP mirror of the same pipeline (class-formula-evaluator.php), which re-computes the total on submit from the form’s own formulas and current option prices. If the submitted price doesn’t match, the submission is rejected.

    So the browser side is for feel and the server side is for trust. The customer gets an instant price; you get a total you can actually charge, because the number that matters is recomputed where the user can’t touch it.


    An honest word on abuse

    Because the submission endpoint is public (that’s what lets a static site reach it), it’s worth being straight about the current protections:

    • Embed keys are bound to a domain — the backend checks the request Origin and refuses others.
    • Server-side re-verification (above) means price, stock, and booking capacity can’t be tampered with.
    • You can require a custom request header on the form as a lightweight shared-secret spam gate.
    • On the WordPress side, a one-click Headless backend mode toggle (CraftForms → Settings → Embed) closes everything but the CraftForms endpoints — public pages, the REST API, XML-RPC — so the install behaves like an API, not a website.

    (One small implementation note if you go build this: the embed key encodes your WordPress URL so embed.js can find your backend on its own, but the framework starters still pass the WP URL explicitly in config — so you’ll set it in one obvious place, not dig it out of the key.)


    Where to go next

    That’s the concept. If you want the stack-specific walkthroughs:

    The pattern is the same across all of them: keep the frontend static and fast, treat the form as a live island, compute prices in the browser for feel and on the server for trust, and touch the backend only when there’s actually something to submit.

    CraftForms embedding and external submissions are CraftForms PRO features.

  • Static Catalog, Live Prices: Stop Rebuilding 15,000 Pages Every Time a Price Changes

    Static Catalog, Live Prices: Stop Rebuilding 15,000 Pages Every Time a Price Changes

    You built a big product catalog as a static site — thousands of prebuilt pages, fast and cheap to serve. Then reality set in: prices change a few times a day. And every change means rebuilding the whole site. On a small catalog that’s an annoyance. At 15,000 pages it’s a build queue, a cache purge, and a nagging feeling you’ve picked the wrong architecture.

    You haven’t. You’ve just been treating one small dynamic thing — the price — as if it were static. The fix isn’t “go full server-side.” It’s to keep the pages static and make only the price dynamic.

    And “the price” is really shorthand for more than a number. On a real product page it’s the options a shopper picks, the live total those options add up to, maybe a custom text field or an uploaded file, and — if they’re ready to buy — the checkout step itself. A product configurator is the same dynamic island as a price display; it just does more once it’s booted. The pattern in this post scales from “show a live number” all the way to “run a full configurator with payment,” because it’s the same embed either way.

    New to the headless-plus-static-frontend idea in general? Using WordPress as a Form Backend for Static Sites and Web Apps covers the underlying pattern before this post zooms in on the pricing case.


    The insight

    A catalog page is 95% static: title, description, images, specs, related links. None of that changes when a price does. So don’t rebuild it. Let the page stay frozen and let the price and options be computed at view time, in the browser, from live data.

    CraftForms is built around exactly this split. Its product forms run a price formula on two sides: instantly in the browser as the shopper picks options, and again on the server when they submit. The browser side is what makes a static page feel live. The server side is what keeps it honest.


    What “make only the price dynamic” looks like

    Embed a CraftForms product form on each catalog page. The page ships static; the form is fetched at runtime and computes the price from current data as the shopper configures the product:

    <div data-craftforms-embed="YOUR_EMBED_KEY" data-resource-id="42"></div>
    <script src="https://your-backend.com/wp-content/plugins/craftforms/build/webcomponents/embed.js" defer></script>
    
    • The page is static — built once, served from the edge.
    • The form (and its pricing logic and current option prices) is fetched live from your WordPress backend via embed.js.
    • The price updates in the browser as options change — no round-trip per keystroke.
    • The backend is only touched on submit, when it re-computes the total and rejects any mismatch.

    Nothing here is specific to a plain price number — the same embed can carry a file upload for custom artwork, a Stripe payment step, or an “Add to Cart” button that lands the configured item in a WooCommerce cart. You’re not bolting a separate checkout widget onto the page; the form is the configurator, the price is one of its outputs, and checkout is another.

    Change a price in WordPress and the next visitor sees it. No rebuild. No 15,000-page deploy.


    Where prices actually live

    You don’t want prices hard-coded into 15,000 templates — that’s what caused the rebuild problem. Keep them in your backend:

    • A catalog item per product (data-resource-id links a form to one), so option prices are looked up live.
    • A price formula on the form for anything computed (area × rate, tiered quantities, add-ons, surcharges).

    Now a price change is a data edit, not a code deploy. The static pages never need to know.


    “Won’t the price flash in after load?”

    Only briefly, and only for the form region — the page itself (images, copy, specs) is already there instantly. If you want a number visible before the form boots, render a build-time price as a placeholder in the static HTML and let the live form replace it on load. Shoppers see a price immediately; the authoritative, current price arrives a moment later and is the one that gets submitted.


    Filtering and search at scale

    Live pricing solves the rebuild problem; it doesn’t solve search. If pure static filtering is getting clunky at your catalog size, that’s a separate concern — offload search/filtering to a client-side index or a hosted search service, and keep using static pages + live pricing for the product detail experience. The two decisions are independent; you don’t have to go fullstack to fix either.


    It’s a configurator, not a price tag

    Once the pricing logic lives in a real form engine instead of a hard-coded number, the same setup covers a lot more than “show the total”:

    • Complex pricing — formulas, tiered quantities, and lookup tables instead of a single price field, for products where the total isn’t just base × quantity. (Example for WooCommerce: Complex WooCommerce Pricing Without Hundreds of Variations; the same formula/table engine runs behind a static catalog.)
    • Stock per option combination — a dynamic SKU built from the shopper’s selections, checked against a stock table before the order goes through, so a static page never oversells a specific size/material/colour combo. (Possibly related article: Track WooCommerce Stock by Option — No Variations is the deep dive.)
    • File uploads for custom artwork or specs, validated server-side.
    • Payment — Stripe (hosted checkout or embedded card element).
    • Booking capacity — the same live-then-verified pattern applies to an appointment slot or a date range, not just a price.

    None of that changes the architecture in this post. It’s the same static page, the same embed, the same submit-only backend — the form itself is just doing more.


    Why not just go server-rendered?

    You could. But you’d give up the thing that made the catalog cheap and fast in the first place. The static-plus-live-price shape keeps the edge performance and the trivial hosting, and confines the “dynamic” surface to a single fetch and a formula. Your backend does almost nothing per pageview — it answers one embed request and, occasionally, a submission.

    One honest note: the submission endpoint is public, so rely on the domain-bound embed key (the backend checks request Origin) plus a required request header as a spam gate (CraftForms doesn’t ship a CAPTCHA yet), and the built-in server-side price re-verification so a shopper can’t submit a total they edited in devtools. Locking the rest of the WordPress install down for this is a one-click Headless backend mode toggle in CraftForms → Settings → Embed — see the locked-down backend guide for what it covers and the extra server-level steps.


    The takeaway

    Rebuilding a whole catalog to change a number is a smell, not a requirement. Keep the pages static. Make the price — or the whole configurator, with options, stock, and checkout — live. Let the browser compute it for feel and the server verify it for trust. Your build stays fast, your catalog stays current, and 15,000 pages stop caring what anything costs or whether it’s even in stock.

    It’s a shape developers tend to recognize the moment they see it work — static frontend, instant in-browser pricing, backend touched only on submit — because it removes the one thing that made big catalogs painful without giving up the speed and cheap hosting that made them appealing.

    Related reading: if your catalog lives on Astro, Add a Real Form Backend to Your Astro Site walks through the embed end to end; on Hugo, see Add Forms to a Hugo Site With a WordPress Backend.

    CraftForms embedding, external submissions, and catalog pricing are CraftForms PRO features.

  • When You Outgrow Hosted Form Endpoints (Formspree, Netlify Forms, Web3Forms, and Friends)

    When You Outgrow Hosted Form Endpoints (Formspree, Netlify Forms, Web3Forms, and Friends)

    If you build static sites, you’ve used one: a hosted form endpoint. You paste a form action, the service catches the submission and emails it to you, and you move on. Formspree, Netlify Forms, Basin, Getform, Web3Forms, staticforms.dev — they’re all variations on the same convenient idea, and for a single contact form they’re genuinely great.

    Then the site grows up. You add a second form, then a fifth. You need a field that only shows when another is checked. You need a file upload. You need a confirmation email that looks like your brand. You hit a monthly submission cap. You realize your submission data — sometimes including personal details — lives on a service you don’t control. Or the ask stops being “collect some fields” entirely: you need a product configurator with a live price, a booking form that checks availability, or a form that actually takes payment. A hosted endpoint was never built for any of that — it can catch a submission, not run a product. At some point “convenient” quietly became “limiting.”

    This post is about recognizing that moment, and what to do when you reach it.


    What hosted endpoints are great at

    Let’s be fair. For the right job they’re excellent:

    • Zero backend. Nothing to host, patch, or monitor.
    • Minutes to set up. Paste a URL, done.
    • Cheap or free at low volume.

    If you have one contact form and light traffic, stop reading and use one. Really.


    Where they start to hurt

    The limits are structural, not bugs — they come from being a generic catch-all:

    NeedTypical hosted endpointA CraftForms backend
    Many formsEach is a separate config / projectAny number, one install
    Submission volumeMonthly caps (e.g. free tiers around 50–250/mo)No caps — it’s your database
    Spam protectionBasic; some tiers have no CAPTCHA at allOrigin-bound + header gate (no CAPTCHA yet either — see below)
    Conditional logicNone — it’s a dumb receiverField-to-field conditions, AND/OR
    Pricing / calculationsNoneFormulas, tiered lookups, price matrices
    Product configuratorsNoneOptions, live price, file uploads, add-to-cart
    BookingsNoneAvailability, capacity, iCal sync
    File uploadsLimited or paid-tier onlyBuilt in, server-validated
    Branded emailsMinimal templatingVisual template builder
    PaymentsNot their jobStripe (hosted or embedded), PayPal
    Data ownershipStored on their infrastructureYour WordPress database

    None of these matter for a contact form. All of them matter for registrations, bookings, quotes, orders, and configurators — and that last group is the point: the same engine that replaces a hosted endpoint also replaces a separate “product configurator” plugin or a payment page, because it was never just a form tool.


    The alternative: a backend you own

    You don’t have to choose between “hosted endpoint” and “rewrite everything as a fullstack app.” There’s a middle path that keeps your static frontend exactly as it is: run one locked-down WordPress install as a private form backend, with CraftForms as the engine.

    • One backend, unlimited forms, all in one place.
    • No submission caps — it’s your database.
    • Conditional logic, calculations, file uploads, branded email, Stripe payments — built in.
    • Product configurators and bookings, not just forms — live pricing, stock per option, availability calendars, WooCommerce add-to-cart — the same engine, not a bolt-on plugin.
    • Your data on your server — nothing sits with a third party.
    • A near-zero attack surface — the WordPress install has one plugin, no public pages, no XML-RPC, and a hidden login. It isn’t a website; it’s an API.

    Your static site connects one of two ways: a direct fetch on submit, or CraftForms’ embed — a <div> + one script that pulls the live form from the backend and renders it in place. Both hit the same REST endpoint; everything else on WordPress is dark. It’s worth internalizing that “form” here covers a lot of ground: the exact same mechanism runs a two-field contact form, a multi-step booking with a deposit, and a product configurator with live pricing and a checkout.


    Isn’t running WordPress the thing we were trying to avoid?

    The objection is fair, so be precise about what’s involved. This is not a normal WordPress site you babysit. It has no theme, no public frontend, no page builder, no pile of plugins — the usual sources of WordPress pain and CVEs simply aren’t installed. What remains is a submission API. Locking it down to that is a single toggle — Headless backend mode in CraftForms → Settings → Embed blacks out the public front-end, closes the REST API to everything but the CraftForms endpoints, and disables XML-RPC, all in one click, reversible any time. For the pieces a plugin can’t do from inside WordPress — hiding the login page, server-level rules — the locked-down backend guide has the extra mu-plugin snippet and server config. Set it once.

    Craftforms headless mode setting

    And be honest about the trade-off in both directions: CraftForms doesn’t yet ship a built-in CAPTCHA or honeypot either. What it gives you today is origin-bound submissions (the form only accepts posts from your domain), server-side re-verification of anything that matters (prices, stock, capacity — a tampered value is rejected), and required request headers as a shared-secret spam gate. For most sites that’s a stronger position than an emailed endpoint with no logic at all; if you need heavy bot mitigation right now, weigh that.


    A rule of thumb

    • One contact form, low volume, no logic? Use a hosted endpoint. It’s the right tool.
    • Many forms, real logic, file uploads, pricing, payments, or data you must own? You’ve outgrown the endpoint. Move the backend in-house — a locked-down WordPress + CraftForms — and keep your static frontend exactly as fast as it is today.

    The static site was never the problem. The backend was. Pick one you don’t outgrow.

    See it built: Add a Real Form Backend to Your Astro Site and Add Forms to a Hugo Site With a WordPress Backend walk through this exact move for those two stacks. If what you actually need is a configurator rather than a contact form, Complex WooCommerce Pricing Without Hundreds of Variations and Track WooCommerce Stock by Option — No Variations show the same formula/stock engine at work.

    CraftForms embedding, external submissions, calculations, and payments are CraftForms PRO features.

  • Generate a Static Site from WordPress — With Forms That Actually Work

    Generate a Static Site from WordPress — With Forms That Actually Work

    Static sites are the easiest sites to love: they’re fast, they cost almost nothing to host, and there’s no server to patch at 2am. The catch is always the same. The moment you need a form — a registration, a booking, a product configurator with a live price — a static site has nowhere to send it.

    The usual answer is to bolt on a hosted form endpoint (Formspree, Netlify Forms, Web3Forms). That works until it doesn’t: submission caps, no real validation, no conditional logic, no pricing, and your submission data living on someone else’s infrastructure.

    There’s a cleaner option if you already work in WordPress. Build the site visually in Builderius, export it to static files, and let CraftForms — running on a locked-down WordPress backend — handle every form. You get a static frontend on Cloudflare or Netlify Pages and a real form/price engine you own. This article shows the exact wiring, including a one-line shortcode that survives the static export intact.

    New here? Two companion articles set the stage: Using WordPress as a Form Backend for Static Sites and Web Apps introduces the mechanism, and Use WordPress as a Locked-Down Form Backend for Static Sites covers securing the backend. This post is the “build it in Builderius” path.


    The stack

    Three pieces, each doing one job:

    • Builderius — the visual builder. Design pages in WordPress, then use its static-site generator to crawl the rendered pages and export clean HTML/CSS/JS with no WordPress dependency in production. Deploys straight to Netlify, Cloudflare Pages, GitHub Pages, or S3.
    • CraftForms — the form + price engine. Builds forms, validates input, runs conditional logic and price formulas (client-side for instant feedback, re-verified server-side on submit), stores submissions, sends notifications, takes payments.
    • A locked-down WordPress install — invisible to the public, serving only the CraftForms REST endpoints. As of CraftForms 1.6, a single toggle in CraftForms → Settings → Embed (“Headless backend mode”) does this natively — no mu-plugin required. See below for what it covers.

    The static site your visitors load has no WordPress in it. When someone opens a form, a tiny script fetches the current form from your backend and boots it in place. When they submit, it posts to your backend. Nothing else touches WordPress.


    The problem with freezing a form

    Here’s the subtle part most walkthroughs skip.

    When a static generator crawls a page that has a form rendered directly into it, it freezes a snapshot of that form. For a CraftForms form rendered inline, that snapshot bakes in two things that don’t survive well:

    1. A submit URL of the …/wp-json/craftforms/v1/submit/… form. On a locked-down backend, /wp-json/ is often blocked outright — so the frozen form posts to a dead URL.
    2. A one-time security nonce. Nonces expire in hours and are tied to a session. On a page frozen at build time and served for weeks, it’s stale on arrival.

    You can work around both by hand. But there’s a purpose-built path that avoids the problem entirely: don’t freeze the form — freeze a placeholder that fetches the live form at runtime.


    The shortcode

    CraftForms ships a shortcode built for exactly this:

    [craftforms_static_form key="YOUR_EMBED_KEY"]
    

    Instead of rendering the whole form, it outputs the CraftForms embed placeholder:

    <div data-craftforms-embed="YOUR_EMBED_KEY"></div>
    <script src="https://your-backend.com/wp-content/plugins/craftforms/build/webcomponents/embed.js" defer></script>
    

    That’s what gets frozen into your static export — two lines, no baked nonce, no dead submit URL. At runtime embed.js decodes your backend’s URL from the embed key, calls GET /craftforms/v1/embed/{key}, and replaces the placeholder with the live form and its current price logic. Because the form is fetched fresh every visit, you can change fields or prices in WordPress without rebuilding the static site.

    Link a form to a catalog item for catalog pricing by adding resource-id:

    [craftforms_static_form key="YOUR_EMBED_KEY" resource-id="42"]
    

    Step by step

    1. Enable external submissions on the form

    Open your form → Submission settings → turn on Allow External Submissions. This is what lets a request from your static domain (a different origin) be accepted at all.

    2. Create an embed key

    Go to CraftForms → Settings → Embed and create a key for that form and the domain your static site will be served from (e.g. camp.example.com). The key encodes your WordPress URL, so you never hard-code it on the static side. Copy it.

    3. Drop the shortcode into Builderius

    In the Builderius canvas, add a Shortcode module where you want the form and set it to:

    [craftforms_static_form key="aHR0cHM6Ly9mb3Jtcy5leGFtcGxlLmNvbQ.abc123"]
    

    Builderius renders the shortcode server-side, so the <div> + <script> land in the page’s HTML — which is exactly what its static-site generator captures.

    4. Generate and deploy the static site

    Run Builderius’ static-site generation on your published release, then deploy to Cloudflare Pages / Netlify / GitHub Pages. The generator freezes the placeholder verbatim and — because embed.js is served from your WordPress domain — bundles the script into the export automatically.

    5. Open the static page

    The form appears, live. Change an option and the price updates instantly (computed in the browser). Submit, and it posts to your locked-down backend — which independently re-computes the price and rejects any tampering before storing the submission or charging a card.

    Why this is fast: the page was already static and instant, and once the form boots, every option change recalculates the price in the browser with no backend call. The backend is touched exactly once — on submit. There’s no per-click round-trip, so a shopper can configure a product as fast as they can click, on hosting that costs almost nothing.


    Why this is safe

    External submission endpoints are public by nature, so CraftForms doesn’t trust the client:

    • Origin check — the embed key is bound to one domain; requests from anywhere else are refused.
    • Server-side price re-verification — the browser shows a live price for UX, but the server recomputes _price, stock, and booking capacity from the form’s own formulas and rejects mismatches. A user editing the price in devtools gets a 4xx, not a discount.
    • Locked-down surface — flip Headless backend mode on in CraftForms → Settings → Embed and the plugin restricts the REST API to its own namespace (anonymous requests to anything else get a 403), disables XML-RPC, and blacks out every public frontend request for logged-out visitors — leaving just the CraftForms embed and submit endpoints reachable. It’s PHP-layer, fully reversible, and ships with a CRAFTFORMS_HEADLESS_DISABLE constant for wp-config.php as a kill switch if you ever lock yourself out.

    That one toggle covers the protections that matter most day to day. It deliberately stops short of two things a plugin can’t safely do for you: hiding /wp-login.php behind a custom slug, and server-level rules (blocking xmlrpc.php at nginx/Apache, disabling PHP execution in /uploads). If you want those too, the locked-down guide still has the extra mu-plugin snippet and server config for them — otherwise the built-in toggle is now the fastest way to get most of the way there.

    One honest caveat: CraftForms does not yet ship a built-in CAPTCHA or honeypot. For public submission endpoints, add a required request header to the form (Submission settings → Required headers) as a lightweight shared-secret filter, and keep an eye on the roadmap for a first-class anti-abuse layer.


    Not using Builderius?

    The same embed placeholder works anywhere. CraftForms ships copy-paste starters for other static generators:

    • Astro — a <CraftForm embedKey="…" /> component. Find out more on how to add a form to Astro website.
    • Hugo — a {{< craftform key="…" >}} shortcode and a partial.

    They’re in examples/static-site/ in the plugin, and each emits the identical <div data-craftforms-embed> + embed.js snippet. One mechanism, every static toolchain.


    The payoff

    You keep everything static sites are good at — speed, cheap hosting, a tiny attack surface — and you stop pretending forms are someone else’s problem. One WordPress backend, locked down and invisible, powers the forms, pricing, bookings, and payments across every static site you build. And because the form is fetched at runtime, your prices are never one stale deploy behind.

    CraftForms embedding, external submissions, the shortcode, and Headless backend mode are CraftForms PRO features.

  • CraftForms 1.6 Is Here — Meet the Frames Editor, Headless Lockdown, and Bulletproof Bookings

    CraftForms 1.6 Is Here — Meet the Frames Editor, Headless Lockdown, and Bulletproof Bookings

    CraftForms 1.6 is out now, and this release is about giving customers a more tactile way to shop, and giving you a more solid platform underneath them. The headline is a brand-new interactive element — the Frames Editor — that lets shoppers preview a framed print in real time. Alongside it: a Locked WP mode for running WordPress as a pure headless form backend, a smoother path to selling a WooCommerce product from any page, and a fix that makes double bookings a thing of the past.

    Here’s everything in 1.6.


    Frames Editor — let customers preview the frame before they buy

    The star of 1.6 is the new Frames Editor interactive element. If you sell framed prints, canvases, photo gifts, or anything that goes on a wall, this is built for you.

    Add it to a form and shoppers get a live, realistic preview: they pick a frame style, a mat/mount color and width, and watch the preview update instantly around their photo or product image. There’s even a “view it in a room” scene so customers can see roughly how the finished piece will look on their own wall before they commit. The finished composite image is saved right along with the order, so you know exactly what to produce.

    It turns a flat product photo into something customers can actually configure and picture in their space — which tends to mean fewer support questions about “what will this look like” and more confident checkouts.

    Craftforms picture frame

    Locked WP mode — run WordPress as a headless form backend

    1.6 adds Locked WP mode, a new setting for anyone using CraftForms to power forms embedded on a separate site — a static site built with Astro, Hugo, or similar.

    Turn it on and your WordPress install locks down: public front-end pages, REST routes, and XML-RPC are closed off to anonymous visitors, while CraftForms’ own embed and submission endpoints stay open exactly as before. You still log in and manage everything as an admin — only the parts of WordPress you’re not using get shut off to the outside world. And if anything ever goes sideways, a single wp-config.php constant is a guaranteed way to switch it back off.

    It’s a small setting with a specific job: let WordPress be the engine for your forms without also being a second, unnecessary public website.


    Sell a product from anywhere — shortcode + WooCommerce, improved

    1.6 improves how a form connects to a WooCommerce product when embedded via shortcode. Connect a form to a product and add it with , and the full add-to-cart experience — price, options, and all — now displays cleanly wherever you drop that shortcode, not just on the product’s own page.

    Combined with better support for page builders’ product widgets, it’s now more reliable to feature a product-connected form on a landing page, a bundle page, or anywhere else you want to sell — without wrestling with where WooCommerce normally expects that markup to live.


    No more double bookings

    Booking the same slot twice used to be possible in one specific situation: two customers checking availability and confirming a booking at almost the same moment. Both could pass the availability check before either booking was actually recorded, and both would go through — leaving you overbooked.

    1.6 closes that gap by making every booking write atomic: each reservation now checks and reserves the slot in a single guarded database operation, so if two bookings race for the same slot, only one can win. Multi-night stays are protected the same way — if any night in the range can’t be secured, the whole booking rolls back rather than leaving you with a booking that’s only partially confirmed. The result: reliable capacity limits, even under real demand.


    Fixes and polish

    1.6 also ships two fixes worth calling out:

    • Add to cart no longer clears your cart. A timing issue meant that, in some cases, adding a CraftForms-connected product to the cart could wipe out whatever else the customer already had in it. That’s fixed — existing cart items now stick around.
    • Multi-file upload fields now receive every file. Fields that accept multiple files were only getting the last one through; all selected files now upload correctly.

    Get CraftForms 1.6 today

    CraftForms 1.6 is about letting customers preview what they’re buying, giving you a safer way to run headless forms, making product-connected forms easier to place anywhere, and making sure your bookings hold up under real traffic. Update now to get all of it.

    New to CraftForms? The free version gives you the full block-based form builder to start with today — the Frames Editor, Locked WP mode, and WooCommerce features arrive with Pro.

  • Best WooCommerce Product-Configurator Plugins (2026)

    Best WooCommerce Product-Configurator Plugins (2026)

    WooCommerce is superb at selling products with a small, fixed set of choices — a T-shirt in three sizes and two colours is six variations, and the built-in attribute-and-variation system handles it cleanly. The trouble starts the moment your product needs more: an engraving line, a photo upload, a made-to-measure size, a price that depends on a formula, or an option that should draw down real stock. Native WooCommerce has no answer for those, so you reach for a product-options or configurator plugin.

    There are a lot of them, and they are not interchangeable. Some are simple “add a text field to the product page” tools; others are full pricing engines; a few are form builders that add a WooCommerce bridge. This guide runs through the six that matter most in 2026 — what each supports and where each stops — so you can match the tool to the hard part of your product.

    What to look for in a configurator plugin

    The dimensions that actually separate these plugins. Each solution below is tagged against them, so you can scan for the ones you need:

    • Field types & swatches — colour/image swatches, file uploads, date pickers, not just text and dropdowns.
    • Conditional logic — show or hide options based on earlier answers, so customers never see impossible combinations.
    • Formula & lookup pricing — flat/percentage fees are table stakes; formula pricing (base × area × rate) and lookup tables (quantity tiers, size grids) are where the hard cases live.
    • Custom quantities & dimensions — can it price any value, not just the presets you typed in? This is the “hundreds of variations” trap in disguise.
    • Option-level inventory — does selecting an option draw down real stock, or is it just a display field?
    • Server-side price validation — is the final charge computed on the server, or can a customer tamper with the browser’s number?
    • CSV price editing — can you update a big price list in a spreadsheet instead of clicking through rows?
    • Scope — WooCommerce-only, or can the same tool build forms elsewhere on your site?

    1. CraftForms

    A native-Gutenberg form builder with a WooCommerce integration, so a configurator is built from the same blocks as any other form — you drop fields into the block editor, wire up their logic, and an Info block shows a running total that updates live as the customer chooses.

    There are ready-made starters (a quantity-tier sticker product and a formula-priced pipe etc) let you load a complete working setup and inspect every rule rather than building from scratch.

    Its standout is doing the two hard things at once. First, rule-based pricing: instead of a row per combination, you write a price formula (diameter_price × material_coef × length) and use a price matrix whose “Closest up” lookup prices custom quantities and dimensions correctly — order 137 and it lands on the right tier, which native variations physically can’t do. The pricing tables import and export as CSV, so onboarding an existing price sheet or bumping every price 5% is a spreadsheet paste, and the live figure is re-evaluated on the server at submit so the charged amount is never taken from the browser. Second, inventory by option: a Dynamic SKU built from the selections plus a stock table draws down real stock and blocks oversell without a single WooCommerce variation — and the same option values can drive product weight, dimensions, and shipping class.

    On top of that it ships interactive elements no rival here has: an Image Cropper (customers crop, flip, and filter an uploaded photo in-form, aspect-locked and saved with the order), a Layered Image live composite preview, and a product image viewer that swaps the main image as options change. And because it’s a form platform first, the same tool builds contact forms, bookings, and standalone Stripe payments with no WooCommerce at all. The catch: it bridges into WooCommerce rather than being a native extension. The WooCommerce integration and the above are Pro.

    Supports: swatches · conditional logic · formula & lookup pricing · custom quantities & dimensions · option-level inventory · server-side validation · CSV price editing · dedicated interactive elements (cropper, layered image) · works outside WooCommerce.

    Doesn’t: edit-from-cart · mix-&-match bundles · swatches from native attributes (limited)

    Price: free tier; Pro $59.99/yr or $149.99 lifetime, unlimited sites.


    2. WooCommerce Product Add-Ons

    The official add-on from the WooCommerce team attaches simple fields — text and textarea, checkboxes, dropdowns, a file upload, and a customer-entered “custom price” — with a flat or percentage fee per option. You can group add-ons and apply them per product or across a category, mark them required, and the choices show up cleanly in the cart, the order, and the confirmation email with no glue. Its whole appeal is trust and simplicity: it’s built by the WooCommerce team, tested against every release, set up in five minutes, and carries zero third-party risk — for a lot of stores that reliability is worth more than features.

    Those features are exactly what’s missing, though. Pricing is purely additive (a fee on top of the base), so there’s no formula engine, no lookup tables, and no way to multiply or replace the price; there’s no conditional logic to hide irrelevant options; and nothing tracks stock for the options you add. It tops out at “add gift wrap (+£3)”. For a coffee shop’s “extra shot (+50p)” or a “add a gift message” field it’s ideal; for made-to-measure or tiered pricing it isn’t the tool.

    Supports: basic fields · file upload · flat/percentage fees.

    Doesn’t: conditional logic · formula/lookup pricing · custom quantities · option inventory · works outside WooCommerce.

    Price: €71/yr, single site (no free version).


    3. WooCommerce Extra Product Options

    Formerly “TM Extra Product Options”. It offers more than twenty field types (text, selects, radio/checkbox with image and colour swatches, date/time, file upload) organised into sections and tabs with tooltips and per-field styling, a genuine conditional logic engine, and a math/formula feature that can add to, modify, or fully override the product price or weight — including quantity-based and character-count pricing. That breadth handles genuinely complex products, and the plugin is actively maintained.

    The settings UI is dense and has a real learning curve — there are a lot of options, and building a big configurator takes patience. Like the rest of the specialists, it has no inventory per option combination and does nothing outside WooCommerce.

    Supports: swatches · conditional logic · formula pricing · file upload.

    Doesn’t: option inventory · CSV price editing · works outside WooCommerce.

    Price: $49/yr for 1 site to $229/yr for 250 sites


    4. Advanced Product Fields for WooCommerce

    The best-reviewed focused option here (4.8/5, 50,000+ active installs on wp.org) and the one that most obviously prioritises quality over feature-count. Its admin is modelled on Advanced Custom Fields, so it feels immediately familiar to WordPress developers, and the plugin is built to be lightweight — a deliberate “zero-bloat” stance that keeps product pages fast. The free version is genuinely useful on its own (around ten field types, conditional logic, and flat-fee pricing), which makes it easy to trial before paying.

    Its sharpest trick is generating option swatches automatically from your WooCommerce attributes with zero setup — nobody else here does that as cleanly. Higher tiers add conditional logic, formula pricing, and lookup-table (conditional) pricing, file uploads with previews, and a date picker. Two extras genuinely set it apart: customers can edit their selections from the cart without starting over, and an add-on adds a mix-&-match bundle builder; it also ships real, tested WPML/Polylang and multi-currency support. What it deliberately doesn’t do is reach beyond the product page: it can’t generate a SKU from options or track option stock, and there are no standalone forms.

    Supports: attribute-native swatches · conditional logic · formula & lookup pricing · file upload · cart editing · bundles (add-on).

    Doesn’t: option inventory · dynamic SKU · works outside WooCommerce.

    Price: free tier; Pro from $69–$199/yr, single site; $399–$999/yr, for 20 sites.


    5. YITH WooCommerce Product Add-Ons & Extra Options

    YITH’s entry does the expected job well. You build “blocks” of options and choose where they appear — all products, specific products or categories, or particular user roles — using field types like radios, dropdowns, text boxes, checkboxes, colour/label swatches, file upload, and date, each able to carry a fixed, percentage, or quantity-based cost. There’s a capable free version, and the premium tier unlocks the swatches, per-role visibility, file uploads, and finer display control.

    Its real draw is consistency: if you already run YITH plugins (or their membership bundle), this one matches the admin conventions, update cadence, and support channel you already know, and it plays nicely with the rest of the YITH catalogue. What it isn’t is a pricing or inventory engine — the model is fee-per-option rather than true formulas or lookup tables, there’s no stock tracking per option combination, and it does nothing outside WooCommerce. It’s a solid, unsurprising middle-of-the-road choice.

    Supports: fields & sections · swatches · per-option fees · partial conditional logic.

    Doesn’t: formula/lookup pricing · option inventory · works outside WooCommerce.

    Price: premium €119/yr, single site.


    6. Form builders with a WooCommerce integration (Gravity Forms, WS Form, JetFormBuilder)

    These are form builders that added a WooCommerce bridge, so a form you’d use for lead capture can instead drive a product page — and you inherit a mature logic and calculation engine that’s often deeper than the dedicated options plugins’:

    • Gravity Forms is the developer’s default: robust conditional logic, calculation fields, and a huge add-on ecosystem. Its WooCommerce Gravity Forms Product Add-Ons extension attaches a form to a product and flows the fields into the cart and order.
    • WS Form is built around a strong calculation engine and a large field library with a visual “Styler,” and its WooCommerce add-on turns a form into a configurator with calculated pricing and add-to-cart.
    • JetFormBuilder (Crocoblock) is free at its core, Gutenberg-native, and a natural fit if you’re already in the Elementor/Crocoblock stack; WooCommerce actions let a submission add a configured product to the cart.

    The shared trade-offs are real: WooCommerce support is a paid add-on or higher tier stacked on top of the base plugin, so you’re buying (and maintaining) two products; none of them track inventory per option combination; and the product-page experience is less native than a purpose-built options plugin, with more wiring to get right. Best when you already own and love one of these builders and want to reuse it. This is also the family CraftForms belongs to — a form builder with a WooCommerce integration — which is what makes the head-to-head so direct.

    Supports: conditional logic · formula pricing · file upload · works outside WooCommerce (the builder itself).

    Doesn’t: option inventory · native product-page UX (WC support is a separate add-on).

    Price: base builder + a paid WooCommerce add-on/tier — e.g. Gravity Forms from $59/yr + the WooCommerce add-on ~$99/yr; WS Form PRO from ~$79/yr + WooCommerce add-on; JetFormBuilder free core, Pro from $49/yr per 1 site or $499 LTD.


    How to choose — by what you actually need

    Start from the hard part of your product, then read across to the tool:

    • Just a few flat or percentage surcharges (gift wrap, extra shot, engraving fee)? → WooCommerce Product Add-Ons.
    • Unique product presentation / dedicated image editor?CraftForms
    • Attribute-native swatches with zero setup? → Advanced Product Fields or Extra Product Options.
    • Lots of field types + conditional logic at a one-time price? → Extra Product Options.
    • Editing selections from the cart, or mix-&-match bundles? → Advanced Product Fields.
    • Reuse a form builder you already own? → Gravity Forms, WS Form, or JetFormBuilder with its WooCommerce add-on.
    • Formula, tiered, or continuous pricing that must handle custom quantities and dimensions? → CraftForms (Extra Product Options and APF do formula pricing too; CraftForms’ closest-up lookup is what prices the 137-unit order correctly).
    • Options that track real stock, or a visual configurator (customers crop their own photo, see a live composite preview)? → CraftForms — the only tool here that does either.
    • One tool that also builds forms outside WooCommerce — contact, bookings, standalone Stripe? → CraftForms.

    For simple fees, the official add-on is genuinely the right call, and each specialist above owns its niche. The field narrows once your products need configurable pricing that also respects inventory, or a configuration that’s visual and interactive — that combination is where CraftForms is hard to match.


  • Track WooCommerce Stock by Option — No Variations

    Track WooCommerce Stock by Option — No Variations


    TL;DR

    • Popular add-on plugins (WooCommerce Product Add-Ons, TM Extra Product Options, YITH) let you add fields to a product, but those fields are display only — they don’t draw down stock.
    • WooCommerce’s own answer, variations, can track stock per combination — but you have to create every combination by hand, which explodes into hundreds of rows (the “196 / 640 variations” trap).
    • CraftForms tracks stock per option combination instead: a Dynamic SKU Expression builds a SKU from the customer’s selections, and a Custom Stock Management table maps each resolved SKU to a quantity.
    • On add-to-cart, the requested quantity is checked against that SKU’s stock on the server and blocked if it would oversell — no variation matrix required.
    • The resolved values are exposed as wc_sku and wc_stock, so you can reuse them in your pricing and on-page messaging formulas.

    The running example in this guide is a Custom Dining Table configurator built with CraftForms. The customer picks a wood type (oak, walnut, ash), a size (150x90, 180x100, 200x110), and a leg style (black-metal, brass, oak-wood, hairpin), optionally adds engraving, and sets a quantity. Those three option fields — wood_type, size, legs — are the axes we’ll track stock against.

    The features in this guide are part of CraftForms Pro and require the WooCommerce integration.


    The problem: add-ons take orders, but they don’t count stock

    If you sell configurable products, you’ve almost certainly reached for an “extra product options” or “product add-ons” plugin. They’re great at one thing: putting extra fields — a colour, a size, an engraving, a finish — onto the product page so customers can configure what they want.

    What they’re not built to do is track inventory for those options. The add-on field is a label that travels with the order; it isn’t connected to any stock count. So if you have 8 black handles and 3 brass ones, nothing stops a customer ordering 10 brass. You find out when you go to fulfil the order.

    This is one of the most repeated complaints in the WooCommerce community — phrased as “alternatives to YITH product options that actually track inventory,” “product options with stock control,” and “variable products with inventory per attribute.” People want the rich configuration of an add-on plugin and real stock control. Normally you can’t have both.

    Why variations aren’t the answer either

    WooCommerce does track stock per combination — through variations. The catch is that a variation is a finished, pre-built row: you have to create every combination in advance, set its stock, and maintain it.

    That’s fine for a T-shirt (3 sizes × 2 colours = 6 rows). It collapses the moment you have more than a couple of option axes:

    • Our dining table is modest by configurator standards — 3 woods × 3 sizes × 4 leg styles = 36 variations — and you’d still have to build and stock-manage all 36 by hand, before you even add engraving.
    • Push the axes a little — 4 sizes × 7 colours × 7 finishes = 196 variations — and you hit exactly the “196 variations, need an upgrade option that doesn’t create new inventory” scenario people describe.
    • Add one more axis and you’re into the “640 manual options for one product” territory.

    Every one of those rows is created and stock-managed by hand. Most shops give up and either drop options or stop tracking stock for them — which puts you right back at the first problem.

    The CraftForms approach: a SKU built from the selection, and one stock table

    CraftForms takes the middle path. You don’t pre-build every combination. Instead you describe, with one expression, how to derive a SKU from whatever the customer picked, and you keep a single table of how much stock each SKU has. Stock is then tracked per option combination — without a variation matrix.

    Two building blocks do the work, and both live in the same place: open the product, go to its CraftForms product settings, and expand the Inventory section (it’s open by default).

    1. Dynamic SKU Expression

    A small expression that returns a SKU string built from the form’s field values. For our dining table:

    'TBL-' ~ wood_type ~ '-' ~ size ~ '-' ~ legs
    

    If a customer selects walnut, the 180×100 size, and brass legs, this resolves to TBL-walnut-180x100-brass. The expression can reference any field or smart variable on the form (wood_type, size, legs here), so the SKU can encode as many option axes as you need.

    A few details worth knowing:

    • It’s optional. Leave it blank and CraftForms falls back to the product’s own WooCommerce SKU.
    • The resolved value is exposed to your other formulas as wc_sku, so you can reuse it in pricing or in a message on the page.
    • It’s evaluated on the server at submit from sanitised field data — the browser never gets to declare the SKU.

    2. Enable Custom Stock Management + the Stock Table

    Flip on Enable Custom Stock Management and a Stock Table appears. It’s a simple two-column table:

    SKUStock Qty
    TBL-oak-150x90-black-metal12
    TBL-walnut-180x100-brass3
    TBL-ash-200x110-hairpin7

    Each row maps a resolved dynamic SKU to the quantity you have on hand — e.g. only 3 walnut / 180×100 / brass tables are buildable because you have 3 sets of brass legs left. Decimal quantities are supported (useful for length- or area-based stock), and the table has Import CSV / Export CSV buttons — so you can edit your whole stock list in a spreadsheet and paste it back, instead of clicking through rows.

    When this is on, the available quantity is also exposed as wc_stock, so you can drive on-page messaging or pricing from it (more on that below).

    Wiring it to the dining table form

    On the Custom Dining Table product, the Inventory panel ends up looking like this:

    • Quantity Variable: qty — the number input the form shares with the Add-to-Cart button.
    • Dynamic SKU Expression: 'TBL-' ~ wood_type ~ '-' ~ size ~ '-' ~ legs — references the three option fields by name.
    • Enable Custom Stock Management: on.
    • Stock Table: one row per combination you actually stock, e.g. TBL-oak-150x90-black-metal → 12.

    The field names are what matter — they have to match the form. In this configurator the wood and leg pickers are rich image-card radio options and size is a select, but to the SKU expression they’re just wood_type, size, and legs resolving to clean, SKU-safe values (oak, 180x100, brass). That’s why the option values are kept short and slug-like even though the on-card labels read “Oak”, “Walnut”, etc.

    How oversell prevention actually works

    Here’s the flow when a customer hits add-to-cart, all of it server-side and authoritative:

    1. CraftForms evaluates your Dynamic SKU Expression against the submitted selections to get the resolved SKU (e.g. TBL-walnut-180x100-brass).
    2. It looks that SKU up in your Stock Table.
    3. It reads the requested quantity from the field you nominated as the Quantity Variable — on this form that’s qty (if you don’t set one, it assumes 1).
    4. If the requested quantity exceeds the available stock for that SKU, the add-to-cart is blocked with a clear message:

    Only 3 item(s) available for this configuration.

    Because this runs on the server before any cart operation, a tampered browser request can’t sneak an oversell through.

    One important rule to design around: a SKU that isn’t in the Stock Table is not restricted. The table is an allow-and-limit list, not a deny-by-default gate. So if you want every combination capped, make sure every resolvable SKU has a row. Combinations you deliberately leave out of the table will sell without a stock check — which is convenient for “made to order, unlimited” options, but a gotcha if you assumed missing = blocked.

    Putting wc_stock to work (beyond just blocking)

    Because the resolved stock is available as wc_stock in your other formulas, you don’t have to limit yourself to the hard block. A few patterns shops like:

    • Low-stock nudges — show “Only a few of these brass-leg tables left” when wc_stock drops below a threshold, using a smart variable feeding an on-page message.
    • Per-configuration pricing tied to availability — e.g. a small surcharge on a scarce leg style or wood.
    • Surfacing the SKU — echo wc_sku (TBL-walnut-180x100-brass) into the order/confirmation so fulfilment sees exactly which combination was bought.

    The point is that the same values powering the oversell check are first-class inputs to everything else CraftForms calculates.

    When native variations are still the right call

    Be fair to variations — they’re the right tool when:

    • You have a small, fixed set of combinations.
    • Each combination needs its own image, SKU, weight, and shipping treatment that WooCommerce manages natively.
    • You want those combinations to appear in WooCommerce reports as distinct products.

    CraftForms’ Dynamic SKU + Custom Stock Management is the answer when the combination count is large, when you don’t want to pre-build a matrix, or when you want rich option configuration (with live pricing) and real per-option stock control on the same product.