Store Builder

Form actions

Three moments in a form's life — before submit, after success, after failure — each take several ordered actions, plus two ways to write your own rules.

Select the Form element on the page and open the Action group (vi: Hành động) in the inspector. A form has no On click trigger — the Submit button belongs to the form block, not to this element — but it has three of its own: Before submit, After success, After failure. Each is its own draggable list.


Several actions, in order

A trigger no longer takes exactly one action. Add action at the end of each list adds as many rows as you need, each an independent action with its own picker and fields; the ✕ button (Remove this action) drops a row, the ⠿ handle drags to reorder. Actions run in the order shown on screen, top to bottom.

Before submit and After failure share one list: Open pop-up, Close pop-up, Open cart, Close cart — nothing that leaves the page, because at that moment the form has not finished, or was just refused. After success adds three that leave the page: Go to URL, Open page, Go to checkout.

After failure only runs when the SERVER refuses the submission — an answer it re-checked and did not accept, an empty cart, a sold-out product, a rejected code — or when the request itself failed (the visitor lost connection). A required field the visitor left blank, or a badly formed answer: both are stopped on the page before anything is sent, so this trigger never runs for them. Putting an Open pop-up action here expecting it to catch a blank field is expecting the wrong thing.

"This action leaves the page, so the actions below it will never run — drag it to the end."

That notice shows under a row whenever it leaves the page and is not the last row of its trigger: everything after it would never run, because the page has already gone somewhere else. Drag the row to the end and the notice clears.

Before submit never blocks the send

Before submit can never block the send, even if one of its actions fails or is not ready. This trigger only does side work — open a pop-up, open the cart — while the submission keeps going behind it; it never stands in the way. If you need a rule that actually refuses a submission — "less than two hours before the booking", "this email is already in use" — this is not the place to write it: see Hooking your own code in below.

After success, and the "After sending" text

The form's Settings tab (open it with Edit form) has a box of its own called After sending — the text that replaces the form the moment a visitor sends it, e.g. "Thank you! We have received your submission."

On an order form this text may contain {orderNumber}: it is replaced with the number of the order just created (for example "#1001"). Left blank, the default already names it: "Order #1001 received. Thank you!". Custom code gets the same number as e.detail.orderNumber on wb:form:success.

The After success action chain runs BEFORE that text. If the chain carries an action that leaves the page — Go to URL, Open page, Go to checkout — the page is already gone by the time the text would have shown: a navigating action always wins. With no navigating action in the chain, the text appears exactly as it always did.


The three triggers above are what a form does on its own, with no code. Two other ways to attach a shop's own rules to a form:

Hooking your own code in

See Hooking your own code into a form — that page covers the three DOM events a form fires on a published page in full: wb:form:submit (cancelable, with reject(<field id>, <message>)), wb:form:success, wb:form:error. That is where rules belonging to your shop, that no switch in the editor can make for you, get written: no booking less than two hours ahead, none of the ten disposable email domains.

Worth stating here: wb:form:submit fires BEFORE the Before submit trigger this page describes. If your code calls reject(...), the submission is cancelled and the Before submit action chain never runs — exactly like a platform-side format error.

The form.submitted webhook

Manage → Settings → Webhooks has an event called Form submitted (vi: Biểu mẫu được gửi, code form.submitted) — see Webhooks for how to register an endpoint and the things that will surprise you (duplicates, no ordering, the signing secret).

This event fires for every submission that is stored — including one whose cart changed out from under it before the server could create a real order (the JSON carries sinkError saying why) — but never for a refused one: a reject() from your own code, a conditional rule, a bad format, or a request that never reached the server at all.

Payload, an ordinary submission:

{
  "submissionId": "sub_...",
  "formId": "form_...",
  "formName": "Book a table",
  "formType": "custom",
  "pageId": "pg_...",
  "submittedAt": "2026-09-06T10:00:00Z",
  "values": { "email": "shopper@example.com" },
  "files": [{ "field": "cv", "name": "cv.pdf", "url": "https://...", "size": 102400 }],
  "orderId": "order_...",
  "orderNumber": "#1001",
  "customerId": "cus_..."
}

values is keyed by Field ID — the same key wb:form:submit uses, so a rule written for the DOM event reads the webhook's data with no key to rename. A password field is never present in values.

Five keys — pageId, orderId, orderNumber, customerId, sinkError — are absent entirely when there is nothing to report, never an empty string: orderId/orderNumber/customerId only appear when that submission created an order or was matched to a customer record, and sinkError only appears when the form saved the answers but the order-creation step behind it failed — in which case orderId is absent too, since no order was created, e.g.:

{ "sinkError": "product prod_9 is out of stock" }

Updated 06/09/2026