2.0 Beta

Smootify 2.0 is in public beta. Your 1.x store keeps working. Move when you want.

What changed

Filters

Change what Smootify does before it does it, with Smootify.addFilter and its four filters for the lines added to the cart, the checkout address, draft orders and the metafields a page asks for.

v2.0 beta
Open .md1.x page ↗
On this page
  1. How a filter works
  2. add_to_cart_lines
  3. checkout_url
  4. draft_order_data
  5. metafield_definitions
  6. Page hooks from 1.x
  7. Where people get stuck
  8. Coming from 1.x

A filter gets a value just before Smootify uses it, and can change it or stop what is about to happen. Use one to check or enrich the lines added to the cart, to send the visitor to another checkout address or keep them on the page, to change a draft order, or to choose which metafields the page asks Shopify for. Events, in comparison, only report what already happened: see Events.

How a filter works

window.Smootify = window.Smootify || [];
window.Smootify.push((Smootify) => {
  const remove = Smootify.addFilter("add_to_cart_lines", (lines, context) => {
    // answer the lines changed, nothing to keep them, or false to add nothing
  });
  // later: remove();
});
  • Add filters in a push function. It runs before Smootify asks Shopify for anything, so the filter applies from the first request.
  • A filter can be async, except metafield_definitions.
  • Filters run in the order they were added. Each gets what the one before answered.
  • addFilter returns a function that removes the filter. A name that is not one of the four only logs a warning.

What a filter answers decides what happens next:

value

Smootify uses it

Then
The next filter gets it, then Smootify goes on.
For example
The lines with an attribute added; the checkout address with a parameter.
undefined

The value stays as it was

Then
The next filter gets the same value.
For example
A filter that only checks, or that leaves draft orders alone.
false

Stops, with no message

Then
Nothing happens. An empty list of lines does the same.
For example
Nothing is added to the cart; the visitor stays on the page.
Except
metafield_definitions, which cannot stop.
Error

Stops, with your message

Then
The message shows in the form or in the cart, and goes out with smootify:errors.
For example
A message written for the visitor.
Except
metafield_definitions: the error is logged and the filter skipped.

add_to_cart_lines

(lines, { goToCheckout, element }): the lines about to be added to the cart. Every addition goes through it: the add-to-cart forms (the main line and its add-ons together), Magic Box boxes, button[data-is="direct-add-to-cart"], buy again in the account, Smootify.addToCart, Smootify.addBoxToCart and the /cart/add.js calls of theme apps.

  • lines is a list of { merchandiseId, quantity, attributes, sellingPlanId }, where merchandiseId is the variant’s global id.
  • element is the smootify-add-to-cart form, or the button that added. It is undefined for an addition from a script.
  • goToCheckout is true for a buy now.
  • An empty list adds nothing, like false.

Refuse a quantity above a limit, with a message in the form:

Smootify.addFilter("add_to_cart_lines", (lines) => {
  if (lines.some((line) => (line.quantity ?? 1) > 5)) {
    throw new Error("You can buy at most 5 of each item.");
  }
});

Add a hidden attribute to every line. A key that starts with _ is saved on the line but not shown at checkout:

Smootify.addFilter("add_to_cart_lines", (lines, { element }) =>
  lines.map((line) => ({
    ...line,
    attributes: [...(line.attributes ?? []), { key: "_source", value: element ? "form" : "script" }],
  })),
);

checkout_url

(url, { cart, draftOrder, draftOrderData }): where the visitor goes to check out. It runs for the cart’s checkout button, for a buy now and for Smootify.checkout().

  • Answer another address, for example with parameters added, or false to keep the visitor on the page (terms not accepted, an order below a minimum).
  • The message of an error it throws shows in the cart, or in the form for a buy now.
  • draftOrder is true when the address is the invoice of a draft order. Its data is draftOrderData.
  • A buy now with a custom price (Name your price, Product configurator) skips the cart: there cart is null, and the line is in draftOrderData.
  • smootify:initiate_checkout goes out only when the visitor actually leaves, after every filter.

Keep the visitor in the cart until a checkbox is ticked:

Smootify.addFilter("checkout_url", () => {
  const terms = document.querySelector("smootify-cart input[name=terms]");
  if (terms instanceof HTMLInputElement && !terms.checked) {
    throw new Error("Please accept the terms of sale.");
  }
});

