2.0 Beta

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

What changed

JavaScript API

The methods of window.Smootify by task, for the cart, products, the customer, the market and consent, with short examples and how each kind of method answers.

v2.0 beta
Open .md1.x page ↗
On this page
  1. Methods, events, filters or the AJAX API
  2. How the methods answer
  3. Cart
  4. Products
  5. Customer
  6. Market and money
  7. Consent
  8. Toasts
  9. Storefront API
  10. Where people get stuck
  11. Coming from 1.x

window.Smootify gives your scripts the cart, the products, the logged-in customer, the visitor’s market and their consent. This page goes through the main methods by task, with short examples. Every method, with its full signature, is in the Reference under API.

Note

Call the methods from a window.Smootify.push function, so they run once Smootify is ready: see Run your scripts with Smootify. The examples on this page are the body of such a function, where Smootify is its argument.

window.Smootify = window.Smootify || [];
window.Smootify.push(async (Smootify) => {
  const cart = await Smootify.getCart();
  console.log(cart.totalQuantity, Smootify.formatMoney(cart.cost.totalAmount.amount));
});

Methods, events, filters or the AJAX API

Your code works with Smootify in four ways. This page covers the methods; the other three have their own pages:

window.Smootify

Methods: ask and act

Use it to
Read the cart, the products, the customer or the market; add to the cart; open the mini cart.
For example
getCart(), addToCart(), getCustomer()
Good to know
Call them from a push function. Most return a Promise.
Smootify.on

Events: react to what happened

Use it to
Keep your own counter in sync, track additions, start analytics after consent.
For example
cart_updated, added_to_cart, consent_changed
Good to know
What the handler returns is ignored. once stops after the first event.
Smootify.addFilter

Filters: change it before it happens

Use it to
Check or enrich the lines added to the cart, change the checkout address or a draft order, choose the metafields the page asks for.
For example
add_to_cart_lines, checkout_url
Good to know
Answer the value changed, nothing to keep it, or false to stop.
/cart.js

AJAX API: apps written for Shopify themes

Use it to
Run an app's snippet as it is: Smootify answers the theme endpoints on the page.
For example
/cart.js, /cart/add.js, /products/{handle}.js
Good to know
Same cart as the page. Start the snippet with type="smootify-load".

How the methods answer

  • Most methods return a Promise. isLoggedIn(), formatMoney(), selectVariant() and the listeners answer at once.
  • Some resolve with Shopify’s errors instead of the cart. addToCart, applyCouponCode and applyGiftCard resolve with the cart, or with a list of errors ({ message, field, code }). Tell them apart with Array.isArray.
  • The other cart methods reject with Shopify’s errors. Use try / catch around updateCartLines, removeCartLines, removeCouponCode, removeGiftCard, updateCartNote, updateCartAttributes and checkout.
  • Ids are Shopify’s global ids. A variant is gid://shopify/ProductVariant/<id>, a cart line gid://shopify/CartLine/<id>, a product gid://shopify/Product/<id>.
  • The data has the Storefront API shape: the cart, the products and the customer carry the fields Smootify asks Shopify for, and null where Shopify has no value. The shapes are in smootify.d.ts.

Cart errors also go out with the smootify:errors event.

Cart

MethodWhat it does
getCart(), getCartID()the cart, or its id
addToCart(lines, goToCheckout)adds lines; true goes on to checkout
updateCartLines(lines), removeCartLines(ids), clearCart()change, remove or empty the lines
applyCouponCode(code), removeCouponCode(code)discount codes
applyGiftCard(code), removeGiftCard(id)gift cards; the id is from cart.appliedGiftCards
updateCartNote(note), updateCartAttributes(attributes)the cart note and the cart attributes
checkout()goes to checkout, as the cart’s checkout button
openCart(), closeCart()open or close the page’s mini cart
reloadCart()asks Shopify for the cart again

Add a variant from your own button, then open the mini cart:

document.querySelector("[data-add-sample]")?.addEventListener("click", async () => {
  const answer = await Smootify.addToCart([
    { merchandiseId: "gid://shopify/ProductVariant/44012345678901", quantity: 1 },
  ]);
  if (Array.isArray(answer)) console.warn(answer[0]?.message);
  else await Smootify.openCart();
});

Every addition goes through the add_to_cart_lines filters, then fires added_to_cart and cart_updated. A line can carry attributes ([{ key, value }], shown at checkout, hidden when the key starts with _) and a sellingPlanId.

Change the quantity of a line:

const cart = await Smootify.getCart();
const line = cart.lines.nodes[0];
if (line) {
  try {
    await Smootify.updateCartLines([{ id: line.id, quantity: line.quantity + 1 }]);
  } catch (errors) {
    console.warn(errors);
  }
}

A code that Shopify accepts but does not apply to this cart comes back in cart.discountCodes with applicable: false, not as an error.

checkout() goes through the checkout_url filters, like the cart’s button. openCart() and closeCart() work on a mini cart (a smootify-cart that opens), not on a cart page.

Two cart methods serve add-ons: addBoxToCart(lines, { title, image }, goToCheckout) adds lines as one Magic Box, and getCartAsDraftOrderData(email, note, customAttributes) gives the cart as a draft order (see Quotes and B2B).

