Hosted Actions let a hosted theme change storefront state by posting a form to the store's own domain: the cart and wishlist, the customer's account, reviews and comments, the shopper's country and language, and cookie consent. Build each form with Vascula's {% form %} tag. It works without JavaScript, and the storefront bridge upgrades it to a background request. Each page in this section lists one area's actions with their fields, validation, limits, and responses. Read the current state from CTX.
CSRF#
The easiest way to build an action form is with Vascula's {% form %} tag. It selects the correct action URL and adds the CSRF token for you. If you build the form yourself, request csrf_token in ctx_needs and send it in a hidden _csrf field, or use the X-CSRF-Token header with fetch. Always use the token rendered for the current visitor session.
{% if product %}
{% form 'cart_add' %}
<input type="hidden" name="product_id" value="{{ product.id }}">
{% if product.selected_variant %}
<input type="hidden" name="variant_id" value="{{ product.selected_variant.id }}">
{% endif %}
<button type="submit">Add to cart</button>
{% endform %}
{% endif %}
Response modes#
Without JavaScript, a form submission ends with a redirect (HTTP 303), usually back to the page, and the next page shows the outcome through form_notice. To receive JSON instead, send Accept: application/json or add _format=json; the storefront bridge does this for you. Every JSON response has success. When success is true, the rest of the response is the result shown on the action's page. When success is false, user_errors lists one or more errors, each with code, message, and, when it concerns one input, field. The HTTP status of a refusal varies by action, from 200 to a 4xx or 5xx status, so check success and user_errors rather than the status. The review-photo upload through tringify.reviews.uploadMedia(file) follows the same rule: it resolves with success false, message and user_errors instead of rejecting. Cart and wishlist actions also accept client_mutation_id, which you can reuse when retrying the same request safely.
Theme presentation events#
Tringify's storefront bridge submits every Vascula hosted-action form in the background, so do not add another submit handler that sends the same form. The bridge dispatches these events on document: tf:action-start when the request begins; tf:action-success when the action is accepted, before marked sections refresh; tf:action-done after marked [data-tf-refresh] sections refresh, where event.detail.refreshed reports whether they did; and tf:action-error when the action is refused or the request fails. Every event's detail has action and form; success, done, and error also have result, the JSON response, which is null after a network failure. tf:action-error is cancelable: call preventDefault() to show your own message; otherwise the bridge shows the user_errors messages. Wait for tf:action-done before reading refreshed cart content or opening a cart drawer. An action whose response names a destination, such as localization_set, order_track_verify, login, register, or logout, navigates there without success or done events, and buy_now opens checkout and dispatches only tf:action-done, with refreshed false. cart_add, wishlist_add, and wishlist_toggle forms keep their originating [data-tf-refresh] section unchanged by default, so product and listing forms retain the shopper's selected variant and quantity. Set data-tf-refresh-origin="true" on a cart-dependent [data-tf-refresh] section, such as one containing a gift picker, when that section must refresh itself. Set data-tf-preserve-state="open" on a refreshed drawer to retain its current open or closed state. A form in a section the bridge does not refresh, such as a newsletter, contact or comment form, shows result.response.message from tf:action-success: the same platform message, in the shopper's language, that a submission without JavaScript shows as form_notice. auth_code_send does not navigate: the theme moves to its own code-entry step from tf:action-success, using result.mode to show the sign-in or create-account form.
document.addEventListener("tf:action-success", function (event) {
if (event.detail.action === "wishlist_toggle") announce("Wishlist updated");
});
document.addEventListener("tf:action-done", function (event) {
if (event.detail.action === "cart_add" && event.detail.refreshed) openCartDrawer();
});
document.addEventListener("tf:action-error", function (event) {
var errors = (event.detail.result && event.detail.result.user_errors) || [];
if (!errors.length) return;
event.preventDefault();
showErrors(errors);
});
User errors#
A user error is ready to show to the shopper. Its message is already translated for the current storefront language and includes any merchant wording overrides. Display message as provided. Use code only when your theme needs different behavior for a particular error, and use field to place feedback beside the relevant input when it is present.
Related: read the state these actions change from CTX, for example Locale, country, and currency and Customers, carts, and consent.