Add a parameter to Shopify’s checkout address:

Smootify.addFilter("checkout_url", (url, { draftOrder }) => {
  if (draftOrder) return;
  const next = new URL(url);
  next.searchParams.set("locale", "it");
  return next.toString();
});

draft_order_data

(data, { reason }): the draft order about to be created. Draft orders need the Draft Orders extension, on the server plan: see Quotes and B2B.

  • reason is quote for a quote from a smootify-cart[data-draft], checkout for a cart with a custom price, or buy-now for a buy now with a custom price.
  • data is { input: { lineItems, customAttributes, note, presentmentCurrencyCode }, customerId, email }.
  • The message of an error it throws shows in the cart, or in the form for a buy now.

Tag every quote with a custom attribute:

Smootify.addFilter("draft_order_data", (data, { reason }) => {
  if (reason !== "quote") return;
  const customAttributes = [...(data.input.customAttributes ?? []), { key: "Source", value: "Website quote" }];
  return { ...data, input: { ...data.input, customAttributes } };
});

metafield_definitions

(definitions, { scope, attribute, selector }): the metafields the page asks Shopify for, as a list of { namespace, key }. It runs once for each kind of request:

  • scope: "product": the products, from the [metafield] elements inside smootify-product;
  • scope: "variant": their variants, from the [variant-metafield] elements;
  • scope: "cart": the products of the cart lines, from the [metafield] elements inside cart-item.

Add the metafields a script reads, and remove the ones nobody shows, which makes each request smaller. The filter is synchronous and cannot stop anything: it answers a list, or nothing. An error it throws is logged and the filter skipped. It runs again whenever the page asks for new metafields, so build the answer from the list it gets.

Smootify.addFilter("metafield_definitions", (definitions, { scope }) => {
  if (scope !== "product") return;
  return [...definitions, { namespace: "custom", key: "care_guide" }];
});

Smootify.on("product_loaded", ({ product }) => {
  const guide = product?.metafields.find((m) => m?.namespace === "custom" && m.key === "care_guide");
  if (guide) console.log(guide.value);
});

In Shopify, the metafield definition still needs Storefront access, or the value never comes back.

Page hooks from 1.x

1.x read a few functions set on window. They still work in 2.0 and are deprecated. On a .webflow.io address, the debugger points them out.

  • window._metafieldsDefsFilter(selector, attribute, definitions): use metafield_definitions. The old hook still runs, before the filters.
  • window._filterDraftOrderData(data): use draft_order_data, which applies to every draft order, not only to quotes. The old hook still runs, before the filters.
  • window.smootifyAddOnsBox(lines), the synchronous box hook: use window.smootifyAddonsBox. The old one is used only when the new one is missing.

window.smootifyAddonsBox is not deprecated. Smootify calls it when an add-to-cart form adds a product and its add-ons as one box. It gets { lines, mainProduct, mainVariant, selectedAddonsVariants }, can be async, and answers the box’s title and image, which replace the box-title and box-image of the form. See Product add-ons.

window.smootifyAddonsBox = ({ mainProduct, selectedAddonsVariants }) => ({
  title: `${mainProduct.title} with ${selectedAddonsVariants.length} extras`,
});

Where people get stuck

Check these first

  • The first product ignores the filter. The filter was added outside a push function, after Smootify had already asked Shopify. Add it in a push function.
  • The checkout button does nothing. A checkout_url filter answered false. That keeps the visitor on the page without a message: throw an Error instead to show one in the cart.
  • false from metafield_definitions changes nothing. This filter cannot stop. Answer the list you want.
  • A metafield added by the filter is still missing. Its definition in Shopify has no Storefront access, or the filter checks the wrong scope.
  • A 1.x hook does nothing. _metafieldsDefsFilter must exist before Smootify’s first request. Move it to a metafield_definitions filter in a push function.

Coming from 1.x

1.x2.0
window._metafieldsDefsFilterSmootify.addFilter("metafield_definitions", …)
window._filterDraftOrderData (quotes only)Smootify.addFilter("draft_order_data", …) (every draft order)
window.smootifyAddOnsBoxwindow.smootifyAddonsBox

add_to_cart_lines and checkout_url are new in 2.0.

    ↑ ↓ to moveEnter to openEsc to close