Products

  • getProductByHandle(handle): a product by its handle, or null when the visitor’s market does not sell it.
  • getProductsById(ids): several products by their global ids, as an object keyed by id. Products the market does not sell are missing from it.
  • selectVariant(element, variantId): selects a variant of a smootify-product on the page. The element is the product or any element inside it; the id is the variant number or its global id. It answers false when the product has no such variant.
const product = await Smootify.getProductByHandle("oak-chair");
if (product) console.log(product.title, product.variants.nodes.length);

const card = document.querySelector("smootify-product");
if (card) Smootify.selectVariant(card, 44012345678901);

The product page’s product carries every variant. Elsewhere a product carries its first 250.

Customer

The customer methods need Shopify’s customer accounts and the newCustomerAccountsPublicKey option: see Customer accounts.

  • getCustomer(): the logged-in customer, or null for a guest, once the session is known.
  • isLoggedIn(): answers at once, and is false until the session is known. Prefer getCustomer() at load.
  • passwordlessLogin() starts Shopify’s login, logout() ends the session on the site and on Shopify.
  • queryCustomer(fields): a Customer Account API query on the customer. fields is what goes inside customer { … }, and the answer is { customer }.
  • setCustomerMetafields(...metafields): writes metafields of the logged-in customer (namespace is custom when left out). In Shopify, the metafield definition needs Customer accounts access set to Read and write.
  • getNewCustomerAccessToken(): the Customer Account API token of the session, "" for a guest.
const customer = await Smootify.getCustomer();
if (customer) {
  const { customer: data } = await Smootify.queryCustomer(
    "orders(first: 3, reverse: true) { nodes { name totalPrice { amount currencyCode } } }",
  );
  console.log(customer.displayName, data);
}

Market and money

  • getLocalization(): the visitor’s country (with its currency), language and market, and the countries and languages the store sells in.
  • changeCountryByIsoCode(isoCode) and changeMarketLanguage(languageCode): change the market. They answer true when it changed, and the page follows the new market.
  • formatMoney(amount): an amount in the store’s money format. It takes a number in cents (1250) or an amount as Shopify sends it ("12.50").
const { country, language } = await Smootify.getLocalization();
console.log(country.isoCode, country.currency.isoCode, language.isoCode);

const changed = await Smootify.changeCountryByIsoCode("DE");
if (changed) console.log("Prices are now for Germany");

The codes are Shopify’s: DE for a country, IT for a language.

  • getConsent(): the visitor’s consent, from Shopify’s Customer Privacy API. It loads Shopify’s consent script if the page has not loaded it yet.
  • setConsent(choices): saves the visitor’s choices for marketing, analytics, preferences and saleOfData, and answers the new consent.
const consent = await Smootify.getConsent();
if (consent.allowed.analytics) {
  // start your own analytics
}
await Smootify.setConsent({ analytics: true, marketing: false });

To react when the visitor answers the banner, listen to consent_changed. The banner itself is in Cookie consent.

Toasts

toast(name, items) shows the page’s [toast="name"] template, the way Smootify shows its own toasts, with items written into its [toast-item] parts. It answers false when the page has no such template.

Smootify.toast("added-to-cart", { title: "Gift wrap", price: "€4.00" });
Smootify.toast("booking-saved", { message: "See you on Tuesday at 10:00" });

A name of your own works when the designer adds a template with that name, such as [toast="booking-saved"]: only your code shows it. A name ending in -error falls back to the [toast="error"] template when its own is missing.

Storefront API

query(query, variables) sends a Storefront API query with the store’s access and answers its data. It rejects with the GraphQL errors. Use it for data no attribute shows.

The query runs as you write it. To get the visitor’s prices and translations, add Shopify’s @inContext directive with the country and language from getLocalization():

const { country, language } = await Smootify.getLocalization();
const data = await Smootify.query(
  `query ($handle: String!, $country: CountryCode, $language: LanguageCode)
  @inContext(country: $country, language: $language) {
    product(handle: $handle) { title tags }
  }`,
  { handle: "oak-chair", country: country.isoCode, language: language.isoCode },
);
console.log(data);

Where people get stuck

Check these first

  • addToCart seems to succeed but nothing is added. It resolved with Shopify’s errors, such as a sold-out variant. Check the answer with Array.isArray.
  • An error thrown by updateCartLines is not caught. These methods reject: await them inside try / catch.
  • isLoggedIn() is false for a logged-in customer. The session is not known yet. Use await Smootify.getCustomer().
  • Products come back empty. getProductsById takes global ids (gid://shopify/Product/…), and a product that is not published to the store’s sales channel, or not sold in the visitor’s market, is not returned.
  • query shows prices in the wrong currency. The query has no @inContext directive.
  • setCustomerMetafields answers errors. The metafield definition in Shopify does not give customer accounts write access.

Coming from 1.x

The 21 methods of 1.x keep their names and arguments. 2.0 adds push, on, once, addFilter, isLoggedIn, getCustomer, logout, getLocalization, getConsent, setConsent, getProductByHandle, removeCouponCode, removeGiftCard, updateCartNote, updateCartAttributes, checkout, selectVariant, openCart and closeCart.

  • window.Smootify.store and the other internals of 1.x are gone: only the methods of window.Smootify are supported.
  • formatMoney accepts its two extra 1.x arguments and ignores them.
  • passwordlessLogin and queryCustomer work only with Shopify’s new customer accounts.

    ↑ ↓ to moveEnter to openEsc to close