# Smootify documentation (2.0), full text
> Smootify at a glance (facts, plans, add-ons and the FAQ), then every page of www.smootify.io/docs as markdown, in sidebar order. Paths are identical to docs.smootify.io/docs/*.
411 pages: 411 ready, 0 drafts written from the 2.0 source notes, 0 being written (outline only). Each page says which it is.
Index: https://www.smootify.io/llms.txt
---
# Smootify at a glance
Smootify is a Shopify app that turns a site designed in Webflow into a Shopify store. You design every page in Webflow; Shopify keeps the products, stock, checkout, payments and orders; Smootify connects the two with elements and attributes placed on native Webflow elements. Smootify 2.0 is in public beta.
- Plans: Free, to build and publish on a webflow.io address; Lite, $19 a month, to go live on your own domain; Server, $49 a month, which adds the 8 add-ons that run on Smootify’s server and the automatic CMS sync. Billed yearly: $15 and $39 a month.
- Billing: Smootify is billed on the store’s Shopify invoice, like any Shopify app: one app on the bill instead of ten. Shopify and Webflow bill their own plans as usual.
- Requirements: Shopify Basic, Grow, Advanced or Plus (not Starter, which cannot use Shopify’s Headless channel) and a Webflow site plan with the CMS. No Webflow Ecommerce plan is needed.
- Add-ons: 17, included in the plan at no extra cost. 9 work on every plan, Free included (magic box, magic add-ons, cart upsell, dynamic properties, predictive search, search and discovery, store locator, last viewed, wishlist); 8 need the Server plan (booking, product configurator, name your price, draft orders, metaobject creator, customer metafield editor, newsletter subscription, product file upload).
- Free Shopify features stay free: Search and Discovery filters, predictive search and complementary products come with every plan and are never sold as paid add-ons.
- Three apps: the Smootify Shopify app (connects the store, carries the plan), the Smootify Webflow app (171 components, every attribute and condition, inside the Webflow Designer) and Smootify Studio, the desktop app for Mac and Windows, in beta (Shopify to Webflow CMS sync, CMS setup, Claude with Smootify’s skills for custom scripts).
- For AI agents: the Figma wireframe kit, the markup contract, 5 skills (github.com/smootify/skills) and the Smootify MCP, in beta, let an agent such as Claude, Cursor or Codex write the Webflow build; a person reviews it, connects Shopify and publishes.
- Shopify Plus is not needed: calculated prices, prices the customer chooses and B2B quotes go through Shopify draft orders on Basic, Grow and Advanced, and bundles merged at checkout work on every plan. Changing a price inside the regular checkout is still a Plus feature.
- Limits: the checkout belongs to Shopify and is not designed in Webflow; a build-your-own box (Magic box) cannot be a subscription, because Shopify’s Cart Transform API does not merge lines that have a selling plan.
- 2.0 is in public beta: sites on Smootify 1.x keep working, and moving is documented step by step (https://www.smootify.io/docs/start/migrate).
- Smootify has more than 7,000 installs on Webflow and was a Webflow Awards 2025 finalist as Tech Partner of the Year. It is made by Eclipse, in Italy.
## Add-ons
- [Booking](https://www.smootify.io/add-ons/booking) (Server plan): Time slots booked from a calendar you design: appointments, paid sessions, pickup and delivery windows.
- [Product configurator](https://www.smootify.io/add-ons/product-configurator) (Server plan): Prices calculated live from the options, quantities and formulas you set.
- [Name your price](https://www.smootify.io/add-ons/name-your-price) (Server plan): The customer sets the amount they pay, inside the limits you give.
- [Draft orders](https://www.smootify.io/add-ons/draft-orders) (Server plan): Quote requests from the store, answered from your Shopify admin.
- [Magic box](https://www.smootify.io/add-ons/magic-box) (every plan, Free included): Mix and match bundles: the customer builds the box from the products you choose.
- [Magic add-ons](https://www.smootify.io/add-ons/magic-add-ons) (every plan, Free included): Extras attached to a product, with their own titles and images at checkout.
- [Cart upsell](https://www.smootify.io/add-ons/cart-upsell) (every plan, Free included): Shopify’s free complementary products, shown in your Webflow cart.
- [Metaobject creator](https://www.smootify.io/add-ons/metaobject-creator) (Server plan): Webflow forms that create Shopify metaobjects, with no manual entry in the admin.
- [Customer metafield editor](https://www.smootify.io/add-ons/customer-metafield-editor) (Server plan): Let customers edit their own profile fields, stored as Shopify metafields.
- [Newsletter subscription](https://www.smootify.io/add-ons/newsletter-subscription) (Server plan): A newsletter form designed in Webflow that subscribes the customer in Shopify.
- [Product file upload](https://www.smootify.io/add-ons/product-file-upload) (Server plan): Shopify’s own file upload on the product, designed in Webflow, with the file managed from your admin.
- [Dynamic properties](https://www.smootify.io/add-ons/dynamic-properties) (every plan, Free included): Show or hide add-to-cart inputs from Shopify metaobjects, when conditional visibility is not enough.
- [Predictive search](https://www.smootify.io/add-ons/predictive-search) (every plan, Free included): Shopify’s own predictive search, free, designed pixel by pixel in Webflow.
- [Search and discovery](https://www.smootify.io/add-ons/search-discovery) (every plan, Free included): Filters, synonyms, boosts and recommendations from Shopify’s free Search and Discovery app.
- [Store locator](https://www.smootify.io/add-ons/store-locator) (every plan, Free included): Your stores, dealers and distributors on one Mapbox map.
- [Last viewed](https://www.smootify.io/add-ons/last-viewed) (every plan, Free included): The products a customer looked at recently, one click away.
- [Wishlist](https://www.smootify.io/add-ons/wishlist) (every plan, Free included): A wishlist you design yourself, saved for guests and account holders.
## FAQ (https://www.smootify.io/faq)
### About Smootify
**What is Smootify?**
A Shopify app that turns a site designed in Webflow into a Shopify store. You design every page in Webflow; Shopify keeps the products, the stock, the checkout, the payments and the orders. Smootify connects the two with elements and attributes you place on native Webflow elements: `product="title"` shows the title, `variant="price"` the price of the chosen variant, and the cart, search, filters and customer accounts work the same way. It has been installed more than 7,000 times on Webflow, and it was a Webflow Awards 2025 finalist as Tech Partner of the Year.
**How do I sell with Shopify on a Webflow site?**
Install the Smootify app on a Shopify store on the Basic plan or above, connect your Webflow site to it in the Smootify dashboard, and paste Smootify’s one-line script in the site’s head code. Then build the pages in Webflow with Smootify elements and attributes: products, prices and stock load live from Shopify, and the cart sends customers to Shopify’s checkout. Products can be copied into the Webflow CMS with Smootify Studio, or a page can show a product by its ID without the CMS. Every step is in the [quick start](https://www.smootify.io/docs/start/quick-start).
**Who is it for?**
Designers and agencies who build stores in Webflow and don’t want to give up their design to a theme, and merchants who want a store that looks like their brand and not like a template. It also suits stores that sell in ways a theme needs paid apps for: made to order with a live price, quotes for business customers, build-your-own bundles, bookings. See the [solutions](https://www.smootify.io/solutions/made-to-order).
**Why choose Smootify?**
Four reasons. The design is yours: no theme in the middle, every page and interaction built in Webflow. The store is Shopify: its checkout, Shop Pay, its admin and every sales channel. The apps come with it: 17 add-ons, from booking to configurators, quotes and file uploads, in one Shopify app billed on the store’s Shopify invoice instead of ten subscriptions. And you can hand the build to your agent: our Figma kit, markup contract, skills and MCP let an AI agent write the Webflow build while you review it.
**Why not Webflow Ecommerce?**
Webflow Ecommerce keeps everything in Webflow, which is simple until the store grows. With Smootify you keep designing in Webflow, and the store runs on Shopify: the Shopify checkout and payments, selling on marketplaces, social and in person with POS, the Shopify apps for shipping, accounting and email, and the mechanics Webflow Ecommerce doesn’t have, such as subscriptions, multiple currencies, bundles and B2B quotes. You need a Webflow site plan, not an Ecommerce plan. The prices side by side are on [Smootify vs Webflow Ecommerce](https://www.smootify.io/compare/webflow-ecommerce).
**Why not Shopify on its own, with a theme?**
A theme decides how your store can look, and every feature beyond it is another app: another subscription, another script on the page, another vendor. With Smootify the design is free in Webflow, and the mechanics come as add-ons of one app on the same Shopify invoice. What you give up: the Shopify theme editor, and the long tail of the app store for very niche needs, though most back-end apps work alongside Smootify. The cost of the apps you would replace is on [Smootify vs a theme and apps](https://www.smootify.io/compare/shopify-apps) and in the [savings calculator](https://www.smootify.io/tools/savings-calculator).
**Is Smootify the same as the Shopify Buy Button?**
No. The Buy Button embeds a Shopify product widget and cart into a page. With Smootify the whole Webflow site is the storefront: product and collection pages, the cart, search, filters and customer accounts are Webflow elements you design, reading live data from Shopify, with the Shopify checkout at the end.
**Do I need to know how to code?**
No. You add attributes to elements in the Designer, or let the [Webflow app](https://www.smootify.io/webflow-app) place whole components for you. If you do write code, there is a JavaScript API with events, filters and raw GraphQL for what the attributes don’t cover.
**Is Smootify 2.0 ready to use?**
2.0 is in public beta: the runtime, the components, the docs and the tools are all out, and we are collecting feedback before the general release. Sites on 1.x keep working as they are, and you choose when to move. The [2.0 notes](https://www.smootify.io/changelog/2-0) list what is new and what changes.
### The tools
**What do I install?**
Two apps, and a third if you want it. The [Shopify app](https://www.smootify.io/shopify-app) connects the store and carries the plan. The [Webflow app](https://www.smootify.io/webflow-app) runs inside the Designer. [Smootify Studio](https://www.smootify.io/desktop-app), the desktop app, syncs your products into the Webflow CMS and puts Claude next to your sites.
**What does the Webflow app do?**
It is the Smootify library inside the Designer: 171 components, every attribute and every condition. Select an element and it shows only what works there, with a check that the component sits inside the right Smootify element. It adds components as native Webflow components, and it shows the project’s plan, domain, Shopify store and setup. See the [Webflow app](https://www.smootify.io/webflow-app).
**What is Smootify Studio?**
A desktop app for Mac and Windows. It syncs products, collections, vendors and metafields from Shopify into the Webflow CMS, automatically every five minutes if you want, with a backup before every full sync. It sets up the CMS fields Webflow’s API can’t, and runs Claude with Smootify’s skills for the custom scripts of each site. See [Smootify Studio](https://www.smootify.io/desktop-app).
**Where do I start a design from?**
From the [Figma wireframe kit](https://www.smootify.io/figma/ui-kit), with every page and state a store needs, or from a [template](https://www.smootify.io/templates): three complete demo stores, each built around a way of selling, and the starter project with every component already in place.
**How does my AI agent build the store?**
It works from files we publish for agents. The [skills](https://www.smootify.io/skills) annotate the Figma file, build the Webflow pages from it and audit the result. The [Smootify MCP](https://www.smootify.io/developers/mcp) checks the markup against the [markup contract](https://www.smootify.io/developers/markup-contract) and reads your store settings. You connect Shopify, review and publish. See [how it works](https://www.smootify.io/figma-to-store).
**What is the Smootify MCP?**
An MCP server that your AI agent, such as Claude, Cursor or Codex, calls while it builds a Smootify site. It knows every element and attribute of Smootify 2.0, checks markup before it is pasted into Webflow, and audits the pages you publish. It is read only, works with your Smootify account, and is in beta. See [Smootify MCP](https://www.smootify.io/developers/mcp).
**What are the Shopify Flows?**
Ready-made automations for Shopify Flow, Shopify’s free automation app, that pair with Smootify elements: a notification when a quote arrives, booking confirmations, back in stock alerts and more. Import them in Flow from the [Flow library](https://www.smootify.io/flows).
### Plans and pricing
**How much does Smootify cost?**
Smootify has three plans. Free, to build and publish on a webflow.io address. Lite, $19 a month or $15 a month billed yearly, for a live store on your own domain. Server, $49 a month or $39 a month billed yearly, which adds the 8 add-ons that run on our server and the automatic CMS sync. The plan is billed on the store’s Shopify invoice. Your Shopify plan and your Webflow site plan are billed by Shopify and Webflow as usual. See [pricing](https://www.smootify.io/pricing).
**What is free in Smootify?**
Everything you need to build: the whole store on a webflow.io address, every component in the Webflow app, the free templates and the starter project, the 9 add-ons that work on every plan, the CMS sync with Smootify Studio, and community support on Discord. You pay when the store moves to its own domain. Free Shopify features stay free: Search and Discovery filters, predictive search and complementary products come with every plan, never as a paid add-on.
**Do I need a paid plan to build my store?**
No. Smootify is free on webflow.io staging, and you can build against a free Shopify development store from a Partner account. You pay us when the store goes live: Lite $19 or Server $49 a month, or $15 and $39 a month billed yearly. On the Webflow side, any workspace or site plan will do for building.
**What needs the Server plan?**
The 8 add-ons that run on our server: [booking](https://www.smootify.io/add-ons/booking), [product configurator](https://www.smootify.io/add-ons/product-configurator), [name your price](https://www.smootify.io/add-ons/name-your-price), [draft orders](https://www.smootify.io/add-ons/draft-orders), [metaobject creator](https://www.smootify.io/add-ons/metaobject-creator), [customer metafield editor](https://www.smootify.io/add-ons/customer-metafield-editor), [newsletter subscription](https://www.smootify.io/add-ons/newsletter-subscription), [product file upload](https://www.smootify.io/add-ons/product-file-upload). They write to Shopify for you: draft orders, metaobjects, customer metafields, files, newsletter sign-ups. Automatic CMS sync from our server is also a Server feature; on other plans Smootify Studio, the free desktop app, does the sync. The other 9 add-ons work on every plan, Free included.
**How is Smootify billed?**
Through Shopify. Smootify is a Shopify app, so its plan appears on the store’s monthly Shopify invoice next to any other app. One app on the bill instead of ten, and whoever pays the Shopify bill pays Smootify: an agency has nothing to rebill.
**Do I pay for add-ons separately?**
No. Add-ons come with the plan. Add-ons marked Server need an active Server plan.
**Which Shopify plan do I need?**
Basic or above. Shopify Starter can’t use the Headless sales channel, so it doesn’t work.
**Which Webflow plan do I need?**
To publish, a site plan sized for your CMS items and bandwidth. You don’t need a Webflow Ecommerce plan: Shopify runs the store.
**What happens to my 1.x store?**
It keeps working, and the [1.x video tutorials](https://www.smootify.io/guides/v1) stay online. 2.0 is in public beta, and moving to it is your choice. To move, follow [Move a site from 1.x](https://www.smootify.io/docs/start/migrate), or hand the job to your agent with the [migration guide](https://www.smootify.io/guides/move-from-1x-with-an-agent). The [2.0 notes](https://www.smootify.io/changelog/2-0) list what changes.
### Shopify Plus
**Do I need Shopify Plus?**
No. Smootify works on Basic, Grow and Advanced. On a theme, a custom price at checkout needs Plus. With Smootify, a calculated or chosen price and B2B quotes go through a draft order on any of those plans. Bundles merged at checkout work on every Smootify plan, Free included.
**Can I charge a custom price on Shopify without Plus?**
Yes, through a draft order. On the Server plan, the [product configurator](https://www.smootify.io/add-ons/product-configurator) and [name your price](https://www.smootify.io/add-ons/name-your-price) add-ons send a calculated or chosen price to Shopify as a draft order with a price override, on Basic, Grow or Advanced, without creating a variant for each price. The customer pays on the draft order’s invoice page instead of the regular checkout, so discounts, Shop Pay and accelerated checkouts behave differently there, and one line with a custom price takes the whole cart through the draft order. Changing a price inside the regular checkout is still a Plus feature.
**What does Plus have that Smootify doesn’t?**
Three things, and we don’t replace them. A custom price inside the real checkout is still a Plus operation: with Smootify, a chosen or calculated price goes through a draft order invoice, where discounts and Shop Pay work differently. Plus-only admin tools for automation and launches stay with Plus. And Plus merchants get a Shopify account team; our support comes from our team and the community. The full side-by-side is on [Smootify vs Shopify Plus](https://www.smootify.io/compare/shopify-plus).
### Subscriptions and boxes
**Can a build-your-own box be a subscription?**
No, and it isn’t a Smootify limit. Shopify’s Cart Transform API refuses to merge lines when any line has a selling plan. The Magic box works by merging the chosen products into one line with its own title and image, so a box and a subscription can’t be combined. Dropping the merge would lose the custom title and image, which are the reason the Magic box exists next to Shopify’s native bundles.
**So how do subscription boxes work?**
The usual pattern has two products. The build-your-own box is a one-time purchase made with the Magic box. Curated boxes are simple products with a selling plan, so customers can subscribe to them. Most subscription boxes you know work this way. See [subscription boxes](https://www.smootify.io/solutions/subscription-boxes).
**Is there a workaround for a box on subscription?**
One, with a hard limit. You sell an “empty box” as a simple product, which can have a selling plan. The customer’s choices travel as line properties, and Shopify Flow decrements the real inventory on each subscription order. The limit: the box has one price, so every product inside must cost the same. It works for a six-slot box of interchangeable items. It doesn’t work if a $4 product and a $12 product can go in the same box.
**Do subscriptions need an app?**
Yes. A Shopify subscription app creates the selling plans. Smootify shows them, sells them and lets customers pause, cancel or reactivate from their account in Webflow. A product without a selling plan shows no subscription options, which looks like broken markup but is missing configuration.
### Quotes and bookings
**How do quotes work?**
The cart becomes a quote request. The customer sends it, and a draft order with the lines, quantities and variants appears in Shopify. You set your price and send the invoice. The customer pays at the normal Shopify checkout. It needs the Server plan and the [draft orders](https://www.smootify.io/add-ons/draft-orders) add-on turned on.
**Can the same catalog sell to consumers and quote to businesses?**
Yes. Use conditional visibility on the customer: a signed-in customer with a company sees “Request a quote”, everyone else sees “Add to cart”. See [B2B and wholesale](https://www.smootify.io/solutions/b2b-wholesale).
**My quote form stopped working after I copied it.**
The email field must be named `email`. Webflow renames fields when you copy a form, and that is where it breaks most often. Also change the success message to “quote sent”, so customers don’t think they placed an order.
**Can customers book appointments or delivery slots?**
Yes, with the [Booking](https://www.smootify.io/add-ons/booking) add-on, new in 2.0 and on the Server plan. One calendar designed in Webflow books appointments without payment, paid appointments sold as a product, or pickup and delivery windows chosen in the cart. You set the hours and the places on the product in Shopify; Smootify counts them on its server, so the last slot goes to one customer, and two Shopify Flow templates confirm the bookings on every Shopify plan, Basic included.
### Building
**Do I have to use your libraries or a class system?**
No. Smootify runs on elements and attributes, so any class system works. A few combo classes must keep their exact name, such as `is-disabled` on variant swatches. The libraries and the [Figma kit](https://www.smootify.io/figma/ui-kit) are starting points, not requirements.
**Do I have to use the Webflow CMS?**
No. For a single product, or a few products that each need a different design, put the product ID on the elements directly.
**How many products can I have?**
As many as Shopify allows. What you import into the Webflow CMS is capped by your site plan’s CMS item limit, and that limit counts every collection together.
**Can products sync from Shopify to the Webflow CMS automatically?**
Yes. Smootify Studio, the free desktop app, copies products, collections, vendors and metafields into the Webflow CMS, every five minutes if you leave automatic sync on, on every plan. The Server plan runs the sync on our server instead, with nothing running on your side. Variants and stock are not copied: they load live from Shopify on the page.
**Does Smootify support multiple currencies and languages?**
Yes, through Shopify Markets. A country switcher designed in Webflow changes the currency, prices and availability to the visitor’s market, and a market suggestion can propose the right country to a new visitor. Languages come from Webflow Localization or Weglot. The markets must exist in Shopify first. See [markets, currencies and languages](https://www.smootify.io/docs/features/markets).
**Can I use any Shopify app?**
Back-end apps, yes: the theme doesn’t affect them. Front-end apps only if they support the Headless sales channel. See [integrations](https://www.smootify.io/docs/features/theme-apps).
**Can I design the checkout in Webflow?**
No. The checkout belongs to Shopify. You can change the logo, colors and a few settings in the Shopify admin.
**What if Smootify doesn’t do what I need?**
There is a JavaScript API underneath, with raw GraphQL, cart methods, events and hooks that replace default behavior. See [API and events](https://www.smootify.io/docs/developers/javascript-api).
### Help
**I have a paid plan but still see the Smootify banner.**
Check the Webflow domain in the Smootify dashboard. It must be in the form `www.domain.tld`.
**Where do I get help?**
On [Discord](https://discord.com/invite/MKeQqEZmjd). The team is in Italy and is usually there Monday to Friday, 10:00 to 17:00 CET. Written answers are in the [docs](https://www.smootify.io/docs); video walkthroughs are in the [guides](https://www.smootify.io/guides).
---
# Quick start
> The whole path from a Shopify store to a published Webflow store with Smootify 2.0, one step at a time, with a link to the page that covers each step.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/quick-start
URL: https://www.smootify.io/docs/start/quick-start
Updated: 2026-10-12
Smootify connects a Webflow site to a Shopify store. Webflow holds the design and the pages. Shopify holds the products, the stock, the customers and the checkout. This page goes through the whole set-up in order. Each step is short and links to the page with the details.
> [!NOTE]
> Smootify 2.0 is in public beta: the `latest` channel of the loader loads the current beta release, and moves to the stable release with the same line. See [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
You need three accounts:
- **Shopify**, on the Basic plan or higher. The Starter plan cannot use Shopify's Headless channel.
- **Webflow**, with a site plan that includes the CMS. You do not need a Webflow Ecommerce plan.
- **Smootify**. The Free plan works on your `.webflow.io` address. A custom domain needs the Lite or Server plan.
## 1. Set up the Shopify store
Create the store, or a development store if you build it for a client. Then install the **Smootify** app from the Shopify App Store and open it from your Shopify admin. The app takes you to the Smootify dashboard, where you log in or create an account.
Before you go live, you also create a storefront in Shopify’s **Headless** channel and give its token to Smootify. Orders placed without it do not send the order confirmation email.
Details: [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
## 2. Create the Webflow site
Start from a blank site, or add Smootify to a site you already have. Do not use a Webflow Ecommerce template: Shopify sells, Webflow shows.
Details: [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project) and [Starter and templates](https://www.smootify.io/docs/start/templates).
## 3. Connect the site in the Smootify dashboard
In the dashboard, connect your Webflow site to the project: you authorize Smootify in Webflow and pick the site. The project now links one Shopify store and one Webflow site. Add your custom domain to the project when you have one. The `.webflow.io` address works without it.
Details: [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
## 4. Create the CMS collections and sync them
Create three CMS collections, **Products**, **Collections** and **Vendors**, with the fields Smootify fills. Smootify Studio, the desktop app, can create them for you. Then import your products from Shopify, with Smootify Studio (every plan) or the CMS sync on Smootify’s server (Server plan). The **Shopify ID** field is the one that matters most: it connects each page to its product.
Details: [CMS collections and sync](https://www.smootify.io/docs/start/cms).
**A store with one or a few products** can skip the CMS. Give each `smootify-product` the product’s ID as a fixed value, such as `data-id="1234567890"`: the number at the end of the product’s address in the Shopify admin. Each product then has a static page of its own, designed as you like.
## 5. Install the script
Paste one line in Webflow, under **Site settings → Custom code → Head code**, and publish:
```html
```
Details, options and release channels: [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
## 6. Build the pages
Every Smootify part is a Webflow element with a custom attribute, or a **Custom element** (Add panel) with its tag set in the element settings. On the template page of **Products**, one `smootify-product` element with its `data-id` bound to the **Shopify ID** field loads the live product. Everything that shows the product goes inside it:
```html
{Name}
{Price Formatted}
```
Then build the rest, in any order:
- [The product page](https://www.smootify.io/docs/build/product-page), with options and the add-to-cart form;
- [The collection page](https://www.smootify.io/docs/build/collection-page) and [product cards](https://www.smootify.io/docs/build/product-cards);
- [The cart](https://www.smootify.io/docs/build/cart) and [the navbar](https://www.smootify.io/docs/build/navbar);
- [Filters and sorting](https://www.smootify.io/docs/build/filters) and [search](https://www.smootify.io/docs/build/predictive-search);
- [Customer accounts](https://www.smootify.io/docs/build/account), if the store has them.
## 7. Check, then publish
Publish to your `.webflow.io` address first. There, press Alt+Shift+D (Option+Shift+D on a Mac) to show Smootify’s debugger. Its panel lists what does not render and why: see [The debugger](https://www.smootify.io/docs/start/debugger).
When the store works there, go through [Before you publish](https://www.smootify.io/docs/start/publish): the Headless token, the custom domain in the Smootify dashboard, a test order. Then publish to your custom domain.
## When Smootify is not a fit
- **A very large catalog.** Synced products count against the CMS items of your Webflow site plan, for all collections together. A catalog larger than that limit cannot have one CMS page per product.
- **A store that depends on many online store apps.** Apps that draw on the pages of a Shopify theme, or need Shopify’s app proxy (`/apps/…`), do not run on a Webflow site. If the store relies on many of them, a Shopify theme fits it better. See [Shopify apps on a Webflow site](https://www.smootify.io/docs/features/theme-apps).
## Get help
Ask on the Smootify Discord, where the team and other builders answer. Projects with a paid plan also get premium support: open a ticket from the chat in the bottom-right corner of the Smootify dashboard. See [Contact](https://www.smootify.io/contact).
## Where people get stuck
> [!STUCK]
> - **The banner "Project not Connected".** The Webflow site is not connected in the Smootify dashboard, or it is a copy of the connected site. Connect this site.
> - **The banner "To connect your custom domain … you need an active Smootify Plan".** The project has no paid plan, or the domain is missing from the project settings.
> - **Products are missing after the sync.** They are not published to the Headless channel, or not available in your markets.
> - **Customers get no order confirmation email.** The project still uses the test token the Smootify app created. Replace it with the token of a Headless storefront.
> - **Links in the cart or the search results open a 404 page.** The URL of the Products collection is not `products`. Set `productsBase` to it, as [CMS collections and sync](https://www.smootify.io/docs/start/cms) explains.
---
# Install Smootify 2.0
> The one line that loads Smootify 2.0 on every page of your Webflow site, the release channels, the stylesheet, in-app browsers on iPhone and the options.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/install
Updated: 2026-10-12
Smootify 2.0 is one line of code in the head of your Webflow site. It loads Smootify on every page, with its stylesheet. This page says where to paste it, which release it loads, and how to set Smootify’s options.
> [!NOTE]
> Before you start, your Webflow site must be connected to a project in the Smootify dashboard: see [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project). The line holds no store details: Smootify finds the project from the Webflow site itself.
## 1. Paste the loader in the head code
In Webflow, open **Site settings → Custom code** and paste this line in **Head code**:
```html
```
Then save, and publish the site.
Paste it in the site’s head code, not in the settings of one page, so that every page loads Smootify. The cart and its count usually sit in the navbar of every page.
## 2. Choose the release (`data-channel`)
`data-channel` says which release of Smootify the site loads:
| Value | What the site gets |
|---|---|
| `latest` | the current stable release, updated as new releases come out; the loader uses it without `data-channel` |
| `beta` | the next release, before it is stable: to try it, for example on a copy of the site |
| a version, such as `2.0.3` | that release only, until you change the value; it does not get later fixes |
> [!NOTE]
> Smootify 2.0 is in public beta. For the whole beta, `latest` and `beta` load the same release: every beta release goes to both channels. Keep `latest`: when 2.0 is stable your site moves to it with the same line, and from then on `beta` is the next release again.
## 3. Keep the stylesheet (`data-css`)
`data-css="true"` adds Smootify’s stylesheet. It holds the rules that show and hide the elements with a condition, such as `condition="out-of-stock"`, and the loading placeholders set with `skeleton`. Keep it, unless you link the stylesheet yourself (see “Without the loader” below).
## 4. In-app browsers on iPhone
A visitor who opens a link from Instagram, Facebook or TikTok on an iPhone sees your site in the app’s own browser, not in Safari. The loader recognises these browsers and gives them `compat.js`, a build of Smootify in a single file. You do not add `compat.js` yourself: the loader chooses it.
`compat.js` behaves like the normal build, with three differences:
- 3D models show only their poster image;
- sliders built with `dynamic-swiper` do not slide;
- the debugger does not load.
## 5. Set Smootify’s options
Options change how Smootify behaves: the URLs of your template pages, the key for customer accounts, analytics and more. The builder asks what your site needs and writes the script:
_The options builder of this page writes the `SmootifyUserOptions` script from a few questions: open the page in a browser to use it._
Or write it yourself, in a script **before** the loader, in the same head code:
```html
```
This example is for a site whose Products collection has the URL `product`, with customer accounts.
- Smootify reads the options once, when it starts. A script after the loader can come too late.
- Use **one** options script. A second one replaces the first.
- You only write the options you change. The others keep their default.
The options set-up pages need most often:
- `productsBase`, `collectionsBase`, `vendorsBase`: the URL of each template page, when a collection’s URL is not `products`, `collections` or `vendors`. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
- `newCustomerAccountsPublicKey`: needed for login and the account pages. See [Customer accounts](https://www.smootify.io/docs/build/account).
- `analytics`: the tracking pixels Smootify loads with the visitor’s consent. See [Analytics and pixels](https://www.smootify.io/docs/features/analytics).
Every option, with its default, is in the [options reference](https://www.smootify.io/docs/reference/options). For your own scripts that use Smootify, see [Run your scripts with Smootify](https://www.smootify.io/docs/developers/scripts).
## Without the loader
The loader is the way to install 2.0. If you cannot use it, these are the direct files of the stable release:
```html
```
Replace `latest` with a version, such as `2.0.3`, to fix one release. With the direct files, every browser gets the normal build: in-app browsers on iPhone do not get `compat.js`.
## Check that it runs
Publish to your `.webflow.io` address and open it. Press Alt+Shift+D (Option+Shift+D on a Mac), or on a phone add `?smootify-debug` to the address. Smootify’s debugger button, marked **Info**, appears with its panel open. The panel lists the Smootify elements on the page and what does not render: see [The debugger](https://www.smootify.io/docs/start/debugger).
On the Free plan, a “Made with Smootify” badge also shows on `.webflow.io`. A custom domain needs a paid plan and the domain in the project settings: see [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
## Where people get stuck
> [!STUCK]
> - **The banner "Project not Connected".** This Webflow site is not connected in the Smootify dashboard, or it is a copy of the connected site: a copy is a new site for Smootify.
> - **Every stock and sale message shows at once.** The stylesheet is missing. Add `data-css="true"` to the loader.
> - **An option does nothing.** The options script comes after the loader, or a second options script replaces it. Keep one, before the loader.
> - **A change made in the Smootify dashboard does not show.** Publish the site again, then reload the page twice. The browser keeps the previous project settings for one visit.
> - **The debugger button does not appear.** It is off until you press `kbd:Alt+Shift+D`, and it exists on `.webflow.io` only: not on your custom domain, and not in in-app browsers.
## Coming from 1.x
In 1.x, two lines loaded the script and the stylesheet from `assets/latest/`. In 2.0, the loader replaces both. Keep your options script where it is, before the loader. The full procedure is in [Move a site from 1.x](https://www.smootify.io/docs/start/migrate).
---
# Starter and templates
> The Smootify starter project and the demo stores built on 2.0, starting from a blank site, and what to do with a site made from a 1.x template.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/templates/smootify-starter-project
URL: https://www.smootify.io/docs/start/templates
Updated: 2026-10-01
A starter project saves you the set-up: the CMS collections, the script and the common pages are already in place. This page says which starting points exist for Smootify 2.0 and what to do with a site made from a 1.x template. Every template, with its demo and its Figma file, is on the [Templates page](https://www.smootify.io/templates).
## The starter project and the demo stores
The Smootify starter project is built on 2.0, with every page already wired to Smootify. The demo stores are complete stores, also on 2.0, each built around a way of selling. Pick one on the [Templates page](https://www.smootify.io/templates), clone it in Webflow, then connect it and sync your products:
1. [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project): a cloned site is a new site for Smootify, so connect it;
2. [Sync the CMS](https://www.smootify.io/docs/start/cms) with your own products;
3. publish, after the [checks before you publish](https://www.smootify.io/docs/start/publish).
## Starting from a blank site
To build the store from scratch, start from a blank Webflow site:
1. [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project);
2. [Create the CMS collections and sync them](https://www.smootify.io/docs/start/cms);
3. [Install Smootify 2.0](https://www.smootify.io/docs/start/install);
4. build the pages, starting from [the product page](https://www.smootify.io/docs/build/product-page).
## Sites made from a 1.x template
The Smootify starter and the templates published for 1.x, listed under v1 on the [Templates page](https://www.smootify.io/templates), load the 1.x script. A site made from one of them keeps working on 1.x as long as it keeps that script. Nothing changes for it until you switch it.
To move such a site to 2.0, follow [Move a site from 1.x](https://www.smootify.io/docs/start/migrate). Do it on your `.webflow.io` address first, where the debugger points out what 2.0 reads differently. [What changes for your site](https://www.smootify.io/docs/start/changes) lists every difference a designer can see.
## Where people get stuck
These happen after switching a 1.x site to 2.0. Each is covered in [Move a site from 1.x](https://www.smootify.io/docs/start/migrate).
> [!STUCK]
> - **Links to products open a 404 page.** 2.0 builds product links as `/products/…`. If the site's product pages start with `/product/`, set `productsBase: "product"`, `collectionsBase: "collection"` and `vendorsBase: "vendor"` in the options.
> - **The login and register forms disappear.** 2.0 supports only Shopify's new customer accounts. Turn them on in Shopify and set `newCustomerAccountsPublicKey`: see [Customer accounts](https://www.smootify.io/docs/build/account).
> - **Nothing shimmers while the page loads.** 2.0 has no automatic placeholders. Add a `skeleton` attribute where you want one.
> - **Variant values such as the SKU keep their placeholder text.** 2.0 reads `variant="…"` only. Rename every `variation` attribute to `variant`, with the same value.
---
# Smootify Studio
> Smootify Studio, the desktop app for Mac and Windows - the tabs of each site (Setup, Build, Sync, Iterate, Check), the Claude of each site and what it knows, and how review notes reach Claude.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/studio
Updated: 2026-10-09
[Smootify Studio](https://www.smootify.io/desktop-app) is the desktop app for Mac and Windows. Every Smootify site you work on sits in its sidebar, with its state: synced, never synced, collections to choose, last sync failed. Each site has five tabs, and a Claude of its own.
Claude in Studio is your own Claude Code, on your Claude account. Studio checks that it is installed and signed in, and never reads your sign-in. Syncing works without it.
## The tabs of a site
- **Setup.** Chooses the CMS collections for products, collections and vendors, and finishes the field settings Webflow’s API cannot change: prices as decimals, Shopify ID and image URL as single lines, the SEO description as multi-line. Fix them automatically, with Claude in Chrome, or by hand from a checklist. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
- **Build.** The site’s custom scripts, one folder each. Claude writes them with the custom-script skill, the dev server runs your latest code on the real site, and Build makes the file the site loads. See [Custom scripts](https://www.smootify.io/docs/developers/scripts).
- **Sync.** Brings products, collections and vendors from Shopify into the Webflow CMS, by hand or every five minutes from the tray, with a log of each item it writes.
- **Iterate.** Review notes on the published site, answered by Claude (below).
- **Check.** Checks the collections and their settings, and runs a site audit with Claude, saved as a dated report in the site’s folder.
## The Claude of each site
The **Claude** button at the top of every site, in every tab, opens Claude Code in the site’s folder, with no task set. The terminal opens in the drawer on the right, next to the audit and the Figma steps, and the same button shows and hides it. Ask it about the markup, fix a page that does not work, change the site through Webflow’s MCP, or work through what the audit found.
Before it opens, Studio writes the site’s brief, `CLAUDE.md`, in the site’s folder: the Webflow site and its Designer link, the addresses, the Shopify store and plan, the CMS collections, and what each Smootify skill does. Claude starts from it, so you do not explain the site from scratch. It tells you what it will change in Webflow before changing it, and leaves publishing to you.
The site’s folder is in `Smootify Studio/` in your home folder.
## Review notes with Iterate
Iterate is a feedback loop on how Smootify is built into the site.
1. **Open the site.** The published site opens in Chrome, Edge or Brave, in a profile of its own for each site, so your Webflow login is not touched. A bar at the bottom holds the notes. Desktop, Tablet (768) and Mobile (390) open the window at that width.
2. **Add a note** (Option+Shift+F on a Mac, Alt+Shift+F on Windows). Click an element, drag over an area, or choose the whole page; write what is wrong, and choose a type and a severity. Save it with Cmd+Enter (Ctrl+Enter on Windows). Studio saves the note in the site’s folder, under `notes/`, with a screenshot of the element and of the window, the Smootify elements around it, the Smootify script the page loads, the page’s errors and the requests to Smootify or Shopify that failed just before.
3. **Send to Claude.** The site’s Claude reads the notes, fixes the site in Webflow with the [Iterate skill](https://www.smootify.io/skills/iterate), and answers inside each note. It never publishes.
4. **Publish, and look again.** A fixed note reads “Fixed · publish to see it” until you publish. You can publish from Studio, on the address you are reviewing: it asks first, because everything changed in Webflow goes online. After a publish, Studio takes a new screenshot of every fix. Mark each one **Verified**, or **Reopen** it with the reason.
Every answer names the cause and how sure Claude is. When the cause is Smootify itself, the note shows it as Smootify’s side, and **Send to Smootify** opens the support chat with the report ready to paste. When Claude is not sure the site’s configuration is ruled out, Studio suggests reopening the note first.
On the page, numbered pins mark the elements with notes: orange while a note is open, black once Claude has answered. Tap one to read the answer, and reply, close, reopen or verify it there; ‹ 2 of 7 › in the bar moves from note to note. Studio tells you when Claude answers, in the app, on the site and with a system notification. **Export** makes a printable HTML report of the notes, in the site’s `reports/` folder.
The notes bar runs apart from the site’s own scripts, so the site cannot see it, and Claude starts only when you click.
> [!STUCK]
> - **The Claude button does nothing.** Claude Code is not installed or not signed in: open the Claude Code panel of Studio and follow its checks.
> - **Claude does not know the site.** Open it from the site's Claude button, not from a terminal elsewhere: the button writes the site's brief first.
> - **A fixed note still looks wrong.** The fix is in the Designer and the site is not published yet. Publish, then use **Look at it again**.
---
# Set up the Shopify store
> The Shopify side of a Smootify store. Covers the store, the Smootify app, the Headless channel and its token, the sales channel your products must be on, markets and customer accounts.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/quick-start/shopify-store
URL: https://www.smootify.io/docs/start/shopify-store
Updated: 2026-10-01
Shopify is where your products, stock, orders and customers live, and where the checkout happens. Webflow only shows them. This page sets up the Shopify side: the store, the Smootify app, and the Headless channel that gives your Webflow site access to the products.
> [!NOTE]
> The store needs the Shopify Basic plan or higher. The Starter plan cannot use the Headless channel.
## 1. Create the store
There are two kinds of store:
- **A store for your own business.** Sign up on shopify.com and follow the set-up. Shopify gives a free trial before you choose a plan.
- **A development store**, if you build the store for a client. You create it from a free Shopify Partner account. It has no time limit, but it cannot take real payments. When the store is ready, you transfer it to the client: see [Hand a project to a client](https://www.smootify.io/docs/start/hand-off).
Add a few products with their variants and images early. You need them to see your pages come alive.
## 2. Install the Smootify app
1. Install **Smootify** from the Shopify App Store, on the store you just created.
2. Open the app from your Shopify admin. It takes you to the Smootify dashboard.
3. Log in, or create a Smootify account.
4. Connect your Webflow site to the project: see [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
When it connects, the app creates a Storefront token for the project, so your Webflow site can read the products right away. The next step replaces it.
## 3. Create a Headless storefront and use its token
Shopify’s **Headless** channel gives a Webflow site its own storefront in Shopify. Its token is the one a live store uses:
| | The Smootify app's token | The Headless storefront's public token |
|---|---|---|
| Created | by the Smootify app, when it connects | by you, in a Headless storefront |
| For | building and testing: the site reads the products right away | the live store |
| Orders | get no order confirmation email | customers get the order confirmation email, and the orders show the storefront's name as their sales channel, apart from other channels such as the Online Store or social media |
1. Install **Headless** from the Shopify App Store, on the same store.
2. Create a storefront. Its name is the sales channel name you will see on your orders.
3. Open the storefront’s **Storefront API** settings and turn on all the permissions. Smootify needs all of them, the one for stock levels included.
4. Copy the **public** access token.
5. In the Smootify dashboard, open the project’s settings and paste it as the Storefront token, in place of the test token.
You can do this at the start or just before launch, but always before real customers order.
> [!WARNING]
> Copy the public access token, not the private one. The dashboard refuses a private token, which is never meant for a website.
## 4. Publish your products to the Headless channel
A product reaches your Webflow site only if it is published to the Headless storefront’s sales channel. In Shopify, add that channel to every product and collection you sell, next to the Online Store if you keep it.
What it changes:
- **Published to the Headless storefront's sales channel:** the product loads on the site, and the CMS sync imports it.
- **Not published there:** the product does not load on the site, and the CMS sync does not import it. The import finds collections through their products, so a collection without published products is not imported either.
## 5. Markets
Shopify Markets decides where you sell, in which currency and at which price. Smootify shows each visitor the prices and the availability of their market.
- A product must be available in the markets you sell in. A product that is not sold in the visitor’s market leaves its product block empty on the page.
- The CMS sync reads your products in the market of each Webflow language, so a product missing from a market is also missing from the CMS.
Country switchers, languages and currencies on the site: see [Markets, currencies and languages](https://www.smootify.io/docs/features/markets).
## 6. Customer accounts
This step is only for stores where customers log in on the Webflow site. Shopify has deprecated its classic customer accounts and will remove them soon, so Smootify 2.0 works with the **new customer accounts**.
1. In Shopify, go to **Settings → Customer accounts** and use the new customer accounts.
2. The Headless storefront gives the public key of the Customer Account API.
3. That key goes in Smootify’s options as `newCustomerAccountsPublicKey`.
The whole set-up, with the login and account pages: [Customer accounts](https://www.smootify.io/docs/build/account).
## 7. Metafields
Metafields add your own data to products, such as a material or care instructions. To show a metafield on the site, its definition in Shopify needs **Storefront access** turned on: see [Metafields on the page](https://www.smootify.io/docs/build/metafields). To copy a metafield into a CMS field, see [CMS collections and sync](https://www.smootify.io/docs/start/cms).
## Where people get stuck
> [!STUCK]
> - **Customers get no order confirmation email.** The project still uses the test token the Smootify app created. Paste the public token of a Headless storefront in the project's settings.
> - **The dashboard refuses the token.** It is the private access token. Copy the public one.
> - **A product never shows on the site or in the CMS.** It is not published to the Headless storefront's sales channel, or not available in your markets.
> - **A collection is missing from the CMS.** It has no products published to the Headless channel.
> - **Login does not work.** Most often the **Callback URIs** or the **JavaScript origins** in the Customer Account API settings of the Headless channel do not match the site: each address you use (your `.webflow.io` address and your own domain) must be in both, written as `https://www.example.com`. Also check that `newCustomerAccountsPublicKey` is set and that the store uses the new customer accounts. See [Customer accounts](https://www.smootify.io/docs/build/account).
> - **A metafield stays empty.** Its definition needs Storefront access.
---
# Connect the Webflow project
> Prepare the Webflow site, connect it to a project in the Smootify dashboard, and add the domains the Smootify licence covers.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/quick-start/connect-webflow-project
URL: https://www.smootify.io/docs/start/webflow-project
Updated: 2026-10-01
A Smootify project links one Webflow site to one Shopify store. This page prepares the Webflow site, connects it in the Smootify dashboard, and explains which addresses the licence covers: your `.webflow.io` address and your custom domains.
> [!NOTE]
> Set up the Shopify store and install the Smootify app first: see [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
## 1. Prepare the Webflow site
- **A new site.** Create a blank site in Webflow. Do not start from a Webflow Ecommerce template. You do not need Webflow Ecommerce at all: Shopify sells, Webflow shows. For a ready-made starting point, see [Starter and templates](https://www.smootify.io/docs/start/templates).
- **An existing site.** It works as it is. You add the CMS collections and the script to it.
The site needs the Webflow CMS for the product pages. Products, collections and vendors are all CMS items, and your Webflow site plan limits how many items the site can have. To publish on a custom domain, the site also needs a Webflow site plan.
## 2. Connect the site to a project
1. Open the Smootify dashboard from the Smootify app in your Shopify admin.
2. Connect a Webflow site: Webflow asks you to authorize Smootify, and you pick the site.
You can also start from the other side: install the Smootify app from the Webflow Marketplace, open the dashboard from it, then install the Smootify app on your Shopify store.
The project now knows the store and the site. Smootify finds the project from the Webflow site itself, so the script you install holds no store details. The same access lets the CMS sync write your products into the site’s CMS.
## 3. Add your domain
In the project’s settings in the Smootify dashboard, add the custom domain the site is published on, such as `www.example.com`.
- **Your `.webflow.io` address always works.** You do not need to add it.
- **`www` or not.** For the site, `example.com` and `www.example.com` count as the same domain. The server add-ons compare the exact address, so write the domain the way the site is published, `www` included when it has one.
- **Weglot.** Language sub-domains, such as `fr.example.com`, are covered by the main domain.
Where the domain points, and how the checkout gets its own sub-domain: see [Domains and checkout](https://www.smootify.io/docs/start/domains).
## 4. What each plan covers
| Plan | Where Smootify runs | Also includes |
|---|---|---|
| Free | your `.webflow.io` address, with a "Made with Smootify" badge | the CMS sync with Smootify Studio, the desktop app |
| Lite | also your custom domain | every client add-on |
| Server | also your custom domain | the CMS sync on Smootify's server, the server add-ons |
- On `.webflow.io`, every client add-on works for testing. Magic Box is the exception: on every plan and every address, it must be turned on in the dashboard first.
- The server add-ons, such as file uploads or the product configurator, need the Server plan and must be activated for the project. On the live site they run only on the domain in the project settings. The list is in [server add-ons](https://www.smootify.io/docs/reference/elements/server-extensions).
When the site is not connected, or a custom domain is not covered, the page shows a banner at the bottom left and Smootify does not load the store:
- **"Project not Connected"**: this Webflow site is not connected to a project, or it is a copy of the connected site. Smootify does not load the store. Connect this site in the dashboard.
- **"To connect your custom domain "www.example.com" you need an active Smootify Plan"**: on a custom domain, the project has no paid plan, or the domain in the banner is missing from the project settings, or written differently. Smootify does not load the store. Pick a paid plan, and write the domain in the project settings exactly as the site is published.
## 5. Install the script
Paste the loader in the site’s head code and publish: see [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
## Keep the connection working
- **Webflow access expires.** Webflow can revoke Smootify’s access, for example when the site moves to another workspace. The CMS sync then stops. Authorize the site again from the dashboard, and pick the same site in Webflow.
- **A copy of the site.** A duplicated Webflow site is a new site for Smootify. It shows “Project not Connected” until you connect it to a project.
- **Someone else’s project.** The dashboard can share a project with another account, or transfer it: see [Hand a project to a client](https://www.smootify.io/docs/start/hand-off).
## Where people get stuck
> [!STUCK]
> - **The banner "Project not Connected".** This Webflow site is not connected to a project, or it is a copy of the connected site. Connect this site in the dashboard.
> - **The banner "To connect your custom domain … you need an active Smootify Plan".** The project has no paid plan, or the domain in the banner is missing from the project settings.
> - **A server add-on works on `.webflow.io` but not on the live site.** The domain in the project settings differs from the site's address, often by `www`. Write it exactly as the site is published.
> - **The CMS sync says Smootify lost access to Webflow.** The authorization expired or was revoked. Authorize the same site again.
> - **A change in the project settings does not show.** Publish the site again, then reload the page twice. The browser keeps the previous project settings for one visit.
---
# CMS collections and sync
> The Products, Collections and Vendors collections, what the sync copies from Shopify and why variants and stock stay out, when to sync, metafields copied into CMS fields, the import and the automatic sync, and the URLs of the template pages.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/quick-start/create-cms-collections
URL: https://www.smootify.io/docs/start/cms
Updated: 2026-10-07
Webflow builds one page per product from the CMS. Smootify works with three CMS collections, **Products**, **Collections** and **Vendors**, and a sync that fills them from Shopify. This page lists the fields to create, explains how the import and the sync work, and how the URLs of the template pages must match.
> [!NOTE]
> Every plan can create the collections and import with **Smootify Studio**, the Smootify desktop app. The automatic sync on Smootify's server needs the Server plan. Products, collections and vendors all count as CMS items in your Webflow site plan.
## What the CMS is for
The CMS holds what Webflow needs to build pages and to filter or sort Collection lists: the name, the slug, the description, a price for sorting, the collections. It also gives search engines text in the page before Smootify loads.
The live data does not come from the CMS. Prices, stock, variants, images and metafields load from Shopify each time a visitor opens a page, so the page always shows what Shopify has now.
### What the sync copies, and what stays in Shopify
The sync writes **one CMS item per product**, one per collection and one per vendor, with the fields listed below and the metafields you choose to copy (step 5). It does not copy:
- the **variants**, their options, prices, SKUs and images;
- the **stock**;
- selling plans, and the metafields you do not copy into a field.
The variants stay out on purpose. Every CMS item counts against your Webflow site plan, which caps them at a few thousand, or a few tens of thousands on the larger plans, while Shopify allows up to 2,048 variants on a single product. One item per variant would fill the CMS with a handful of products: a shirt in 8 colours and 6 sizes alone would take 48 items. The stock changes with every order, so a copy in the CMS would always be behind. Smootify reads variants and stock from Shopify on the page instead, and the CMS keeps only what Webflow needs to list and sort products.
### Products the sync leaves out
The sync skips a product, or a collection, that you mark in Shopify with a metafield set to `1`:
- **`seo.hidden`**: the product is hidden from search engines and from the store’s search, Smootify’s included, and the sync leaves it out of the CMS. Use it for products customers never browse, such as an add-on or a gift wrap.
- **`sync.hidden`**: the product stays in search, but the sync leaves it out of the CMS.
To set them in the admin, add a product (or collection) metafield definition with that namespace and key, type integer, and keep its **Storefronts** access on: Smootify Studio reads Shopify through the Storefront API and does not see a metafield the storefront cannot read. A value set without a definition, or with the Storefronts access off, still hides the product from search, but Studio imports it into the CMS anyway. The server sync skips it either way, because it reads Shopify through the Admin API. The sync also skips products whose handle contains `untitled`. Studio’s sync log lists what it left out, and why.
**An item imported before you set the metafield:**
- with Smootify Studio, a full sync (**Sync now**) removes it from the CMS, as it does with anything Shopify no longer has, and its log names each item it removes, with its slug, per collection (filter the log by the delete operation to find them). The automatic sync never deletes, and its log says how many removals it skipped;
- with the server sync, it stays until you delete it in Webflow.
### When to sync
With the server sync (Server plan) there is nothing to do: Shopify tells Smootify about each change and the CMS follows. With **Smootify Studio**, sync when Shopify changes something the CMS holds:
| Sync | No need to sync |
|---|---|
| You add a product, a collection or a vendor | Stock changes, or an order comes in |
| You delete a product | You add, remove or rename a variant, or change its options or swatches |
| You change a title, a handle, a description, the featured image or the SEO texts | You change a variant’s image or SKU |
| A product moves into or out of a collection | You change a price, and no Collection list sorts or filters by price: the page shows the live price |
| A metafield you copy into a CMS field changes | A metafield you show with an attribute changes |
| The price of the first variant changes, and a Collection list sorts or filters by **Price** | Selling plans change |
Studio can also sync every five minutes on its own, so the CMS follows Shopify while the app runs.
## 1. Create the three collections
**Smootify Studio**, the desktop app, creates the three collections for you, with every field below and its settings. It is the quickest way, and the one that avoids a wrong field type.
To create them by hand, add three collections in the Webflow CMS: **Products**, **Collections** and **Vendors**. Set their **Collection URL** to `products`, `collections` and `vendors`: these are the URLs Smootify 2.0 expects. If you choose others, see step 7.
Then add the fields below. The sync finds each field by its **name**, so use these names exactly (capital letters do not matter) and the field type shown.
## 2. Products fields
The fields of the Products collection, with their type in Webflow:
| Field | Type | What the sync writes |
|---|---|---|
| **Name** | built in | the product title |
| **Slug** | built in | the product handle |
| **Shopify ID** | Plain text | the product id |
| **Description** | Rich text | the description |
| **Image** | Image | the featured image |
| **Price** | Number, decimal, format `0.00` | the price of the first variant |
| **Price Formatted** | Plain text | that price, in the store's money format |
| **Compare at Price** | Number, decimal, format `0.00` | the compare-at price of the first variant, `0` when there is none |
| **Compare at Price Formatted** | Plain text | that price, formatted; empty when there is none |
| **Is Gift Card** | Switch | on for a gift card |
| **Vendor** | Reference to Vendors | the vendor |
| **Collections** | Multi-reference to Collections | the collections the product is in |
Optional fields, filled when they exist:
| Field | Type | What the sync writes |
|---|---|---|
| **Product Type** | Plain text | the product type |
| **SEO Title** | Plain text | the SEO title from Shopify, or the title when it has none |
| **SEO Description** | Plain text, multi-line | the SEO description, or the description when it has none |
| **Image URL** | Plain text | the address of the featured image on Shopify, as text; Webflow does not copy the file |
| **Image Alt** | Plain text | the alt text of the featured image |
**Shopify ID** matters most: it connects each page and each card to the live product (see step 8).
The number fields must be **decimal**, with the format **0.00** (two decimals) in their settings. A number field that is not decimal makes Webflow round the price: 11.99 is saved as 12.
## 3. Collections fields
The fields of the Collections collection:
| Field | Type | What the sync writes |
|---|---|---|
| **Name** | built in | the collection title |
| **Slug** | built in | the collection handle |
| **Shopify ID** | Plain text | the collection id |
| **Description** | Rich text | the description |
| **Image** | Image | the collection image |
The optional **SEO Title**, **SEO Description**, **Image URL** and **Image Alt** work as for products.
## 4. Vendors fields
A vendor in Shopify is only a name, so the Vendors collection needs only **Name** and **Slug**. The sync creates a vendor item the first time a product with that vendor arrives. The slug is the name in lower case, with dashes.
You can add fields of your own, such as a logo or a text, and fill them in Webflow.
## 5. Your own fields and metafields
You can add more fields to any of the three collections.
- **Do not make them required.** The sync cannot fill a required field it knows nothing about, and the item then fails to save.
- **A field whose name matches a metafield is filled from it.** The sync turns the field’s name into a metafield key in snake case and reads it from the `custom` namespace. A field named **Care Instructions** is filled from `custom.care_instructions`.
- **Any other field is left alone.** Fill it by hand in Webflow.
Only these metafield types are copied, and only into the matching field type:
| Metafield type in Shopify | Webflow field type |
|---|---|
| True or false | Switch |
| Color | Color |
| Date, Date and time | Date/Time |
| Integer, Decimal | Number (decimal, format `0.00`, for a decimal) |
| Single line text, Multi-line text | Plain text |
| List of single line text | Plain text (the values separated by a vertical bar) |
| Rich text | Rich text |
| URL | Link or Plain text |
| Dimension, Volume, Weight | Plain text (the value and the unit) |
| File: an image | Image |
| File: another file, not a video | File |
| List of files: images | Multi-image |
| Product, Collection | Reference to Products or Collections |
| List of products, List of collections | Multi-reference to Products or Collections |
Other types are not copied. Copy a metafield into the CMS only when a Collection list must filter or sort by it. To show a metafield on the page, an attribute reads it straight from Shopify: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).
## 6. Import and keep in sync
There are two ways to bring Shopify into the CMS:
- **Smootify Studio**, the desktop app (every plan). It imports your products, collections and vendors into the CMS, and shows a detailed log of what happens to each item. Run it again after you add products in Shopify.
- **The CMS sync on Smootify's server** (Server plan). It runs on its own, on Smootify's server, and follows the changes made in Shopify. You set it up in the Smootify dashboard, as below.
To set up the server sync, open the CMS sync of the project in the Smootify dashboard:
1. Tell Smootify which of your Webflow collections holds the products, the collections and the vendors.
2. Choose to import everything first, or, if the CMS is already filled, only to follow the changes from now on.
From then on, Shopify tells Smootify when a product or a collection is created, changed or deleted, and the CMS follows. A product deleted in Shopify is removed from Webflow. You can turn the sync off at any time: the items already in Webflow stay.
**Languages.** If the site uses Webflow Localization, the import writes every enabled locale, with the translations you made in Shopify. Webflow’s default locale must be set. See [Markets, currencies and languages](https://www.smootify.io/docs/features/markets).
Change products in Shopify, not in the Webflow CMS: the next sync writes over every field it fills.
> [!TIP]
> While you build the site, always sync with Smootify Studio, even on the Server plan. Its log shows each item it writes and why one fails (a missing field, a wrong type, a required field), so you fix the collections before going live. Turn on the server sync once the CMS is final.
## 7. Match the URLs of the template pages
Webflow gives each collection a template page at `//`. Smootify writes links to these pages itself, in cart lines, in search results and on links with `product="url"`. In 2.0 it builds them as:
- `/products/` for products;
- `/collections/` for collections;
- `/vendors/` for vendors.
When your Collection URLs are `products`, `collections` and `vendors`, there is nothing to set. When one is different, tell Smootify in its options, set in the head code before the loader (see [Install Smootify 2.0](https://www.smootify.io/docs/start/install)):
```html
```
Each value is the Collection URL of that collection. Smootify also uses `productsBase` to know that a page is a product page. With Webflow Localization, the language folder, such as `/fr/`, is added before it for you.
The slug is the Shopify handle, and Smootify’s links use the handle. Do not edit slugs in Webflow: change the handle in Shopify, then sync.
## 8. Bind the Shopify ID
On the template page of **Products**, and in every Collection list of products, a `smootify-product` element takes its `data-id` from the **Shopify ID** field. In the element settings of the Custom element, add the custom attribute `data-id` and bind its value to the field.
```html
{Name}
{Price Formatted}
```
In purple, the values bound to a CMS field. Binding the texts too (**Name**, **Price Formatted**) gives the page its content before Smootify loads, which search engines read; Smootify then writes the live values from Shopify.
The layout of the card is up to you. More in [Build the product page](https://www.smootify.io/docs/build/product-page) and [Product cards](https://www.smootify.io/docs/build/product-cards).
## Where people get stuck
> [!STUCK]
> - **The sync finishes but products are missing.** They are not published to the Headless storefront's sales channel, or not available in your markets. See [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
> - **Nothing is imported at all.** Webflow's default locale is not set. Set it in Webflow, under Localization in the site settings.
> - **The sync fails and you do not know why.** Run it from Smootify Studio: its log names the item and the field that failed.
> - **The sync fails.** One of your own fields is set as required. Make it optional.
> - **Prices in the CMS are rounded (11.99 shows as 12).** The number field is not decimal. Set it to decimal, with the format 0.00, and sync again.
> - **A metafield field stays empty.** Its name does not turn into the metafield key, the metafield is not in the `custom` namespace, or its type is not in the table above.
> - **The sync reports a conflict.** The site is published to its `.webflow.io` address and its custom domain at different times. Publish to both together, or unpublish the `.webflow.io` address.
> - **Links in the cart or the search results open a 404 page.** A Collection URL is not the one Smootify expects. Set `productsBase`, `collectionsBase` or `vendorsBase`.
> - **The sync says Smootify lost access to Webflow.** Authorize the same site again from the dashboard: see [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
---
# Before you publish
> The checks to make before a Smootify store goes live on its custom domain, in Shopify, in the Smootify dashboard and in Webflow, and how to test the store on your .webflow.io address first.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/publish/checklist
URL: https://www.smootify.io/docs/start/publish
Updated: 2026-10-01
A store works on your `.webflow.io` address long before it is ready for customers. This page lists what to check before you publish to your custom domain: in Shopify, in the Smootify dashboard and in Webflow. Then it says how to test the whole store on `.webflow.io`, where Smootify’s debugger helps you.
> [!NOTE]
> Each check links to the page that explains it. If you build the store for a client, also see [Hand a project to a client](https://www.smootify.io/docs/start/hand-off).
## 1. Shopify
- **The Headless token.** The project uses the public token of your Headless storefront, not the test token the Smootify app created. With the test token, customers get no order confirmation email. See [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
- **Sales channel and markets.** Every product and collection you sell is published to the Headless storefront’s sales channel, and available in the markets you sell in. A product that is not there does not load on the site.
- **Payments, shipping and taxes.** They are set up in Shopify. Smootify shows prices as Shopify sends them, so your tax settings in Shopify decide whether the prices on the site include tax.
- **No store password.** The store’s password protection is removed.
- **The checkout domain.** Your main domain is removed from Shopify, and a sub-domain such as `store.example.com` is connected there for the checkout. See [Domains and checkout](https://www.smootify.io/docs/start/domains).
- **Customer accounts**, if customers log in on the site. In the Headless storefront’s Customer Account API settings, the live address of your site is in the JavaScript origins and in the callback URIs, as your `.webflow.io` address already is. Without it, login works on `.webflow.io` and fails on the live site. See [Customer accounts](https://www.smootify.io/docs/build/account).
## 2. The Smootify dashboard
- **A paid plan.** A custom domain needs the Lite or the Server plan. The Free plan runs only on `.webflow.io`.
- **The live domain.** The project settings hold the domain the site is published on, written exactly as published, `www` included when it has one.
- **Add-ons.** Magic Box, if you use it, is turned on in the dashboard. Server add-ons, such as file uploads, need the Server plan and must be activated for the project. On the live site they run only on the domain in the project settings. See [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
## 3. Webflow
- **The script.** The loader is in the site’s head code, and `SmootifyUserOptions`, if you use it, comes before it. See [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
- **The template page URLs.** If a Collection URL is not `products`, `collections` or `vendors`, the matching option (`productsBase`, `collectionsBase`, `vendorsBase`) is set. Otherwise links in the cart and in search results lead to a 404 page. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
- **The custom domain.** It is connected to the Webflow site, and only to Webflow.
- **Cookie consent, on every site.** Smootify does not add Shopify’s cookie banner: design one with `smootify-consent` and put it in a Component every page uses, such as the footer. Add it even if you do not sell in Europe: it shows only to visitors of the regions where Shopify says consent is needed, and stays hidden for everyone else. See [Cookie consent](https://www.smootify.io/docs/features/cookie-consent).
- **Analytics.** The tracking pixels are set in `SmootifyUserOptions.analytics`, and removed from Webflow’s integrations and from your custom code, so each pixel loads once and waits for consent. See [Analytics and pixels](https://www.smootify.io/docs/features/analytics).
- **Search engines, speed and accessibility.** The SEO settings of the template pages are bound to CMS fields, and the images Smootify fills have a size. See [SEO and structured data](https://www.smootify.io/docs/start/seo), [Performance](https://www.smootify.io/docs/start/performance) and [Accessibility](https://www.smootify.io/docs/start/accessibility).
## 4. Test the store on `.webflow.io`
Publish to your `.webflow.io` address. Press Alt+Shift+D (Option+Shift+D on a Mac) to turn on Smootify’s debugger. On a phone, add `?smootify-debug` to the address. Its **Checks** tab lists markup Smootify cannot use, keys that are missing, pixels loaded twice, and a trial that is about to end. Work through it until it is clean. See [The debugger](https://www.smootify.io/docs/start/debugger).
Then go through the store as a customer:
- **A product page:** options, prices, add to cart, buy now.
- **A collection or search page:** filters, sorting, pagination.
- **The cart:** quantities, discount codes, the checkout button.
- **Login and the account pages**, if the store has them.
- **Pages with your own scripts:** no errors in the browser console.
- **Social media:** if you sell through them, open a product link from Instagram on an iPhone.
## 5. Place a test order
Use Shopify’s test payments to place an order from the site. Then check two things:
- the order confirmation email arrives;
- in Shopify, the order shows the name of your Headless storefront as its sales channel.
Both prove that the project uses the Headless token.
## 6. Publish to the custom domain
Publish the site to your custom domain and open it:
- no Smootify banner shows;
- the cart opens, and the checkout opens on your checkout sub-domain;
- login works, if the store has accounts.
The debugger does not run on the custom domain, so fix what you find on `.webflow.io` and publish again. After a change in the Smootify dashboard, publish the site again and reload the page twice: the browser keeps the previous project settings for one visit.
## Where people get stuck
> [!STUCK]
> - **The banner "To connect your custom domain … you need an active Smootify Plan".** The project has no paid plan, or the domain in the banner is missing from the project settings, or written differently.
> - **Customers get no order confirmation email.** The project still uses the test token. Paste the public token of a Headless storefront in the project's settings.
> - **The checkout does not open.** Your main domain is still connected to Shopify, so Webflow's hosting answers the checkout address. Remove it from Shopify and use a sub-domain.
> - **Login fails on the live site only.** The live address is missing from the JavaScript origins and callback URIs of the Customer Account API.
> - **A server add-on works on `.webflow.io` but not on the live site.** The domain in the project settings differs from the site's address, often by `www`.
> - **Visitors in the EU are never tracked.** Analytics ids are set but the page has no `smootify-consent`, so nobody can give consent. Add the banner to a Component every page uses, such as the footer.
---
# Domains and checkout
> Which domain goes to Webflow and which to Shopify, why the checkout needs its own sub-domain, the Headless storefront, and the Shopify theme that sends visitors of the Shopify store to your Webflow site.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/publish/domains
URL: https://www.smootify.io/docs/start/domains
Updated: 2026-10-07
A Smootify store uses two hosts. Webflow serves your pages, and Shopify serves the checkout. Each needs its own address: your main domain for Webflow, and a sub-domain for Shopify. This page sets up both, and says what to do with Shopify’s own storefront, which you no longer use.
> [!NOTE]
> Where you add DNS records depends on your domain provider. Webflow's and Shopify's help pages on connecting a domain give the records to add.
## How the addresses fit together
| Address | Connected to | Serves |
|---|---|---|
| `www.example.com` | Webflow only | every page, the cart, the account pages |
| `store.example.com` | Shopify, as the primary domain | the checkout |
| `example.myshopify.com` | Shopify, always | nothing you use: the Redirect theme sends visitors to your site |
## 1. Connect the main domain to Webflow
Connect your main domain, such as `www.example.com`, to the Webflow site, and to Webflow only.
- **Use `www` as the default domain in Webflow.** Many domain providers cannot point a bare domain, such as `example.com`, with the records Webflow needs. With `www` as the default, the bare domain redirects to it.
- **Add the domain to the Smootify project.** In the project settings in the Smootify dashboard, write the domain exactly as the site is published, `www` included. See [Connect the Webflow project](https://www.smootify.io/docs/start/webflow-project).
## 2. Remove the main domain from Shopify
If your main domain was connected to Shopify before, for example because the store had an Online Store, remove it from Shopify. Otherwise the checkout does not work: its address is answered by Webflow’s hosting instead of Shopify.
## 3. Give the checkout a sub-domain
The checkout works on the `.myshopify.com` address too. Customers trust a checkout on your own domain more, so connect a sub-domain to Shopify:
1. In Shopify, go to **Settings → Domains** and connect a sub-domain, such as `store.example.com`.
2. Make it the store’s primary domain. The checkout opens on the primary domain.
The checkout is Shopify’s page, not a Webflow page. You cannot design it in Webflow: its logo, colours and a few other settings are in Shopify’s checkout settings.
## 4. Use the Headless storefront’s token
The token the Smootify app creates at install is for building and testing only: orders placed with it get no order confirmation email. Before launch, create a storefront in Shopify’s **Headless** channel and paste its public token in the project settings. The storefront’s name then shows as the sales channel of your orders. The steps are in [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
## 5. Send Shopify’s storefront visitors to Webflow
Shopify keeps its own storefront, the **Online Store**, at `example.myshopify.com`. Visitors can still reach it, from an old link or a search result. If you do not use the Online Store, Smootify’s **Redirect theme** sends them to your Webflow site.
What the theme does:
- it sends every page of the Online Store to the same page on your Webflow site, except the cart page;
- product and collection pages go to your template pages, using the URLs you set in the theme, with no extra redirects in Webflow;
- Shopify’s bot check and discount links keep working. A discount link sends the visitor to your site with the code, and Smootify applies it to the cart.
To set it up:
1. Download the [Redirect theme](https://deliverables.smootify.io/Smootify%20-%20Redirect%20theme-1.1.1.zip).
2. In Shopify, go to **Online Store → Themes** and upload the file.
3. Click **Customize**, then open **Theme settings → Storefront** and fill in:
- **Hostname**: the domain of your Webflow site, with `www` when it has one, such as `www.example.com`. Without `www`, visitors are redirected twice.
- **Products Base**: the Collection URL of your Products collection, such as `products`.
- **Collections Base**: the Collection URL of your Collections collection, such as `collections`.
4. Publish the theme.
The Redirect theme also lets the Smootify dashboard read your app embeds, such as the Shopify Inbox chat, straight from the store: see [Shopify Inbox](https://www.smootify.io/docs/features/shopify-inbox).
Already using an older version? Upload the new file the same way and publish it. A theme uploaded from a file starts with the default settings, so fill in **Theme settings → Storefront** again.
## Where people get stuck
> [!STUCK]
> - **The checkout does not open.** Your main domain is still connected to Shopify. Remove it there and use a sub-domain for Shopify.
> - **The checkout opens on the `.myshopify.com` address.** The sub-domain is not the store's primary domain in Shopify.
> - **The banner "To connect your custom domain … you need an active Smootify Plan".** The domain in the project settings is missing or differs from the published one, often by `www`.
> - **Customers get no order confirmation email.** The project still uses the test token. Use the Headless storefront's public token.
> - **Visitors of the Shopify store land on a 404 page.** The theme's Products Base or Collections Base differs from your Collection URLs.
---
# Hand a project to a client
> How to give a finished store to your client, in Shopify, in Webflow and in the Smootify dashboard, in the right order and without losing the connection between them.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/getting-started/publish/project-hand-off
URL: https://www.smootify.io/docs/start/hand-off
Updated: 2026-10-01
When you build a store for a client, the Shopify store, the Webflow site and the Smootify project start in your accounts. At the end, each one moves to the client. This page goes through the three platforms in order, and says what to reconnect afterwards.
> [!NOTE]
> Skip the Shopify part if the store was created in the client's own Shopify account, and the Webflow part if the site was built in the client's workspace.
## 1. Shopify: transfer the store
This part is for a development store created from your Shopify Partner account.
1. Log in to the Shopify Partner Dashboard, find the development store and log in to it.
2. In the store’s admin, go to **Settings → Users and permissions**.
3. In **Store owner**, click **Transfer ownership**, fill in the client’s details and confirm.
4. The client gets an email to accept the transfer, and chooses a Shopify plan.
> [!WARNING]
> The transfer cannot be undone. If the client does not accept it and pick a plan, the store is lost, and you cannot get access to it again.
The Headless storefront and its token belong to the store, so they move with it.
## 2. Webflow: transfer the site
1. Transfer the site to the client’s Webflow workspace, from the site’s settings.
2. The client adds a site plan to the site.
3. The client connects the custom domain to the site and publishes it. See [Domains and checkout](https://www.smootify.io/docs/start/domains).
> [!WARNING]
> Transfer the original site, not a copy. Smootify is connected to that exact site: a copy is a new site for Smootify, and the whole connection would have to be set up again.
## 3. Smootify: give the project to the client
1. **The client’s account.** The client creates an account in the Smootify dashboard. The next step needs it.
2. **Share the project.** In the dashboard, open the project and share it with the client’s email. You both keep access. To give it away completely, transfer it instead: the client gets it, and it leaves your account.
3. **The plan.** The client picks a paid plan from the project in the dashboard. The plan is billed through Shopify, on the store’s bill.
4. **The domain.** Check that the live domain is in the project settings, written exactly as the site is published. Without it, Smootify does not run on the live site.
5. **Reconnect the Webflow site.** A Webflow transfer removes Smootify’s access to the site, and the CMS sync stops. Connect the site again from the dashboard and pick the same site in Webflow. The project keeps its plan: only the access to Webflow is renewed.
## Where people get stuck
> [!STUCK]
> - **The client does not see the project.** No Smootify account uses the email you shared it with. The client signs up first, with that email.
> - **The CMS sync stopped after the transfer.** Webflow removed Smootify's access with the transfer. Connect the same site again from the dashboard.
> - **The banner "Project not Connected".** A copy of the site was transferred, not the original.
> - **The banner "To connect your custom domain … you need an active Smootify Plan".** The client has not picked a plan yet, or the domain is missing from the project settings.
---
# SEO and structured data
> How search engines see a Smootify store. Covers the text and SEO settings that come from the CMS, the product structured data Smootify adds to product pages, alt texts, and Google Shopping.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/seo
Updated: 2026-10-01
A Smootify store is a Webflow site, so search engines index it as they index any Webflow site: its pages, titles, URLs and sitemap are Webflow’s. Smootify adds the live data from Shopify and, on product pages, the product’s structured data for Google. This page says what comes from the CMS, what Smootify adds, and what you set yourself.
## Let the CMS give each page its text
Your product, collection and vendor pages are CMS template pages. Webflow publishes them with the text of their CMS items, so that text is in the page before Smootify loads.
- **Bind the visible text.** Bind the product’s title to the **Name** field and its description to the **Description** field, even on elements that Smootify also fills. Smootify then writes the live values from Shopify over them. See [Build the product page](https://www.smootify.io/docs/build/product-page).
- **Bind the SEO settings.** In the template page’s settings, bind the title tag and the meta description to the **SEO Title** and **SEO Description** fields. The CMS sync fills them from the SEO fields in Shopify, or from the title and the description when those are empty. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
- **Keep the URLs stable.** A page’s slug is the Shopify handle. If you change a handle in Shopify, the page’s URL changes after the next sync: add a 301 redirect from the old URL in Webflow’s site settings.
Webflow’s sitemap lists the CMS pages with the other pages of the site.
## Product structured data
On a product page, Smootify adds the product’s structured data (JSON-LD) to the page, so that Google can show the product as a rich result. There is nothing to add or set.
It holds, from Shopify:
- the name, the description, the images and the brand (the vendor);
- for each variant: the SKU, the barcode when it is a valid GTIN, the price and currency of the visitor’s market, whether it is in stock, on backorder or sold out, and its link with `?variant=`;
- the options Google knows, when the product’s options are named Color, Size, Material or Pattern, in English, Italian, French, German or Spanish (every name is below);
- the rating, when the page shows it (see below).
The option names Smootify recognises, in any capitals:
| Google option | Names |
|---|---|
| Color | Color, Colour (English), Colore (Italian), Couleur (French), Farbe (German), Color (Spanish) |
| Size | Size (English), Taglia, Misura (Italian), Taille (French), Größe, Grösse (German), Talla, Tamaño (Spanish) |
| Material | Material (English), Materiale (Italian), Matériau, Matière (French), Material (German), Material (Spanish) |
| Pattern | Pattern (English), Fantasia (Italian), Motif (French), Muster (German), Estampado (Spanish) |
A product with one variant is described as one product. A product with several variants is described as a group, with one entry per variant. When the visitor changes market, the prices and the currency follow.
**Which pages.** Smootify adds it only on product pages: the page’s URL is under your products folder (`products`, or the value of `productsBase`), and its last part is the Shopify handle. There is none on collection pages, on the home page or on the cart.
**The rating.** Google recommends showing the rating on the page that carries it. If a review app writes Shopify’s own review metafields, `reviews.rating` and `reviews.rating_count`, show both inside `smootify-product`: Smootify then adds the rating to the structured data.
```html
4.5
12
```
Both need `namespace="reviews"`: without it, Smootify reads the `custom` namespace. Stars and review widgets: see [Product reviews](https://www.smootify.io/docs/features/reviews).
**Your own structured data wins.** If the page already has Product structured data, from an SEO app or from custom code in the template, Smootify leaves it alone and adds nothing. To use Smootify’s instead, remove yours.
**Turn it off.** Set `structuredData` to `false` in Smootify’s options, in the site’s head code before the loader:
```html
```
If you already have an options script, add the line to it: use only one. See [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
**Check it.** Smootify adds the structured data after the page loads, so the page source does not show it. Test the page with Google’s Rich Results Test, or with the URL inspection in Search Console. Google decides whether a rich result shows.
## Google Shopping
For Google Shopping, Google reads product markup that is added by scripts less often. The product feed of Shopify’s Google & YouTube app stays the reference there.
## Images and alt text
On the images it fills, Smootify writes the alt text of the image in Shopify. When the Shopify image has no alt text, the image keeps the one you set in Webflow. Write alt texts in Shopify, on the product’s media.
The structured data also takes its images from the product’s media in Shopify, not from images in the CMS. A product without media in Shopify has no image there.
If you group gallery images by alt text, the variant tag is part of the public alt text: see [Product gallery and media](https://www.smootify.io/docs/build/product-gallery).
## The Shopify storefront
Shopify’s own storefront, the Online Store, can still show your products on the `.myshopify.com` address. If you do not use it, send its visitors to your Webflow site: see [Domains and checkout](https://www.smootify.io/docs/start/domains).
## Where people get stuck
> [!STUCK]
> - **The structured data is not in the page source.** Smootify adds it after the page loads. Check with the Rich Results Test or the URL inspection in Search Console.
> - **A product page has no structured data.** Its URL is not under the products folder set in `productsBase`, its slug differs from the Shopify handle, or `structuredData` is `false`.
> - **Smootify's structured data never appears.** The page already has Product markup, often old custom code in the template or an SEO app. Remove it.
> - **No rating in the structured data.** Both metafields must be on the page, with `namespace="reviews"`, and the count must be above zero.
> - **No image in the structured data.** The product has no media in Shopify. Images in the CMS are not used.
> - **No GTIN.** The barcode in Shopify is not a valid GTIN, such as an internal code: it is left out.
> - **The product is missing from Google Shopping.** Shopping relies on the product feed of the Google & YouTube app.
---
# Performance
> What Smootify loads on a page and when, what it does by itself to make pages feel fast, and what you control as a designer, from image sizes to loading placeholders.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/performance
Updated: 2026-10-01
Smootify loads the parts a page uses, when it uses them, and prepares the next product page while the visitor is still on this one. Some of the biggest gains depend on your design, though: the size of the images Smootify fills, and the placeholders that keep the layout still while data arrives. This page says what loads when, what Smootify does by itself, and what you can do.
## What loads, and when
Each part loads at its own moment:
- **At start, on every page:** one small loader script in the head, which loads Smootify without blocking the page, and its stylesheet. Products, prices, the add-to-cart form and the cart start with it.
- **Only on the pages that contain them:** every other part, such as search, filters, account pages, add-ons, the cookie banner and the market suggestion. A page without a search does not download the search.
- **Only when first needed:** the account data (orders, subscriptions, addresses), the 3D viewer (when a 3D model is shown), the map of the store locator (from Mapbox, only on its page), and the product structured data (on product pages, after the page has loaded).
- **After consent:** the tracking pixels set in `SmootifyUserOptions.analytics`. A visitor who must consent downloads none until they accept. See [Analytics and pixels](https://www.smootify.io/docs/features/analytics).
- **When the page is idle:** the animation library used by the cart and other panels, unless the page already loads it. The `gsap` option turns it off.
- **Never on your custom domain:** the debugger. It loads only on `.webflow.io`, when you turn it on.
Visitors who open your site in the Instagram, Facebook or TikTok app on an iPhone get a build made for those browsers: see [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
## What Smootify does by itself
- **Prepares the next page.** In browsers that support it, such as Chrome and Edge, a product page starts loading when the visitor holds the pointer on its link, and a collection or vendor page when the visitor presses its link.
- **Fetches the next product.** Once the page is idle, the products of the links near the screen, or hovered, or touched, are fetched into the browser’s cache. Their page then shows the product at once, in every browser.
- **Remembers products.** Products stay in the browser’s cache for a few minutes, so a product seen on one page shows at once on the next.
- **Restarts Webflow interactions only when needed**, after a change that Smootify made to the page.
These are on by default. The `preload`, `cacheDuration`, `prefetch` and `gsap` options change them: see [Options](https://www.smootify.io/docs/developers/options).
## Size the images Smootify fills
Images from Shopify arrive at the size they were uploaded, often several thousand pixels wide. When Smootify writes an image, it replaces the responsive versions Webflow made for your placeholder. Without a size, a grid of product cards downloads every photo at full size.
Add a size attribute to each image Smootify fills, and the Shopify CDN sends a smaller file:
| Attribute | What the Shopify CDN sends |
|---|---|
| `max-width="600"` | the image at most 600 pixels wide |
| `max-height="600"` | the image at most 600 pixels high |
| `crop="center"` | with both sizes, the image cropped to them |
| `pad-color="ffffff"` | with both sizes, the image with the space around it filled with that color |
Choose about twice the width the image shows at on the page, so it stays sharp on high-density screens.
```html
Product name
```
The attributes go on the element that has the Smootify attribute: the Image with `variant="image"` or `product="specific-image"`, the image of a cart line, and in the gallery the Slider and the thumbnails. The gallery’s places: [Product gallery and media](https://www.smootify.io/docs/build/product-gallery). Every image they work on: [image size](https://www.smootify.io/docs/reference/attributes/image-size).
## Keep the layout still while data loads
Smootify fills the page once the data arrives. Until then, what you designed shows. Two things keep the page from jumping:
- **Give placeholders their final size.** Keep a placeholder text in a text element, and give images a width and a height, or an aspect ratio, in the Style panel.
- **Use `skeleton`.** `skeleton="text"`, `"button"`, `"block"` or `"image"` shows a loading shimmer on an element until Smootify fills it, and `skeleton="hide"` hides it while keeping its space. Nothing shimmers unless it has the attribute. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
A skeleton does not make the page load faster. It keeps the page still, which visitors and Google’s Core Web Vitals both notice.
## Large catalogues
A filtered collection page with many cards is the heaviest page of a store. Show fewer products per page, and use previous and next buttons instead of loading more on long lists: see [Filters, sorting and pagination](https://www.smootify.io/docs/build/filters).
## Measure
On your `.webflow.io` address, turn on the debugger with Alt+Shift+D (Option+Shift+D on a Mac), or on a phone add `?smootify-debug` to the address. Its **Performance** tab shows the page’s Largest Contentful Paint, when Smootify was ready, and the Smootify files the page downloaded, with their size. See [The debugger](https://www.smootify.io/docs/start/debugger).
## Where people get stuck
> [!STUCK]
> - **Product cards load slowly.** The images have no size attribute, so each card downloads the full photo. Add `max-width`.
> - **Size attributes do nothing on the gallery slides.** Put them on the Slider, not on the slide.
> - **Size attributes do nothing on an image.** It is bound to a CMS field. They work only on images Smootify fills from Shopify.
> - **The page still jumps with a skeleton.** The element has no size before it is filled. Give it a size or keep placeholder text in it.
> - **Nothing shimmers while the page loads.** Only elements with a `skeleton` attribute do. Add it where you want a placeholder.
> - **The collection page is slow on phones.** Too many products per page, or loading more keeps adding cards. Lower `limit` and use previous and next.
---
# Accessibility
> What Smootify tells screen readers about the parts it renders, such as the chosen option, a value that cannot be bought or a form error, and what stays with your design, from real buttons and labels to contrast and focus styles.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/accessibility
Updated: 2026-10-10
Smootify shows states with classes, such as `is-active` on the chosen colour. It also writes the same states for screen readers, so a visitor who cannot see the design hears what it shows. The rest is up to the design: the elements you choose, their labels, the contrast and the focus styles. This page covers both parts.
## What Smootify writes for you
There is nothing to set. Smootify adds these attributes next to the classes it already sets:
| State | Class you style | What a screen reader hears |
|---|---|---|
| the chosen value, on a button | `is-active` | pressed (`aria-pressed="true"`) |
| the chosen value, on a link | `is-active` or `w--current` | current (`aria-current="true"`) |
| a value that cannot be bought | `is-disabled` | unavailable (`aria-disabled="true"`) |
| a link to the current page | `w--current` | current page (`aria-current="page"`) |
Where these apply:
- **Swatches:** `variant-swatches`, `addon-swatches`, `filter-swatches`, the swatches of dynamic properties, and the buttons of `wishlist-toggle`.
- **Dropdowns and popovers of values:** the pickers for variants, add-ons, filters, sorting, the product configurator and markets. Their values are links, so the chosen one is “current”.
- **Gallery thumbnails:** the thumbnail of the image on screen.
- **Product links** (`product="url"`, `product="link"`), when they point to the current page, as Webflow does for its own links (with the class `w--current` too). A `?variant=` in the address and a final `/` do not count: the link to `/products/tee` is current on `/products/tee?variant=…`, not on `/products/tee-shirt`.
A value that cannot be bought stays focusable and clickable, because a sold-out variant can still be chosen.
Smootify also:
- **names values that have no text.** A swatch that shows only a colour or an image is read with its value’s name, such as “Red”;
- **groups the values of an option.** `variant-swatches`, `variant-dropdown`, `variant-popover` and `variant-selector` are read as a group named after the option, such as “Color”;
- **reads errors out.** The Error message of a Form Block (add to cart, cart, account forms) is announced as soon as it shows;
- **marks the chosen tab** of `subscription-swatches` when it switches tabs itself;
- **writes alt texts from Shopify** on the images it fills. When the Shopify image has no alt text, the one you set in Webflow stays.
For example, this swatch has no text, only a colour. Smootify names each copy after its value:
```html
```
## What stays with your design
- **Real buttons.** Things that act, such as swatches, quantity steps and Buy now, are Custom elements with the tag `button`. A Webflow Button publishes a link, and a screen reader announces a link.
- **Names for icon buttons.** Smootify names only swatch values. A button that shows only an icon, such as the plus and minus of a quantity, a remove button in the cart or a cart icon, needs a name: add the custom attribute `aria-label` with what it does.
- **Labels for fields.** Give every field a label: the quantity field, the discount code field, the fields of account and address forms. Webflow’s field Label element works.
- **Contrast.** Check the contrast of your text and of your states. A sold-out value styled with `is-disabled` must stay readable, and the chosen value must differ by more than colour alone, for example with a border.
- **Focus styles.** Style the **Focused (keyboard)** state of buttons, swatches and links in the Style panel, so keyboard users see where they are.
- **Alt texts.** Write them in Shopify, on the product’s media. If you group gallery images by alt text, the variant tag is read aloud: see [Product gallery and media](https://www.smootify.io/docs/build/product-gallery).
- **Ratings.** Stars drawn from a rating metafield are only a picture. Show the number as text too.
This quantity field names its two icon buttons with `aria-label`:
```html
```
**The mini cart.** Built with a dialog or a popover, the cart gets the browser’s own keyboard behaviour: Escape closes it, and a dialog keeps the focus inside while it is open. A cart opened with your own `data-trigger` elements does not. See [Build the cart](https://www.smootify.io/docs/build/cart).
Smootify writes the states. Whether the whole site meets an accessibility standard depends on the site: test it with a screen reader and a keyboard before you publish.
## Where people get stuck
> [!STUCK]
> - **A style on the chosen value does nothing in a dropdown.** Dropdown values are links, which get "current", not "pressed". Style the `is-active` or `w--current` combo class.
> - **A sold-out value cannot be chosen any more.** A style or an interaction blocks its click. Smootify keeps unavailable values clickable on purpose: style `is-disabled` without blocking the click.
> - **A picker is read with the wrong name.** It has an `aria-label` copied from another element. Smootify never replaces a name you set: remove it, and Smootify names the group after the option.
> - **An icon button is read only as "button".** It has no text. Add `aria-label` as a custom attribute.
> - **Buy now or a quantity step is read as a link.** It is a Webflow Button, which publishes a link. Use a Custom element with the tag `button`.
---
# Move a site from 1.x
> The steps to move a Webflow site from Smootify 1.x to 2.0 - customer accounts, the new script on your .webflow.io address, the options and markup to update, your own scripts, going live and going back.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/start/migrate
Updated: 2026-10-05
A site on Smootify 1.x moves to 2.0 by replacing two lines of code with one. Most of the markup keeps working as it is: the elements, the attributes, the state classes and the options have the same names. A few 1.x features were removed, and a few settings must be in place. This page goes through the move in order: you switch on your `.webflow.io` address first, fix what the debugger points out, then go live.
> [!NOTE]
> This page is for a site that loads Smootify from `cdn.smootify.io/assets/latest/`. You find those lines in Webflow under **Site settings → Custom code → Head code**. A site that keeps them stays on 1.x, and nothing changes for it until you switch.
## 1. Move to the new customer accounts first
Shopify has deprecated its classic customer accounts and will remove them soon, so 2.0 works with the new customer accounts. The classic forms `login-form`, `register-form`, `recover-form`, `reset-form` and `activate-form` remove themselves from the page. If the site sells without accounts, or already uses the new ones, go to step 2.
If the store still uses the classic accounts, move it before you switch the script (Shopify asks for it anyway):
1. In Shopify, open **Settings → Customer accounts** and turn on the new customer accounts.
2. Set `newCustomerAccountsPublicKey` in `SmootifyUserOptions`, and replace the classic forms with the login of the new accounts. [Customer accounts](https://www.smootify.io/docs/build/account) shows both.
This is a setting of the whole store, so the live site changes with it. 1.x works with the new accounts too, with the same key: do this step on the live 1.x site. In 2.0, the site no longer handles the links in Shopify’s emails for classic accounts (activate the account, reset the password).
## 2. Switch on your `.webflow.io` address
Until you go live, publish every change to your `.webflow.io` address only. In Webflow’s **Publish** menu, select the `.webflow.io` address and not the custom domain. Your visitors keep the last version you published to the custom domain, with 1.x.
In **Site settings → Custom code → Head code**, find the two 1.x lines:
```html
```
Replace them with the 2.0 loader, in the same place:
```html
```
Keep your `SmootifyUserOptions` script where it is, before the loader. Scripts with `type="smootify-load"` stay as they are. Then publish to the `.webflow.io` address.
[Install Smootify 2.0](https://www.smootify.io/docs/start/install) explains the loader, its release channels and the stylesheet.
Then open the site on its `.webflow.io` address and press Alt+Shift+D (Option+Shift+D on a Mac) to turn on the debugger. On a phone, add `?smootify-debug` to the address. Its **Checks** tab lists what 2.0 reads differently on that page, and its **Elements** tab shows which elements run. Go through every template page: product, collection, cart, search and account. See [The debugger](https://www.smootify.io/docs/start/debugger).
> [!WARNING]
> While the `.webflow.io` address and the custom domain are published at different times, Webflow does not let the CMS sync write items: the sync reports a conflict. Keep this stage short, and run the sync once you have gone live. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
## 3. Set the options
**Product, collection and vendor URLs.** 2.0 builds its links as `/products/…`, `/collections/…` and `/vendors/…`, like Shopify. 1.x built `/product/…`, `/collection/…` and `/vendor/…`. Open a product page of your site: if its address starts with `/product/`, add the three options to your options script:
```html
```
- Each option is the URL of one template page. Set the ones whose URL is singular, and keep the others at the default.
- Add the lines to the options script the site already has. A second options script replaces the first.
- A site that already sets the three options changes nothing.
Without them, the links Smootify writes, such as those in the cart and the search results, open a 404 page.
**Customer accounts.** `newCustomerAccountsPublicKey` must be set for login and the account pages, as in step 1.
Every option is in the [options reference](https://www.smootify.io/docs/reference/options).
## 4. Update the markup
Most sites need only some of these. The debugger points out the `variation` attributes, the tax attributes, styles written for the old cookie banner class and an address form still using `territoryCode`.
### `variation` attributes
Rename every custom attribute named `variation` to `variant`, with the same value: `variation="sku"` becomes `variant="sku"`, `variation="swatch1"` becomes `variant="swatch1"`. 2.0 reads only `variant`. The debugger counts the elements left, and a click on its row scrolls the page to the first one.
Inside a swatch button, the image of the button’s variant is `option-variant="image"`. In 1.x, `variation="image"` and `variant="image"` also worked there: rename them to `option-variant="image"`.
### Cookie banner
2.0 no longer adds Shopify’s cookie banner. Build the banner in Webflow with a `smootify-consent` element, in a Component every page uses, such as the footer. Every site needs it, even one that does not sell in Europe: it shows only where consent is needed. See [Cookie consent](https://www.smootify.io/docs/features/cookie-consent). Styles written for the body class `is-shopify-cookie-banner-visible` move to `cookie-banner-visible`.
### Colour swatches
2.0 takes swatch colours and images from Shopify’s own swatches, or from the option metaobject in the product metafield `custom.options`, as 1.x did. It no longer looks colours up in all the colour patterns of the store.
Open a product with colours. If its swatches have no colour, the Color option was linked to the Color metafield after it was created, and Shopify leaves those swatches empty. Delete the option and add it again, choosing the Color metafield. This creates the variants again: note their SKUs, prices and stock first. See [Swatch colours and images](https://www.smootify.io/docs/build/swatches).
### Address forms
In the add and edit address forms, rename the country Select field from `territoryCode` to `countryCode`: its **Name** setting in Webflow. Shopify has deprecated `territoryCode`, and without a `countryCode` Select field the form removes itself. See [Addresses](https://www.smootify.io/docs/build/addresses).
### Loading placeholders
1.x made every text, image and button shimmer until the product loaded. In 2.0, only elements with a `skeleton` attribute shimmer. Add `skeleton="text"`, `"button"`, `"block"`, `"image"` or `"hide"` where you want a placeholder. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
### Upload fields (server plan)
1.x built upload fields with FilePond and its own look. 2.0 uses the elements you design, with the `upload` parts. Styles written for the `.filepond--…` classes no longer apply: see [File uploads](https://www.smootify.io/docs/features/file-uploads).
### Everything else
A few elements must sit in a different place, a few search widgets read fewer settings, and some values look different. [What changes for your site](https://www.smootify.io/docs/start/changes) lists them. The full list of removed elements is in [removed elements](https://www.smootify.io/docs/reference/elements/removed).
## 5. Update your own scripts
This step is for custom code that uses Smootify. Pass it to whoever wrote the code.
- **What scripts can use.** `window.Smootify`, its documented methods, the documented element methods and the `smootify:*` events. `Smootify.store` and module imports are gone.
- **Waiting for Smootify.** Code that uses Smootify goes in a `window.Smootify.push` function. A third-party snippet that calls the cart (`/cart.js`, `/cart/add.js`) or wraps `fetch` goes in a `
```
`selected-value` matches the label as shown: after a change, write the new label, such as `selected-value="Price, low to high"`.
**Several languages.** With Webflow Localization, each locale sets the page language in ``. Pick the labels from it, and keep the English ones for the other locales:
```html
```
## 4. Count, active filters and reset
These elements go anywhere inside `smootify-search-discovery`. All are optional.
- **`filter="count"`** on text shows the total number of results.
- **`filter="active"`** is the template of one active filter: a Div block, a link or a Custom element with the tag `button`. Smootify makes one copy per active filter, and a click on a copy removes that filter. Inside it, **`filter="active-label"`** shows “Colour: Red”.
- **`filter="active-count"`** shows how many filters the visitor can remove.
- **A reset button**: a Custom element with the tag `button` and `type="reset"`. It removes every filter the visitor can remove, and the search words. It is disabled and gets `is-disabled` when there is nothing to remove.
- **`filter="empty-state"`** on a Div block shows only when no product matches.
```html
Colour: Red
No products match these filters.
```
Filters the visitor cannot remove, such as a value preselected without `allow-reset`, come first among the active filters, with the class `preselected-filter`, and a click on them does nothing.
## 5. Pagination
Pagination buttons are Custom elements with the tag `button` inside `smootify-search-discovery`. Their `data-action` decides how the visitor moves through the results:
| Way | `data-action` | Shows | Good to know |
|---|---|---|---|
| Previous and next | `previous`, `next` | one page at a time | the best choice for large catalogs |
| Page numbers | `page` | one page at a time, with the numbers to jump to, between previous and next | on a collection, it needs the Availability filter (see Page numbers) |
| Load more | `load` | the next page, added below the products shown, on a click | every page adds cards, which slows phones down on large catalogs |
| Load as the visitor scrolls | `load-in-view` | the next page, added below when the button scrolls into view | every page adds cards, which slows phones down on large catalogs |
A button is hidden and disabled when there is no such page. Use `previous` and `next`, or one of the two load buttons, not all of them. Buttons inside a product card, such as the previous and next of a booking calendar, are not pagination buttons.
When new results appear, the page scrolls to the block. `disable-scroll` on `smootify-search-discovery` turns this off, and `disable-scroll-on-load-more` turns it off for the load buttons only.
### Page numbers
Next to previous and next, the block can show page numbers, such as `1 … 4 5 6 … 12`:
```html
```
- **One number button.** A Custom element with the tag `button` and `data-action="page"` is the template of every number: Smootify copies it once per number shown. Inside it, `filter="page-number"` holds the number; without it, the number becomes the button’s text. The current page gets the class `is-active` and cannot be clicked: style it with a combo class.
- **The gap.** `filter="page-ellipsis"` is the template of the gaps between numbers, such as `…`. It is optional. When the gap is a single page, that page’s number shows instead.
- **How many numbers.** `page-range` on `smootify-search-discovery` sets how many pages show on each side of the current one, `1` by default. The first and the last page always show.
- **“Page 3 of 12”.** `filter="page-current"` and `filter="page-total"` on text. The total stays empty until Smootify knows it.
- **The class `has-page-numbers`** is on `smootify-search-discovery` while there is more than one page to number: use it to hide the number bar otherwise.
- **In the address.** With `sync-query-params`, the address also gets the page from the second one on, such as `?page=3`, and a link with `?page=3` opens that page.
> [!NOTE]
> On a **collection**, page numbers need the **Availability** filter turned on in Shopify's Search & Discovery app. Shopify does not give the number of products in a collection, so Smootify adds it up from that filter. Without it the numbers do not show (previous and next still work), `filter="count"` shows only the products of the page, and on `.webflow.io` the debugger says why. A **search** counts at most 1,000 results, a Shopify limit.
Opening a page far from the current one takes a moment longer: Shopify gives the pages one after the other, so Smootify first reads the pages before it, with small requests. The pages already seen, and the next and previous ones, open at once.
## 6. Links, mobile panels and an apply button
**Filters in the address.** With `sync-query-params` on `smootify-search-discovery`, the address follows the filters, such as `?Color=Red`, so a filtered page can be shared. Locked filters stay out of it. Even without it, a link to the page with a filter’s label and a value, such as `/shop?Color=Red`, opens it with that filter on, as long as the page has a filter element with that label. `?q=` followed by words opens it with that search.
**Filters outside the block.** Filters and sort elements can sit in a panel elsewhere on the page, such as a side panel on mobile. Give the panel an ID in its element settings, and add `data-expand="#filters"` (with that ID) to `smootify-search-discovery`.
**Filters in a popover.** With `auto-close-details="true"` on `smootify-search-discovery`, a popover of filters inside it closes once the results change. See [Popovers](https://www.smootify.io/docs/build/popovers).
**An apply button.** A Custom element with the tag `button` and `type="submit"`, inside the block but outside the filters’ forms, changes how filters apply: a choice updates the filters only, and the products reload when the visitor clicks the button.
## Other settings
On `smootify-search-discovery`:
- `animation` (`fade` by default, `slide`, `scale`, `fadeBlurred`, `slideBlurred` or `none`), `duration`, `easing` and `stagger` set how new cards appear;
- `unavailable-products` sets where products that cannot be bought go: `LAST` (the default), `HIDE` or `SHOW`. It does not apply inside a collection;
- `avoid-webflow-restart` skips restarting Webflow interactions after each render.
Every attribute is in [`smootify-search-discovery`](https://www.smootify.io/docs/reference/elements/smootify-search-discovery) and [search & discovery attributes](https://www.smootify.io/docs/reference/attributes/search-discovery).
**While it loads.** Add `skeleton` to the filters, the count and the card elements that should shimmer until the first results. Smootify removes it when it writes them. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
## Before you publish
Shopify’s own limits apply: a collection with more than 5,000 products, or a search with more than 100,000 results, shows no filters, and a filter shows at most 100 values. When a filter has more, group similar values in the app: a group holds at most 200 values, and a store can have at most 1,000 groups.
> [!STUCK]
> - **A filter never shows.** Its `label` is not written exactly as in Shopify's Search & Discovery app, or none of the listed products has a value for it.
> - **A filter element disappears.** It has no Form Block inside, or it sits outside `smootify-search-discovery` and outside the `data-expand` panel.
> - **Every card shows the same name or image.** Those elements are bound to CMS fields only. Add `product="title"`, `variant="image"` and the other attributes.
> - **Only three sort options.** Best selling, title and date exist only inside a collection.
> - **`data-tag` changes nothing.** The **Tag** filter is off in Shopify's Search & Discovery app. Turn it on.
> - **`data-vendor` changes nothing.** The **Vendor** filter is off in Shopify's Search & Discovery app. Turn it on.
> - **A search in a big collection misses products.** Only the first 2,000 products of the collection are compared with the search. Give the collection's products a tag and use `data-tag`.
> - **The count shows only the products of the page, and no page numbers appear.** On a collection, the total comes from the **Availability** filter. Turn it on in Shopify's Search & Discovery app.
> - **The page is slow on phones.** Lower `limit`, and use `previous` and `next` instead of loading more.
## Coming from 1.x
- Nothing shimmers by default any more: add `skeleton` where you want it.
- `filter="count"` is now the total number of results; 1.x showed the products of the page.
- `filter-search` reads no `label`, and `filter-price` is always the price filter.
- `filter-swatches` keeps its first button and removes the others, a submit button included.
- The text search works on collection pages: 1.x hid `filter-search` there with the `hide-search` class, which 2.0 no longer sets. Remove a shared `filter-search` from the collection pages if they should not have one.
---
# Product lists without the CMS
> Best sellers, newest products, related and complementary products, a custom query, one collection or the products in a metafield, filled straight from Shopify with one attribute on smootify-product.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/ecommerce/products/listing
URL: https://www.smootify.io/docs/build/product-lists
Updated: 2026-10-13
Some lists cannot come from the Webflow CMS: best sellers need Shopify’s sales history, and related products are computed by Shopify. For these, a `smootify-product` fills itself from Shopify. You design one card, give it a `data-id` value, and Smootify repeats it once per product. There is no Collection list and no sync.
> [!NOTE]
> Before you start, Smootify must be installed on the site. These lists read Shopify directly, so the products do not need to be in the CMS.
## How it works
1. Add a Div block for the grid, or a Webflow Slider.
2. Inside it, add a `smootify-product` and design it as a [product card](https://www.smootify.io/docs/build/product-cards).
3. Give it the custom attribute `data-id` with one of the values below, and `limit` for the number of products.
```html
```
When the products arrive, the card takes the first one, and Smootify places a copy of the card after it for each of the others. In a Webflow Slider, each product gets its own slide (see below).
**How many.** Without `limit`, a list shows 3 products. Related and complementary products are the exception: without `limit` they show all the products Shopify returns. A metafield list always shows all the products picked in it.
**When the list is empty.** If Shopify returns no product, the card is removed. The heading you put next to it stays, unless you hide it: see “Hide a section when its list is empty” below.
## The lists
Each `data-id` fills the card from a different place:
| `data-id` | What it shows | Also needs |
|---|---|---|
| `best-seller` | the best sellers, over the whole life of the store | nothing |
| `created-at` | the newest products, by their Shopify creation date | nothing |
| `purchasable` | products that can be bought now | nothing |
| `custom-query` | products that match a Shopify search | `data-query`, optionally `data-sort` |
| `specific-collection` | the products of one collection, in its Shopify order | `data-collection-handle` |
| `related` | products Shopify finds related to a product | `data-parent-id` |
| `complementary` | products that go with a product | `data-parent-id` |
| `metafields.custom.pairs_with` | the products picked in a metafield of a product | `data-parent-id` |
| `metafields.custom.my_products` | the products picked in a metafield of the customer | `data-parent-id="customer"` |
### Best sellers, newest, available
`best-seller`, `created-at` and `purchasable` need nothing else. The first two use data Webflow never receives: the store’s sales history, and the date each product was created in Shopify (not in Webflow).
### A custom query
`data-id="custom-query"` lists the products that match `data-query`, written in Shopify’s [product search syntax](https://shopify.dev/docs/api/storefront/latest/queries/products#arguments-query):
- `tag:gift` for a tag;
- `vendor:Acme` for a vendor;
- `product_type:Shoes` for a product type;
- `variants.price:<50` for a price range.
`data-sort` sets the order with one of Shopify’s [product sort keys](https://shopify.dev/docs/api/storefront/latest/enums/ProductSortKeys), such as `TITLE`, `PRICE` or `CREATED_AT`. The order runs one way only: `PRICE` is the lowest price first.
### One collection
`data-id="specific-collection"` with `data-collection-handle="summer"` lists the products of that collection, in the order set in Shopify. It is useful where the page is not the collection’s own page, such as the home page.
Inside a Collection list of your **Collections**, bind `data-collection-handle` to the **Slug** field: each collection item then shows its own products. A Webflow Collection list cannot do this with a nested list of products.
### Related and complementary products
On the product page, `data-id="related"` or `data-id="complementary"` lists products for the product in `data-parent-id`. Bind `data-parent-id` to the **Shopify ID** field of the page.
```html
```
- Shopify computes related products by itself.
- Complementary products are the ones you pick for each product in Shopify’s **Search & Discovery** app. If none are set there, the list stays empty.
- Without `data-parent-id`, the element is removed.
For the same lists in the cart, based on what the visitor added, see [Cart upsells](https://www.smootify.io/docs/build/cart-upsells).
### Products picked in a metafield
`data-id="metafields.custom.pairs_with"` lists the products of the product metafield `custom.pairs_with` of the product in `data-parent-id`. Write the namespace and the key of your own metafield after `metafields.`.
- The metafield must be of type product reference, or a list of product references. Any other type gives no products.
- Its definition in Shopify needs **Storefront access**.
- `data-repeat="2"` repeats the list twice, which helps a slider that has only a few products.
The merchant then picks the products for each product in Shopify, such as “Complete the look”. To show the referenced products without prices or add to cart, the `metafield` attribute is lighter: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).
### Alternatives when a variant is sold out
Instead of a dead end, the product page can offer other products while the chosen variant cannot be bought. It takes only attributes on the page’s `smootify-product`: nothing goes into the Webflow CMS.
1. In Shopify, create a **variant** metafield of type **List of products**, such as `custom.alternatives`, with **Storefront access**. On each variant that needs them, pick its alternatives: a sold-out size can point to similar products.
2. Inside the page’s `smootify-product`, add a Div block with `condition="out-of-stock"`. It shows only while the chosen variant cannot be bought.
3. Inside it, add a Div block with `if-variant-metafield="alternatives"` and put your heading in it. It shows only when the chosen variant has alternatives.
4. Inside that Div block, add a Link block with `variant-metafield="alternatives"`, holding an Image with `reference="image"` and a Text block with `reference="title"`. It repeats once per alternative.
```html
```
Everything follows the chosen variant: the block shows for a sold-out size that has alternatives, changes with the size, and hides when the visitor picks a size in stock. More about references and `if-variant-metafield`: [Metafields on the page](https://www.smootify.io/docs/build/metafields).
These links show the image and the title of each alternative. For cards with a price and an add-to-cart button, use a **product** metafield with a `smootify-product` list, as above, inside the same `condition="out-of-stock"` block: the alternatives are then the same for every variant, and `if-metafield="alternatives"` on the Div block around the heading hides it when the product has none.
### Products picked for each customer
With `data-parent-id="customer"`, the same `data-id="metafields.."` lists the products of a metafield of the logged-in customer: a list the merchant curates for each customer, such as their usual order or the catalog of a wholesale buyer.
```html
Product name
$0.00
```
- The metafield is a customer metafield, of type product reference or a list of product references. Create it in Shopify under **Settings › Custom data › Customers**, with its **Customer accounts** access set to **Read**, then pick the products on each customer.
- Customer accounts must be set up on the site: see [Customer accounts](https://www.smootify.io/docs/build/account).
- For a visitor who is not logged in, or a customer with no products in the metafield, the element is removed.
- `data-repeat` works as for a product metafield. While the list has products, the page’s `body` gets the class `has-customer-` followed by the namespace and key, such as `has-customer-custom-my_products`.
## Lists in a slider
Put the `smootify-product` in the first slide of a Webflow Slider. Each product after the first goes into its own slide.
**Placeholder slides.** In the slides after the first, you can design `smootify-product` elements with `data-id="loader"`, as in the example above. The list fills them in order, one per product, so the slider has its final shape before the products load. When there are more products than placeholders, Smootify adds copies of the first slide. Placeholders the list does not reach stay as designed: they are part of the layout, not leftovers. See [placeholder cards](https://www.smootify.io/docs/build/product-cards).
**The slider follows the products.** Smootify redraws the Webflow Slider once the slides are filled, and again when they change. A card that hides, such as one with `hide-if-product-in-cart` whose product is in the cart, hides its whole slide, so the slider leaves no gap. When every card fits in view, with one page, both arrows get the class `is-disabled` and `aria-disabled="true"`: style `is-disabled` as a combo class on the arrows, for example with lower opacity. At the ends of a slider that is not infinite, Webflow’s own **Hide arrows** setting hides them.
## Hide a section when its list is empty
While a list has products, the page’s `body` gets the class `has-` followed by the list’s value: `has-best-seller`, `has-related`, and so on. For a metafield list, the class is `has-` with the namespace and key, such as `has-custom-pairs_with`.
For related and complementary products, two conditions do the work in the Designer: an element with `condition="has-related"` or `condition="has-complementary"` shows only while that list has products. Put it on the section around the heading and the list, as in the example above. For the other lists, the `body` class can be used in custom CSS: see [CSS](https://www.smootify.io/docs/developers/css).
## Other lists
- The visitor’s wishlist: see [Wishlist](https://www.smootify.io/docs/features/wishlist).
- Recently viewed products: see [Recently viewed](https://www.smootify.io/docs/features/last-viewed).
- Filtered lists the visitor can change: see [Filters, sorting and pagination](https://www.smootify.io/docs/build/filters).
## Before you publish
> [!STUCK]
> - **Only 3 products show.** That is the default. Add `limit` with the number you want.
> - **Related or complementary products disappear.** `data-parent-id` is missing or empty. Bind it to the `cms:Shopify ID` field.
> - **Complementary products never show.** No complementary products are set for that product in Shopify's Search & Discovery app.
> - **The metafield list is empty.** The metafield is not a product reference, or its definition has no Storefront access.
> - **The customer's list never shows.** The visitor is not logged in, the metafield is not a customer metafield, or its Customer accounts access is off.
> - **The heading stays when the list is empty.** Put `condition="has-related"` or `condition="has-complementary"` on the section, or use the `body` class for the other lists.
---
# Vendor pages
> One Webflow page per Shopify vendor (brand), from the Vendors collection the sync fills. Covers the template page and its URL, listing the vendor's products, and linking to the page from a product.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/ecommerce/vendors/template
URL: https://www.smootify.io/docs/build/vendor-page
Updated: 2026-10-08
In Shopify, the vendor is a line of text on each product, often the brand. Shopify has no vendor pages, but the Smootify sync creates one item per vendor in your **Vendors** collection, so Webflow can have one. This guide builds the vendor template page and lists the vendor’s products on it.
> [!NOTE]
> Before you start, your products must be synced, with the **Vendor** reference field of the Products collection pointing to the Vendors collection. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
## How it fits together
The sync fills two fields of each vendor item, and each has its own use:
| Field of the vendor item | Filled with | Use it for |
|---|---|---|
| `Name` (such as Acme Studio) | the vendor, as written in Shopify | the page header, and `data-vendor` on `smootify-search-discovery` |
| `Slug` (such as acme-studio) | the same name in lower case, with dashes | the page address, such as `/vendors/acme-studio`, where `product="vendor"` links; never `data-vendor` |
Nothing else comes from Shopify. You can add your own fields in Webflow, such as a logo or a description, and fill them in the CMS.
What Smootify needs:
- one product card per product, as on the [collection page](https://www.smootify.io/docs/build/collection-page);
- for search & discovery, the vendor’s name on `smootify-search-discovery`.
What is up to you: the page layout, the vendor header, the cards, and which way you list the products.
## 1. The template page and its URL
Each vendor page lives at the Vendors collection’s slug followed by the item’s slug, such as `/vendors/acme`. Smootify builds vendor links with `/vendors/` by default. If your Vendors CMS collection has another slug, such as `vendor` on a site built with 1.x, set `vendorsBase` in the [Smootify options](https://www.smootify.io/docs/reference/options) to that slug.
Bind the header to the vendor’s fields as on any Webflow template page.
## 2. List the vendor’s products with a Collection list
1. Add a **Collection list** and connect it to your **Products** collection.
2. In the Collection list settings, add a filter: **Vendor** equals **Current Vendor**.
3. Inside the Collection item, add a `smootify-product` with `data-id` taken from the **Shopify ID** field, and design the card: see [Product cards](https://www.smootify.io/docs/build/product-cards).
The list shows what the CMS holds, in the order set in the Collection list settings.
## 3. Or list them with search & discovery
For filters and sorting on the vendor page, use search & discovery (the Search & discovery add-on). Add `smootify-search-discovery` with the custom attribute `data-vendor`, and take its value from the **Name** field.
```html
```
`data-vendor` needs the **Vendor** filter turned on in Shopify’s Search & Discovery app (Filters › Add filter › Vendor). Without it, Shopify ignores the vendor without an error, and the block lists every product. The debugger tells you when this happens.
The vendor is locked: the visitor’s filters apply within that vendor’s products, and the reset button keeps it. Filters, sorting and pagination are in [Filters, sorting and pagination](https://www.smootify.io/docs/build/filters).
## 4. Link to the vendor page
On a product card or on the product page, `product="vendor"` on a Text link writes the vendor’s name and links to its page. On a link that holds other elements, such as a logo, it sets only the link.
To list all vendors, such as a “Brands” page, use a Collection list of your Vendors collection: it is plain Webflow.
## Before you publish
> [!STUCK]
> - **The vendor page shows every product.** The Collection list has no filter. Add **Vendor** equals **Current Vendor**.
> - **Search & discovery shows every product.** The **Vendor** filter is off in Shopify's Search & Discovery app. Turn it on.
> - **Search & discovery shows no product.** `data-vendor` must be the vendor's name exactly as in Shopify. Bind it to the `cms:Name` field, not the Slug.
> - **Vendor links lead to a page that does not exist.** The slug of your Vendors CMS collection is not `vendors`: set `vendorsBase` in the Smootify options.
> - **A vendor has no page.** Vendor items are created by the sync, from the products. Run the sync after adding a vendor in Shopify.
---
# Build the cart
> The cart as a page or as a mini cart that opens over the page. Covers the line template, totals, quantity and remove, the empty state, errors, checkout, and the five ways a cart can open.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/cart/mini-cart
URL: https://www.smootify.io/docs/build/cart
Updated: 2026-10-10
The cart is one element, `smootify-cart`, with a Webflow Form Block inside it. The same element makes a cart page or a mini cart that opens over the page: only what you wrap the form in changes. This guide builds the cart first, then shows the ways to open it.
Discount codes, notes and progress bars are in [Discounts, notes and progress bars](https://www.smootify.io/docs/build/cart-extras). Product suggestions are in [Cart upsells](https://www.smootify.io/docs/build/cart-upsells).
## How it fits together
What Smootify needs:
- a `smootify-cart` element;
- inside it, a Form Block. Its Form holds the line template (`cart-item`) and the checkout button.
What is up to you:
- where the cart lives: a page of its own, the navbar, or both;
- the layout, the order of the parts, the classes and the styles;
- which parts you show. A mini cart can show only the title and total of each line.
The smallest cart looks like this in the Navigator:
```html
Your cart is empty
Something went wrong
```
## 1. Add the cart and its form
1. Add a Custom element with the tag `smootify-cart`. For a mini cart, put it in the navbar (see [Navbar](https://www.smootify.io/docs/build/navbar)). For a cart page, put it on a page of its own.
2. Inside it, add a Form Block.
Smootify uses the three parts of the Form Block this way:
- **Form**: the lines, the totals and the checkout button. Hidden while the cart is empty.
- **Success message**: the empty cart, shown when there are no lines. Never used as a success message.
- **Error message**: Shopify's errors, written in its text and shown for 5 seconds. Screen readers read them out.
Design the Success message as the empty cart, and leave a text element inside the Error message: Smootify writes each error into it, such as a quantity above the stock. A change that cannot reach Shopify, such as a quantity, a removal, a code, the note or the checkout while the visitor is offline, shows its error there too, and the line comes back from its loading state.
While the cart is empty, `smootify-cart` has the class `is-empty-cart`. You can style it as a combo class.
The cart uses the first form inside `smootify-cart`. A form inside a product card in the cart, such as an upsell, is left alone.
## 2. Design one line
Add a Custom element with the tag `cart-item` inside the Form. It is the template of a line: Smootify copies it once for each line in the cart. Design only one. Any other `cart-item` you add is removed.
Inside it, add a custom attribute to a Webflow element to fill it with a value of the line:
| Attribute | Put it on | What it shows |
|---|---|---|
| `cart-item="title"` | text | the product title |
| `cart-item="variant-title"` | text | the variant, such as “Red / M”; hidden for products without variants |
| `cart-item="image"` | an Image | the variant image (`product-image` for the product’s main image) |
| `cart-item="price"` | text | the price of one item |
| `cart-item="total"` | text | the line total, after the line’s discounts |
| `cart-item="compare-at-price"` | text | the compare-at price; hidden when there is none |
| `cart-item="savings"` | text | what the line saves; hidden when it saves nothing |
| `cart-item="unit-price"` | text | the price per unit, such as €1.50/ 100 g; empty without one |
| `cart-item="url"` | a Link block or Text link | links to the product page |
A Text link with `cart-item="url"` and only text inside also shows the product title. Every value is in [cart line values](https://www.smootify.io/docs/reference/attributes/cart-item).
**Options and extra fields.** A Div block with `cart-item="options"` is repeated once for each option of the variant. Inside it, `option="name"` shows the name (“Size”) and `option="value"` the value (“M”). The same template also lists the extra fields the visitor filled in the add-to-cart form, such as an engraving, and the name of the subscription plan.
**Metafields.** The product’s metafields work inside the line, with the same attributes as on the product page: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).
**While a line changes.** A line shimmers and ignores clicks while its quantity changes or while it is removed. It has the class `updating` or `removing` during that time.
## 3. Quantity and remove
**Quantity.** Inside `cart-item`, add a Custom element with the tag `quantity-input` with a Text field named `quantity` inside it. The minus and plus buttons are optional: they are Custom elements with the tag `button` and `data-action="minus"` or `data-action="plus"`.
- A change updates the line at once. Going down to 0 removes the line.
- Plus stops at the stock or at the product’s quantity rule. `data-disable-max` on `quantity-input` removes that limit.
- With both buttons, the field does not take clicks: the number changes through the buttons. Without buttons, the visitor types the number.
- Some lines cannot change quantity, such as a [Magic Box](https://www.smootify.io/docs/features/magic-box). Their quantity field is removed.
**Remove.** Inside `cart-item`, add a Custom element with the tag `button` and `data-action="remove"`. It removes the line, together with the other lines of its bundle.
## 4. Totals
Add a custom attribute to a text element anywhere inside `smootify-cart`, inside or outside the Form. Here, three items cost €150 at compare-at prices, €120 at their sale prices, and a discount code takes €12 off the order:
| Attribute | What it shows |
|---|---|
| `cart="count"` | the number of items (a line with 3 items counts as 3) |
| `cart="subtotal"` | the amount before the discounts on the whole order |
| `cart="compare-at-total"` | the total at compare-at prices; hidden unless it is higher than the total |
| `cart="savings"` | the compare-at total minus the total; hidden when nothing is saved |
| `cart="total"` | the amount after every discount |
`cart="savings"` replaces what is inside it with the amount, and hides only itself. A label such as “You save” placed next to it stays visible when there is nothing to save.
**Shipping.** For a logged-in customer with an address, Shopify adds a shipping estimate to the total. `cart="shipping"` shows it, so that subtotal, shipping and taxes add up to the total. Without an estimate (no address yet, free shipping, or draft prices) the element is hidden. To show a text instead, such as “Calculated at checkout”, give it the custom attribute `data-fallback` with that text. `data-fallback` works the same on every `cart` value, and an empty one is ignored.
Taxes, and shipping without an address, are calculated at checkout, so the total in the cart is not always the final amount. The discount and gift card lists are in [Discounts, notes and progress bars](https://www.smootify.io/docs/build/cart-extras). Every value is in [cart values](https://www.smootify.io/docs/reference/attributes/cart).
`skeleton="text"` on a total shows a shimmer until the cart loads. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
## 5. Checkout
The checkout button is the Form’s **Form Button**. A click sends the visitor to Shopify’s checkout. Before that, Smootify saves the cart note and the other fields of the cart form (see [cart attributes](https://www.smootify.io/docs/build/cart-extras)). The Form Button’s **Waiting text** setting shows while this happens.
- **Shop Pay.** A Custom element with the tag `smootify-shop-pay` inside `smootify-cart` shows Shopify’s Shop Pay button for the cart. Inside `smootify-product` it shows the button for the chosen variant. Shopify draws the button, and it always fills the element: for a narrower button, give the element a width, not a background or a centred layout.
- **Checkout domain.** Where the checkout opens depends on your domains: see [Domains and checkout](https://www.smootify.io/docs/start/domains).
- **A quote instead of checkout.** See [Quotes and B2B](https://www.smootify.io/docs/features/quotes).
**A link to Shopify’s own cart.** Some Shopify apps work only on the cart page of Shopify’s online store, such as pickup and delivery schedulers like Pickeasy. For them, add the custom attribute `shopify-link="cart"` to a Text link or a Link block, on any page. Smootify points the link to the online store’s cart, on your store’s `myshopify.com` address, with the visitor’s lines and their extra fields, and updates it every time the cart changes. When more than one line has extra fields or is a bundle, the link opens the visitor’s cart itself instead. Any other value is a path of the store’s address: `shopify-link="account"` opens `/account` there.
## 6. A page or a mini cart
A `smootify-cart` with nothing around its form is a cart page: it shows in the page. To make a mini cart, wrap the Form Block in one of these, inside `smootify-cart`:
| Way | Build it with | Opens with | Good to know |
|---|---|---|---|
| A cart page | nothing: the Form Block sits right inside `smootify-cart` | always shown, on a page of its own | open and close buttons do nothing here |
| Webflow Dropdown | a Dropdown, with the Form Block in its list | the Dropdown toggle | opens again when the lines change |
| Dialog | a Custom element with the tag `dialog`; its first child is the panel | an open-cart button | a click on the backdrop and Escape close it |
| Details | a Custom element with the tag `details`, with a `summary` | the `summary` | a click outside closes it |
| Popover | Webflow's Popover element, or a Div block with the attribute `popover` | its trigger, or an open-cart button | stays open while the visitor types |
| Your own interaction | two elements with `data-trigger="open"` and `data-trigger="close"` | your Webflow interaction | Smootify clicks them to open and close the cart |
You don’t tell Smootify which one you use. It looks inside `smootify-cart`, in this order, for a popover, a dialog, a details and a Dropdown. The first it finds decides how the cart opens. Use only one.
**Open and close buttons.** A Custom element with the tag `button` and `data-action="open-cart"` opens the cart; `data-action="close-cart"` closes it (`open` and `close` also work). Both must be inside the same `smootify-cart`. Keep them outside the Form, or give them `type="button"`: inside the Form, a button without it also submits the cart and goes to checkout.
**Open after an add.** Add `data-open="on-add"` to `smootify-cart`. The mini cart then opens each time a product is added, except with Buy now, which goes straight to checkout. On a page that has another `smootify-cart` with no Dropdown and no popover inside it, such as your cart page, `data-open` does nothing.
For developers, [`openCart`](https://www.smootify.io/docs/reference/api/methods/opencart) and [`closeCart`](https://www.smootify.io/docs/reference/api/methods/closecart) open and close it from code, and the [`cart_opened`](https://www.smootify.io/docs/reference/api/events/cart-opened) and [`cart_closed`](https://www.smootify.io/docs/reference/api/events/cart-closed) events tell when it happens.
### In a Webflow Dropdown
The toggle holds the cart icon and the count. The Dropdown list holds the Form Block.
```html
Cart
0
```
A click inside the list does not close it, and Smootify opens it again when the lines change. A Dropdown can still close while the visitor types in the cart’s fields. A popover stays open.
### In a dialog
A dialog is a panel over the page, such as a drawer. Put a Custom element with the tag `dialog` inside `smootify-cart`. Its first child is the panel: design it as you like, such as a column fixed to the right. Everything around the panel is the backdrop.
```html
```
- A click on the backdrop and the Escape key close it.
- The panel fades and scales in. A developer can change the animation: see [Options](https://www.smootify.io/docs/developers/options).
- The backdrop is 80% black. The CSS variable `--smootify-backdrop-color` changes it: see [CSS](https://www.smootify.io/docs/developers/css).
- A dialog is hidden in the Designer. To design it, add the custom attribute `open` with the value `true` while you work, and remove it before you publish: a dialog with `open` shows when the page loads.
### In a details element
Put a Custom element with the tag `details` inside `smootify-cart`. Inside it, a Custom element with the tag `summary` is the trigger, and can hold the count. The element after it is the panel.
A click on the summary opens and closes the cart, and a click outside the details closes it. A close button goes inside the panel.
### In a popover
A popover opens above the page. Escape and a click outside close it, and the browser handles the focus. Use Webflow’s Popover element where your site has it, or a Div block with the custom attribute `popover`.
```html
Your cart is empty
Something went wrong
```
- Webflow’s Popover element comes with its own trigger, which works as it is. With the custom attribute, open it with a `data-action="open-cart"` button as above, or with a button whose `popovertarget` is the panel’s ID.
- The popover stays open while the visitor types in the cart’s fields and while the lines change.
- Smootify does not animate a popover: it keeps the transitions you design.
- Unless the cart is in a dialog or a details, a popover anywhere inside `smootify-cart`, even in a line, makes Smootify open the cart as a popover. Keep other popovers out of the cart.
More in [Popovers](https://www.smootify.io/docs/build/popovers).
### With your own interaction
When none of the above is inside `smootify-cart`, you can build the panel with Webflow interactions. Give one element `data-trigger="open"` and another `data-trigger="close"`, and make interactions that show and hide the panel when they are clicked. When Smootify needs to open or close the cart, for example after an add, it clicks them.
## Before you publish
On your `.webflow.io` address, Smootify shows a debugger button. Its panel lists the elements that are out of place on the page.
> [!STUCK]
> - **Only one line shows, or lines you designed disappear.** Only the first `cart-item` is the template. Design one.
> - **The quantity buttons do nothing.** `quantity-input` is outside the Form, or its buttons are Webflow Buttons, which are links. Use Custom elements with the tag `button`.
> - **A close button goes to checkout.** It is inside the Form without `type="button"`.
> - **The mini cart does not open after an add.** `data-open` is missing, or the page has another `smootify-cart` with no Dropdown and no popover, such as the cart page.
> - **The cart shows open when the page loads.** The dialog still has the `open` attribute you used to design it.
> - **The compare-at total is missing.** It shows only when it is higher than the total: nothing in the cart is on sale or discounted.
## Coming from 1.x
- In 1.x, the open and close buttons worked only with a dialog or a details. In 2.0 they work with every way, and a popover is a new way.
- `cart="compare-at-total"` now adds up every line, including the lines at full price, so the crossed-out total is correct.
---
# Discounts, notes and progress bars
> The parts you add to a cart - discount code and gift card fields, the list of discounts, a note and other fields saved on the order, a free shipping progress bar and a countdown.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/cart/additional-data
URL: https://www.smootify.io/docs/build/cart-extras
Updated: 2026-10-10
This page adds parts to the cart you built in [Build the cart](https://www.smootify.io/docs/build/cart): fields for discount codes and gift cards, the list of discounts, a note and other fields saved with the order, a free shipping progress bar and a countdown. Each part is optional. Add only the ones your store needs.
> [!NOTE]
> These parts need no plan or add-on, except file fields, which need the Server plan.
What the visitor types goes in fields of the cart’s Form, and Smootify reads each field by its **Name**, set in the field settings:
| Field Name | What it is | Field | Applied by | Shows |
|---|---|---|---|---|
| `Coupon` | a discount code | a Text field | a button with `data-action="coupon"`, or Enter | in the list of `cart="discount"` |
| `gift-card` | a gift card | a Text field | a button with `data-action="gift-card"` | in the list of `cart="gift-card"` |
| `Note` | the order note | a Text area or a Text field | saved at checkout | as the note of the order, in Shopify |
| any other name, such as `Delivery day` | a cart attribute | a Text field, Select, Checkbox or Radio button | saved at checkout | in the order's additional details, in Shopify |
## 1. Discount codes
The field and its button go inside the cart’s **Form**.
1. Add a Text field and set its **Name** to `Coupon` in the field settings. This is a Webflow setting, not a custom attribute.
2. Add a Custom element with the tag `button` and `data-action="coupon"`. A click applies the code. Pressing Enter in the field does the same.
3. Optional: inside the button, an element with the custom attribute `data-wait="Applying…"` shows that text while the code is applied.
If the field is empty, the browser asks the visitor to fill it. Once the code is applied, the field is emptied.
**A code in the link.** A link to any page of your site with `?discount=` and a code, such as `/boxes?discount=WELCOME10`, adds that code to the visitor’s cart when the page opens, with no cart element needed on the page: nobody has to type it. Use it in newsletters, ads and referral programs. The code is saved on the Shopify cart, so it stays on every page and visit until it is removed or the order is placed. You can send the link to a visitor whose cart is empty or under the code’s minimum: the code waits on the cart, also while other codes are typed or removed, and applies by itself once the cart qualifies. It shows in the `cart="discount"` list only from then. Removing it, or a new cart after an order, forgets it. A code that does not exist shows nowhere and does no harm at checkout.
**A cart paid on an invoice.** A cart with a chosen price ([name your price](https://www.smootify.io/docs/features/name-your-price), the [product configurator](https://www.smootify.io/docs/features/product-configurator)) or sent as a [quote](https://www.smootify.io/docs/features/quotes) becomes a Shopify draft order. The codes that apply to the cart go with it: the invoice opens with them already applied. Its own field still takes a gift card or another code.
**Show the discounts.** A Div block with `cart="discount"`, anywhere inside `smootify-cart`, is a template, repeated once for each code that applies and once for each automatic discount on the order, such as “10% off orders over $100”. Inside it:
- `discount="title"`: the code, or the name of the automatic discount;
- `discount="amount"`: the amount, such as ”-$10.00”;
- a Custom element with the tag `button` and `data-action="remove"`: removes the code. An automatic discount cannot be removed, so Smootify removes its button.
```html
CODE
-$0.00
```
Smootify gives the remove buttons of the discount and gift card lists `type="button"`, so removing a code does not submit the cart form.
A discount shows in one of two places, depending on what it lowers:
- **On the whole order**, `cart="discount"`: each code that applies and each automatic discount on the order, with `discount="title"`, `discount="amount"` and a remove button inside (an automatic discount loses its button).
- **On one line**, `cart-item="discount"` inside `cart-item`: each discount that lowers the price of that product, with `discount="title"` and `discount="amount"`; already off `cart="subtotal"`, never in the order list.
**Discounts on products.** A code or automatic discount that lowers the price of products shows on the lines, not in this list: `cart="subtotal"` already has it off. To show it on a line, add a Div block with `cart-item="discount"` inside `cart-item`, with `discount="title"` and `discount="amount"` inside it. It is repeated once for each discount on that line.
## 2. Gift cards
Gift cards work the same way as codes, inside the cart’s Form:
- a Text field with the **Name** `gift-card`;
- a Custom element with the tag `button` and `data-action="gift-card"`, which applies it. Pressing Enter in the gift card field does the same;
- a Div block with `cart="gift-card"`: a template repeated once for each gift card applied. Inside it, `gift-card="code"` shows the end of the code, such as ”•••• 4F2A”, and `gift-card="amount"` the amount used, removed when the card covers nothing. A button with `data-action="remove"` removes the card.
A second gift card is added next to the first: it does not replace it.
## 3. A note and other fields
Every field in the cart’s Form is saved with the order when the visitor checks out. In Shopify, you find them on the order, under its additional details.
- **The note.** Add a Text area, or a Text field for a short note, and set its **Name** to `Note`. Its text becomes the order note. When the cart already has a note, the field shows it.
- **Other fields.** Add any Text field, Select field, Checkbox or Radio button, such as a delivery day or a gift wrap option. The field’s **Name** becomes the name of the value saved on the order. These values are called cart attributes.
```html
```
How the fields behave:
- **Saved at checkout.** Smootify saves the note and the fields when the visitor clicks the checkout button, not while they type.
- **Empty fields** are not saved. A Checkbox saves `on` when it is ticked. A Select field that allows several choices saves them in one value, separated by commas.
- **Shown again.** When the cart renders, Text fields, Text areas, Select fields, Checkboxes and Radio buttons show the value already saved in the cart.
- **Required fields.** A field with Webflow’s **Required** setting must be filled before the visitor can check out. The buttons that apply a code or a gift card skip that check.
- **Attributes set by apps stay.** At checkout the form saves only the attributes it has fields for. Cart attributes that an app or a script set, under other names, are kept. A field of the form, even empty, replaces the attribute with its name.
- **Names Smootify keeps for itself.** Fields named `Coupon`, `Note`, `gift-card` or `quantity` are never saved as cart attributes.
**Files.** With the Server plan, a [`file-input`](https://www.smootify.io/docs/reference/elements/file-input) in the cart’s Form uploads a file to Shopify and saves it with the order. See [File uploads](https://www.smootify.io/docs/features/file-uploads).
For developers, [`updateCartNote`](https://www.smootify.io/docs/reference/api/methods/updatecartnote) and [`updateCartAttributes`](https://www.smootify.io/docs/reference/api/methods/updatecartattributes) do the same from code. The cart attributes Smootify writes itself are listed in [cart attributes](https://www.smootify.io/docs/reference/cart-attributes).
## 4. Free shipping progress bar
Add a Custom element with the tag `free-shipping-bar`. It follows the cart wherever it is: in the mini cart, on the cart page or on a product page.
Give it one custom attribute:
- `data-amount="100"`: free shipping from that amount. Smootify compares it with the cart total after discounts.
- `data-quantity="3"`: free shipping from that number of items.
Inside it:
- `data-prop="left"` on a text element: what is still missing, as an amount or a number of items;
- `data-prop="progress-width"` on a Div block: Smootify sets its width to the share of the goal reached, from 0% to 100%;
- `free-shipping-condition="needs-more"` and `free-shipping-condition="free-shipping"`: an element shown before and after the goal is reached.
```html
Add $0.00 for free shipping
You get free shipping
```
With `data-amount="100"`, a cart of €65 and one of €120:
With `data-amount="100"`: a €65 cart shows `free-shipping-condition="needs-more"`, `data-prop="left"` is €35.00 and `data-prop="progress-width"` is 65% wide. A €120 cart shows `free-shipping-condition="free-shipping"`, the bar gets the class `is-free`, and the width goes past 100% (give its track Overflow: hidden).
- The bar is hidden until the cart loads. Once the goal is reached, it has the class `is-free`.
- Past the goal, the width keeps growing above 100%. Set the parent of the bar to **Overflow: hidden**.
- `data-amount` is the same number in every currency: `100` means $100 for one visitor and €100 for another.
- The bar only shows progress. The free shipping itself is a shipping rate or a discount you set up in Shopify, with the same amount.
## 5. Countdown
Add a Custom element with the tag `urgent-cart-countdown`, with `data-time` set to the number of minutes, such as `data-time="15"`. Inside it, `data-prop="minutes"` and `data-prop="seconds"` show the time left.
```html
Your cart is saved for 15 min 0 s
```
- **When it starts.** When the cart has items, the countdown starts and its end time is saved in the cart. It keeps running across pages and reloads, and does not start again when more products are added. It is hidden while the cart is empty.
- **When it ends.** The cart is emptied. With `data-on-end="redirect"`, the visitor also goes to the page in `data-redirect-to`, or to the home page without it. `data-on-end="clear"`, the default, only empties the cart.
- **Where to put it.** The cart is emptied by a page that shows the countdown. Put it in the navbar or the mini cart, so it is on every page.
- **Seconds.** They are written without a leading zero: 5, not 05. A design like “4 min 5 s” reads better than “4:05”.
> [!WARNING]
> When the time runs out, the visitor's cart is really emptied. Use the countdown only for a real limit, such as products held for a set time.
## Before you publish
> [!STUCK]
> - **A discount code seems ignored.** Shopify did not apply it to this cart: the code does not exist, or its conditions are not met. The cart lists only the codes that apply, and shows no message for the others.
> - **The code is saved on the order instead of applied.** The field's Name is not `Coupon`, such as "Discount code". Rename it.
> - **The note is not saved.** The field's Name is not `Note`.
> - **The gift card is not applied, or Enter in its field goes to checkout.** The field's Name is spelled another way. Set it to `gift-card`.
> - **The progress bar spills out of its track.** Past the goal its width goes above 100%. Set the track to Overflow: hidden.
## Coming from 1.x
- The cart attributes that apps or scripts set are kept at checkout. 1.x replaced all the attributes with the fields of the form, and lost the others.
- A Text field named `Note` now saves the order note too. 1.x saved it only from a Text area.
- `cart="discount"` now also lists the automatic discounts on the order, without a remove button.
- `urgent-cart-countdown` shows the total minutes: 75 minutes left shows 75, where 1.x showed 15. It no longer needs the easytimer.js script.
---
# Cart upsells
> Product suggestions based on what the visitor just added to the cart, shown in the mini cart, on the cart page or anywhere on the page.
Status: Ready (Smootify 2.0)
Plan: Lite plan
1.x page: https://docs.smootify.io/docs/cart/cart-upsells
URL: https://www.smootify.io/docs/build/cart-upsells
Updated: 2026-10-01
Cart upsells suggest products that go with what the visitor just added. Smootify asks Shopify for the recommendations of the product added most recently, leaves out the products already in the cart, and fills a product card you design. The suggestions change as the cart changes.
> [!NOTE]
> Cart upsells need the Lite or Server plan, or the **Cart Upsell** extension activated in the Smootify dashboard. On your `.webflow.io` address they work without it, so you can build and test first.
## How it fits together
What Smootify needs:
- a `smootify-product` element with `data-id="cart-upsells"`. It is the template of one card. It needs no product id and no CMS.
What is up to you:
- where the suggestions go: inside the mini cart, on the cart page, or anywhere else on the page;
- the card design: image, title, price, an add button;
- a heading or a wrapper around the cards, shown only when there is something to suggest.
```html
```
## 1. Add the card template
1. Add a Div block where the suggestions should appear, such as under the lines of the mini cart. The cards are placed inside it, next to each other, so style it as a grid or a row.
2. Inside it, add a Custom element with the tag `smootify-product` and the custom attribute `data-id="cart-upsells"`.
3. Design one card inside it. Everything a product card can show works here: see [Product cards](https://www.smootify.io/docs/build/product-cards).
Smootify copies the card once for each suggestion. On the live site, the card you designed is replaced by the suggestions, and nothing shows while there are none. Each card gets its product id and `cart-upsell="true"`: Smootify writes both, so don’t add them yourself.
**How many.** `limit` on `smootify-product` sets the most cards it shows. The default is 3. Smootify asks Shopify for at most 10 recommendations of each kind, so a list of one kind never shows more than 10.
## 2. Choose which suggestions
The value of `data-id` decides which recommendations Shopify gives:
| `data-id` | What it shows | Who chooses the products |
|---|---|---|
| `cart-upsells` | complementary products first, then related products | you, then Shopify |
| `cart-complementary` | complementary products only | you, for each product, in Shopify's Search & Discovery app; without them, the list shows nothing |
| `cart-related` | related products only | Shopify; you can adjust them in Shopify's Search & Discovery app |
The suggestions always come from the product added most recently. When the visitor adds a suggestion, that product becomes the latest one, and the suggestions change to match it.
You can use several lists on the same page, such as related products in the mini cart and complementary products on the cart page. Each list is filled on its own.
## 3. The add button
Add a Custom element with the tag `button` and `data-is="direct-add-to-cart"` inside the card. A click adds one item of the product’s current variant, without a form.
- It is disabled when that variant cannot be bought.
- The elements inside it with `data-state="default"` and `data-state="loading"` swap while the product is added.
- The cart stays open and shows the new line.
- A picker inside the card, such as [`variant-swatches`](https://www.smootify.io/docs/reference/elements/variant-swatches), lets the visitor choose the variant first.
Use this button inside the cart. An add-to-cart form cannot go there, because Webflow does not put a Form Block inside another Form Block. A full [`smootify-add-to-cart`](https://www.smootify.io/docs/reference/elements/smootify-add-to-cart) form works for suggestions placed outside the cart’s Form.
## 4. Hide the section when it is empty
An element with `condition="has-cart-upsells"` shows only while at least one list on the page has a card. Put it on the wrapper with the heading, so “You may also like” does not stay on screen alone. It works anywhere on the page. See [page conditions](https://www.smootify.io/docs/reference/conditions/page).
## Before you publish
> [!STUCK]
> - **Suggestions show on `.webflow.io` but not on your domain.** The plan does not include Cart Upsell. Use the Lite or Server plan, or activate the extension in the Smootify dashboard.
> - **`cart-complementary` shows nothing.** The product has no complementary products. Add them in Shopify's Search & Discovery app.
> - **No suggestions at all.** The cart is empty, or every recommended product is already in the cart.
> - **The heading stays when there are no suggestions.** Add `condition="has-cart-upsells"` to the wrapper.
> - **You cannot add an add-to-cart form to the card.** The card is inside the cart's Form. Use the `direct-add-to-cart` button.
---
# Customer accounts
> Let customers log in on your Webflow site with Shopify's customer accounts. Covers the Shopify set-up, the key Smootify needs, login and logout, the customer's details, the customer conditions and the pages only logged-in customers can open.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/accounts/how-to-enable
URL: https://www.smootify.io/docs/build/account
Updated: 2026-10-10
Customers log in on your Webflow site with Shopify’s customer accounts. There is no password: Shopify sends a one-time code by email. This page sets up the Shopify side and the key Smootify needs, then covers login and logout, the customer’s details, the elements shown only to some customers, and the pages only logged-in customers can open.
> [!NOTE]
> Smootify 2.0 works with Shopify's **new customer accounts**. Shopify has deprecated its classic customer accounts and will remove them soon. You also need a Headless storefront in Shopify: see [Set up the Shopify store](https://www.smootify.io/docs/start/shopify-store).
## How it fits together
What Smootify needs:
- new customer accounts turned on in Shopify;
- the client id of your Headless storefront’s Customer Account API, in the option `newCustomerAccountsPublicKey`;
- a `passwordless-login` form, where customers log in.
What is up to you:
- where the login goes: the navbar, a page of its own, or both;
- which account pages you build, and their layout. Every part of them is optional.
The parts an account can have, each with its own guide:
| Part | What it shows | Elements |
|---|---|---|
| [Addresses](https://www.smootify.io/docs/build/addresses) | saved addresses, with forms to add, edit and delete them | `customer-addresses`, `create-address`, `edit-address`, `delete-address` |
| [Orders](https://www.smootify.io/docs/build/orders) | past orders, a page for each order, and Buy again | `customer-orders`, `order-page`, `line-item`, `order-fulfillment` |
| [Subscriptions](https://www.smootify.io/docs/build/account-subscriptions) | subscriptions, a page for each one, and forms to pause, resume and cancel them | `customer-subscriptions`, `subscription-page`, `subscription-pause`, `subscription-activate`, `subscription-cancel` |
| [Customer data](https://www.smootify.io/docs/build/customer-data) | the name, customer metafields, the email subscription and store credit | `customer-user-update`, `customer-metafields-editor`, `customer-subscribe-email`, `customer-unsubscribe-email`, `store-credit` |
You do not have to build account pages at all. Shopify has its own account pages, and you can link to them from any Webflow link. Shopify gives their address under **Settings › Customer accounts**.
## 1. Turn on customer accounts in Shopify
The Shopify screens, in order, numbered as the steps below:
1. **Settings › Customer accounts**: choose the new customer accounts. The classic customer accounts are deprecated by Shopify.
2. **Headless › your storefront › Customer Account API settings**: set the **Client type** to **Public** (step 3) and turn on all the **permissions** (step 4).
3. **Application setup**, on the same page: **JavaScript origins** `https://www.example.com` and `https://example.webflow.io` (step 5), and **Callback URIs** with the same two addresses (step 6), each without a slash at the end.
4. **Client ID**: copy it (step 7). It is the public key Smootify needs, for the option `newCustomerAccountsPublicKey`.
1. In Shopify, go to **Settings › Customer accounts** and choose the new customer accounts.
2. Open the **Headless** channel and your storefront, then its **Customer Account API** settings.
3. Set the **Client type** to **Public**.
4. Turn on all its permissions.
5. Under **JavaScript origins**, add the address of your site without a slash at the end, such as `https://www.example.com`. Add your `.webflow.io` address too, so you can log in while you build.
6. Under **Callback URIs**, add the address Shopify sends customers back to after the login. By default it is your home page, written the same way: `https://www.example.com`, and your `.webflow.io` address.
7. Copy the **Client ID**. This is the public key Smootify needs.
Shopify’s login page shows your store’s logo and colours. You set them in Shopify’s checkout settings.
## 2. Give Smootify the key
Put the Client ID in Smootify’s options. The options go in your site’s head code, in a script **before** the Smootify loader: see [Install Smootify 2.0](https://www.smootify.io/docs/start/install). If the site already has an options script, add the key to it. A second script replaces the first.
```html
```
Without the key, the login does nothing and the account elements remove themselves from the page.
**Where the login lands.** After the login, Shopify sends the customer back to your home page. To send them somewhere else, set `newCustomerLoginRedirect` to that address, and add the same address to the **Callback URIs** in Shopify. With `newCustomerLoginRedirect: "same-page"`, customers go back to the page where they started the login. The callback is then your home page address.
## 3. Log in and log out
**Log in.** Add a Custom element with the tag `passwordless-login`, and put a Webflow **Form Block** inside it. The Form needs only a Form Button, such as “Log in”. A click takes the customer to Shopify’s login page, where they type their email and the code Shopify sends them. Then Shopify sends them back to your site, logged in.
There is no separate sign-up. A customer who has no account yet creates one in the same step, so “Log in” and “Create account” can be the same form.
**Log out.** Add a Custom element with the tag `logout-form`, with a Form Block and a Form Button inside it. It ends the session on your site and in Shopify. The browser also forgets the cart, so the next visitor on that device starts with an empty cart.
**How long the login lasts.** The customer stays logged in until they log out or Shopify ends the session, such as when it expires. If Shopify cannot be reached for a moment, the page shows the visitor as not logged in, and the next page tries again with the same session: there is no new login to do.
**Show one or the other.** Wrap the login in an element with `customer-condition="not-logged-in"`, and the logout and account links in one with `customer-condition="logged-in"`:
```html
Redirecting…
Something went wrong
Hi, there
Logged out
Something went wrong
```
In a navbar, the same parts work as in [Navbar: cart, search and account](https://www.smootify.io/docs/build/navbar).
## 4. Show the customer’s details
Add a custom attribute to a Text block or a Heading to fill it with a detail of the logged-in customer. These work anywhere on any page, with no wrapper.
| Attribute | What it shows |
|---|---|
| `customer="first-name"`, `customer="last-name"` | the first name, the last name |
| `customer="display-name"` | the name Shopify shows for the customer |
| `customer="email"` | the email; on a link, the link opens an email to it |
| `customer="phone"` | the phone number; on a link, the link calls it |
| `customer="defaultAddress"` | the default address, one line under the other |
| `customer="tags"` | one copy of the element per customer tag |
| `company="name"` | the company of a B2B customer |
- **Tags.** `exclude="Wholesale, Staff"` on the `customer="tags"` element leaves those tags out.
- **Company.** Every `company` element is taken out when the customer has no company.
- **Logged out.** For a visitor who is not logged in, these elements are taken out of the page. They come back after the login.
- **While it loads.** Until Smootify knows who the visitor is, these elements show as a placeholder shimmer.
- **A value that is missing.** `data-fallback="there"` on the element keeps it and shows that text when the customer has no value, such as a customer without a first name: `Hi, name`. It works on every element Smootify fills, product and metafield values included. To show or hide a whole block instead, use `customer-condition="has-names"` / `"no-names"`.
The full list is in [customer values](https://www.smootify.io/docs/reference/attributes/customer). To show the customer’s metafields, see [Customer data and newsletter](https://www.smootify.io/docs/build/customer-data).
```html
Hello, name
email@example.com
Default address
```
## 5. Show or hide by customer
An element with `customer-condition="…"` shows only while the customer is in that state. This is separate from Webflow’s conditional visibility. The conditions come in pairs, one for each state:
| Condition | Shown when the customer |
|---|---|
| `logged-in` / `not-logged-in` | is logged in / is not logged in |
| `has-names` / `no-names` | has both a first and a last name / lacks one of them |
| `has-addresses` / `no-addresses` | has saved addresses / has none |
| `has-orders` / `no-orders` | has orders / has none (only on a page with a `customer-orders` list, which counts the orders) |
| `has-subscriptions` / `no-subscriptions` | has subscriptions / has none (only on a page with a `customer-subscriptions` list) |
| `email-subscribed` / `email-not-subscribed` | is subscribed to your email marketing / is not |
**Logged-out visitors.** The conditions after the first pair are for logged-in customers. Use them on account pages, or inside an element with `customer-condition="logged-in"`.
**Customer tags.** `customer-tagged="vip"` shows an element only to customers with that tag. With several tags, `customer-tagged="vip, gold"`, the customer needs all of them. Add `any="true"` to show it when the customer has at least one, or `exclusive="true"` to show it only when the customer has exactly those tags and no other. `customer-not-tagged="…"` does the opposite, and reads `any="true"` too. For a visitor who is not logged in, both are taken out of the page.
Every condition is in [customer conditions](https://www.smootify.io/docs/reference/conditions/customer) and [customer tag conditions](https://www.smootify.io/docs/reference/conditions/customer-tags).
> [!WARNING]
> These conditions hide elements on the page, but what they hide is still in the published page's code. Never use them to hide private information.
## 6. Pages for logged-in customers
Pages inside a Webflow page folder with the slug `account`, such as `/account/orders`, are for logged-in customers only. A visitor who is not logged in is sent to your home page. Only the folder’s own page and the pages inside it count: `/accounts` or `/accounting/rates` stay open to everyone.
- **Other languages.** With Webflow Localization, the same pages in each language are covered, such as `/fr/account/orders`.
- **Pages that stay open.** A page in the folder whose address contains `login`, `register`, `reset`, `activate` or `recover` is open to everyone.
- **Where visitors go.** `accountPageRedirectTo` sends them to another address, such as `"/login"`. With `accountPageRedirectTo: "passwordless-login"`, they go straight to Shopify’s login page. That needs `newCustomerAccountsPublicKey`: without it no login can open, so they go to the home page, and on your `.webflow.io` address the debugger points out the missing key.
- **Another folder.** If your account pages are in a folder with another slug, set it in `accountBase`, such as `accountBase: "my-account"`.
These options go in the same options script as the key:
```html
```
Two pages have a fixed address by default: the order page at `/account/order` and the subscription page at `/account/subscription`. See [Orders and buy again](https://www.smootify.io/docs/build/orders) and [Subscriptions in the account](https://www.smootify.io/docs/build/account-subscriptions). Every option is in the [options reference](https://www.smootify.io/docs/reference/options).
## Common questions
- **Can I import customers from another platform?** Yes, their emails, with Shopify’s customer import. Passwords cannot be imported, and none are needed: customers log in with a one-time code sent to their email.
- **Can I change Shopify’s own account pages?** Shopify’s login page and its account pages (orders, profile) take your logo and colours, a menu and app blocks, set in Shopify’s checkout and accounts editor (**Settings › Checkout**, then **Customize**). Nothing else of their design changes.
- **Do I have to use Shopify’s account pages?** No. You can build every account page in Webflow, as on this page. Apps that add blocks to customer accounts, such as loyalty or returns apps, work only in Shopify’s account pages: link to them from your Webflow pages when you need one.
## Before you publish
Log in on your `.webflow.io` address to see the account pages with real data. Press Alt+Shift+D (Option+Shift+D on a Mac) there to turn on Smootify’s debugger (on a phone, add `?smootify-debug` to the address): its panel lists the account elements that are not running, and why. See [The debugger](https://www.smootify.io/docs/start/debugger).
> [!STUCK]
> - **Login fails, or Shopify does not send the customer back.** Most often the Callback URIs or the JavaScript origins in Shopify do not match the site's address. Add the address exactly, as `https://www.example.com`, to both.
> - **Log in does nothing.** `newCustomerAccountsPublicKey` is missing, or its script comes after the Smootify loader. Put it in the options script before the loader.
> - **Login works on one address and not on the other.** Each address you use, the `.webflow.io` one and your own domain, must be in both the JavaScript origins and the Callback URIs in Shopify.
> - **A visitor who is not logged in sees an account page.** The page is outside the `account` folder, such as `/account-orders`: move it into the folder, `/account/orders`.
> - **Back from Shopify's login, the customer is still logged out.** A second login was started in another tab before the first one finished, and only the last login started counts. Log in again from one tab.
> - **An account page sends you to the home page.** You are not logged in, and the pages in the `account` folder are for logged-in customers. Log in first, or change `accountPageRedirectTo`.
> - **`has-orders` and `no-orders` both show.** The page has no `customer-orders` list, which is what counts the orders.
> - **The default address stays empty.** The value is `customer="defaultAddress"`, in one word. `default-address` is not read.
> - **The login and register forms disappeared after the move to 2.0.** They were classic account forms. Replace them with one `passwordless-login`.
## Coming from 1.x
- `login-form`, `register-form`, `recover-form`, `reset-form` and `activate-form` remove themselves from the page. One `passwordless-login` replaces them all. The links in classic account emails, such as `/account/activate/…` and `/account/reset/…`, are no longer handled.
- A store still on classic accounts moves to the new ones in Shopify first, then sets `newCustomerAccountsPublicKey`.
- Visitors stay logged in when the site moves to 2.0.
---
# Addresses
> Show the logged-in customer's saved addresses and let them add, edit and delete them. Covers the address list, the field names of the address forms, and the country and province selects.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/accounts/components-attributes
URL: https://www.smootify.io/docs/build/addresses
Updated: 2026-10-09
Customers keep their shipping addresses in their Shopify account. This page builds the list of saved addresses, with an edit and a delete form in each row, and the form that adds a new address. Smootify fills the country and province menus for you.
> [!NOTE]
> Customer accounts must be set up first: see [Customer accounts](https://www.smootify.io/docs/build/account). Put these parts on an account page, since they need a logged-in customer.
## How it fits together
What Smootify needs:
- for the list: a `customer-addresses` element with one `customer-address` inside, the template of each row;
- for each form: a Form Block with a Select field named `countryCode` (the country) and one named `zoneCode` (the province);
- the edit and delete forms inside the row, so they know which address they change.
What is up to you:
- the design of a row, and which details it shows;
- which fields the forms have, apart from the two Select fields;
- where the forms go: in the row, in a popover inside the row, or in a section of their own for the new address.
## 1. List the addresses
1. Add a Custom element with the tag `customer-addresses`.
2. Inside it, add a Custom element with the tag `customer-address`. Design it as one row: Smootify repeats it once per address.
3. Inside the row, add a custom attribute to each Text block that shows a part of the address.
```html
Name, street, city
Deleted
Something went wrong
```
| Attribute | What it shows |
|---|---|
| `address="formatted"` | the whole address, with the name, one line under the other |
| `address="name"` | the full name |
| `address="firstName"`, `address="lastName"` | the first name, the last name |
| `address="company"` | the company |
| `address="address1"`, `address="address2"` | the street, the second address line |
| `address="city"`, `address="zip"` | the city, the postal code |
| `address="zoneCode"`, `address="countryCode"` | the province and country codes, such as `CA` and `US` |
| `address="phoneNumber"` | the phone number |
**The default address.** The row of the customer’s default address gets the class `is-default`. Inside a row, `address-condition="default"` shows an element only on the default address, such as a “Default” pill, and `address-condition="not-default"` only on the others, such as a “Make default” button.
- **Write the values as in the table**, such as `firstName`. A value written with a dash, such as `first-name`, is not read.
- **Empty parts.** An element whose part of the address is empty is taken out of that row.
- **How many.** The list shows up to 10 addresses.
- **No addresses.** Show an empty state with `customer-condition="no-addresses"`, and hide a heading with `customer-condition="has-addresses"`.
- **The default address.** `customer="defaultAddress"` shows it anywhere on the page: see [Customer accounts](https://www.smootify.io/docs/build/account).
For a visitor who is not logged in, the list removes itself. All the values are in [address values](https://www.smootify.io/docs/reference/attributes/address).
## 2. The fields of an address form
The add form and the edit form use the same fields. Each field’s **Name** setting tells Smootify what it holds. The Name is a setting of the field, not a custom attribute. Write it exactly as shown here:
| Name | Field | What it holds |
|---|---|---|
| `firstName`, `lastName` | Text field | the first and last name |
| `company` | Text field | the company |
| `address1`, `address2` | Text field | the street and the second address line |
| `city`, `zip` | Text field | the city and the postal code |
| `phoneNumber` | Text field | the phone; Smootify removes its spaces |
| `countryCode` | Select field | the country (required) |
| `zoneCode` | Select field | the province or state |
| `defaultAddress` | Checkbox | makes this the default address when ticked |
When Shopify refuses an address, its message shows in the form’s Error message.
**The country menu.** Smootify fills the `countryCode` Select field with the countries your store ships to, named in the site’s language and in alphabetical order. An option without a value that you add in the Designer, such as “Choose a country”, stays at the top. The customer must choose a country.
**The province menu.** When the customer picks a country, Smootify loads its provinces or states into the `zoneCode` Select field. The options you add to it in the Designer are removed. Many countries have no provinces. Put the province menu and its label inside a Div block with `address-condition="zone"`, so they only show when they are needed:
| Country | Province menu (`zoneCode`) | Div block with `address-condition="zone"` | Form |
|---|---|---|---|
| with provinces, such as United States | filled with its provinces or states | shown | no `no-zone` class |
| without provinces, such as Germany | empty, and still in the form | hidden | gets the class `no-zone` |
The Select field must stay in the form either way.
## 3. Add an address
Add a Custom element with the tag `create-address` and put a Form Block inside it, with the fields above. It can go on the same page as the list, or anywhere else.
```html
Address saved
Something went wrong
```
When the address is saved, the Success message shows, and the page reloads a second later, so the list shows the new address.
## 4. Edit and delete in the row
**Edit.** Inside `customer-address`, add a Custom element with the tag `edit-address`. Build its Form Block like the add form, with the same fields. Smootify fills the fields with the address of that row, so the customer changes only what they need. When it is saved, the page reloads. Outside a row, the form has no address to fill in.
**Delete.** Inside `customer-address`, add a Custom element with the tag `delete-address`, with a Form Block and a Form Button, as in the list above. It deletes the address of that row and removes the row.
- **The default address cannot be deleted.** Smootify removes the delete form from its row. To delete it, make another address the default first.
- **Changing the default.** The `defaultAddress` Checkbox makes an address the default when it is ticked. Left unticked, the default address stays as it is.
- **A “Make default” button.** For a one-click button in the row, put an `edit-address` with only a hidden field and a submit: ``. Without a Country field, the form changes only which address is the default and leaves every field of the address as it is. Show it with `address-condition="not-default"`.
- **In a popover.** The edit form can go in a popover inside the row. Each copy of the row gets its own popover.
## Where people get stuck
> [!STUCK]
> - **The address form disappears.** It has no Form, or no Select field named `countryCode` or `zoneCode`. Both Select fields are required, even when the province menu is hidden.
> - **A country is missing from the menu.** The menu lists only the countries your store ships to. Add the country to your shipping settings in Shopify.
> - **The edit form is empty.** It is outside `customer-address`, so it does not know which address to show.
> - **One row has no delete button.** It is the default address, which cannot be deleted.
> - **A value in the row stays empty.** It is written with a dash, such as `address="first-name"`. Write `address="firstName"`.
## Coming from 1.x
- The country Select field is named `countryCode`. 1.x named it `territoryCode`, which Shopify has deprecated: rename it, or the form is removed. On your `.webflow.io` address the debugger points out a Select field still named `territoryCode`.
- The country menu shows the countries in the site’s language, in alphabetical order. 1.x showed them in English, in Shopify’s order.
- The provinces load when the customer picks a country.
- Editing the default address without ticking the checkbox now saves. In 1.x it failed.
---
# Orders and buy again
> Show the logged-in customer's orders. Covers the order list and its pages, the order page with its products and shipments, status labels, printing, and the Buy again button that puts an order back in the cart.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/accounts/order-listing
URL: https://www.smootify.io/docs/build/orders
Updated: 2026-10-10
Customers see their past orders in their account. This page builds the order list, the page of a single order with its products and shipments, and a Buy again button that puts the products of an order back in the cart.
> [!NOTE]
> Customer accounts must be set up first: see [Customer accounts](https://www.smootify.io/docs/build/account). Put these parts on account pages, since they need a logged-in customer.
## How it fits together
What Smootify needs:
- for the list: a `customer-orders` element with one `customer-order` inside, the template of each order;
- for the order page: a page at `/account/order` with an `order-page` element on it;
- inside an order, a `line-item` for its products and an `order-fulfillment` for its shipments, if you show them.
What is up to you:
- which details each order shows, and the design;
- whether you build an order page at all. `order="statusUrl"` can link to Shopify’s own page for the order instead.
## 1. The order list
1. Add a Custom element with the tag `customer-orders`.
2. Inside it, add a Custom element with the tag `customer-order`. Design it as one order: Smootify repeats it once per order, newest first.
3. Inside it, add the order values of step 2, and a link to the order page with `order="url"`.
```html
#1001
1/1/2026
Status
$0.00
View order
```
- **How many.** `limit` on `customer-orders` sets the orders per page. Without it, the list shows 5.
- **No orders.** Show an empty state with `customer-condition="no-orders"` on the same page. The list is what counts the orders, so this condition works only on a page that has it. If the list cannot load, neither `has-orders` nor `no-orders` shows, and a next page that fails to load leaves the orders already on screen.
- **While it loads.** Add `skeleton="text"` or `skeleton="block"` to the placeholders you want to shimmer until the orders arrive. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
**More orders.** Pagination buttons are Custom elements with the tag `button`, inside `customer-orders` and outside `customer-order`. There are two kinds:
- **Load more**, a `button` with `data-action="load"`: adds the next orders below the ones shown; hidden when there are no more orders.
- **Previous and next**, `button` elements with `data-action="previous"` and `data-action="next"`: show the previous or the next page of orders instead; each one is hidden when there is no page in its direction.
## 2. Order values
These work in a row of the list and on the order page.
| Attribute | What it shows |
|---|---|
| `order="name"` | the order name, such as `#1001` |
| `order="processedAt"` | the order date, in the visitor’s date format |
| `order="financialStatus"` | the payment status |
| `order="fulfillmentStatus"` | the shipping status: `UNFULFILLED` until the first shipment, then that shipment’s status |
| `order="subtotalPrice"`, `order="totalShipping"` | the subtotal, the shipping |
| `order="totalTax"`, `order="totalPrice"` | the taxes, the total |
| `order="totalDiscounts"` | the discounts of the order, on its lines and on shipping; removed when the order has none |
| `order="totalRefunded"` | the amount refunded |
| `order="shippingAddress"`, `order="billingAddress"` | the addresses, one line under the other |
| `order="note"`, `order="email"`, `order="phone"` | what the customer entered at checkout |
| `order="cancelReason"` | why the order was cancelled |
Three values go on a Text link or a Link block:
- `order="url"`: your order page for this order;
- `order="statusUrl"`: Shopify’s own page for this order, in the customer’s Shopify account;
- `order="customerUrl"`: the customer’s profile in their Shopify account.
An element whose value is missing, such as a cancel reason on an order that was not cancelled, is taken out. The full list is in [order values](https://www.smootify.io/docs/reference/attributes/order).
**Status labels.** The statuses show Shopify’s codes, such as `PAID`. To show your own words, add a script with the labels to the head code of your site, next to the options script. Write every label in the same script:
```html
```
The keys are the codes as Shopify sends them. The same script holds the statuses of [subscriptions](https://www.smootify.io/docs/build/account-subscriptions). The codes of each status:
| Value | Codes | Without a label |
|---|---|---|
| `order="financialStatus"` (payment) | `PENDING`, `AUTHORIZED`, `PAID`, `PARTIALLY_PAID`, `PARTIALLY_REFUNDED`, `REFUNDED`, `VOIDED`, `EXPIRED` | the code, such as `PENDING` |
| `order="fulfillmentStatus"` (shipping) | `UNFULFILLED` until the first shipment, then that shipment's `SUCCESS`, `CANCELLED`, `ERROR` or `FAILURE` | the code, such as `UNFULFILLED` |
| `subscription="status"` | `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED`, `FAILED`, `STALE` | the code with a capital letter only at the start, such as Paused |
Example labels: `PENDING: "Awaiting payment"`, `UNFULFILLED: "Being prepared"`, `PAUSED: "On hold"`.
## 3. The order page
1. In Webflow, create a page in the `account` folder with the slug `order`, so its address is `/account/order`. For another address, set it in the option `orderPageUrl`.
2. Add a Custom element with the tag `order-page`, and put the order values inside it.
The page shows the order that `order="url"` links to: the link adds the order number to the address. A visitor who is not logged in gets the same treatment as on the other account pages: with the option `accountPageRedirectTo: "passwordless-login"`, the Shopify login opens and then comes back to the same order, so a link to an order in an email works. Opened by a logged-in customer without an order in the address, or for an order that is not theirs, the page goes to your home page.
**Products.** Inside `order-page`, add a Custom element with the tag `line-item`. Design it as one product: Smootify repeats it once per product of the order. Design only one: the other copies you make are removed.
```html
#1001
Product
Size: M
1
$0.00
$0.00
```
Inside `line-item`:
- `line-item="name"`, `line-item="title"`, `line-item="sku"`, `line-item="vendor"`, `line-item="quantity"`: texts;
- `line-item="image"` on an Image, taken out when the product has no image;
- `line-item="price"`, `line-item="totalPrice"`, `line-item="totalDiscount"`: amounts;
- `line-item="option"` on a Div block: one copy per option and per property of the line, such as a size or an engraving, with `option="name"` and `option="value"` inside. Properties whose name starts with `_` are left out.
The full list is in [order line values](https://www.smootify.io/docs/reference/attributes/line-item).
**Shipments.** Add a Custom element with the tag `order-fulfillment`, the template of one shipment. Inside it, these values fill a shipment:
| Attribute (inside `order-fulfillment`) | What it shows |
|---|---|
| `fulfillment="status"` | the status of the shipment, in lowercase: success, cancelled, error or failure |
| `fulfillment="latestShipmentStatus"` | the latest delivery status, such as in transit or delivered, in lowercase |
| `fulfillment="estimatedDeliveryAt"` | the expected delivery date, or `TBD` when Shopify does not know it |
| `fulfillment="trackingCompany"` | the carrier |
| `fulfillment="trackingNumber"` | the tracking number |
| `fulfillment="trackingUrl"` | on a link: the tracking page; taken out when there is none |
| `fulfillment="updatedAt"` | the date of the last update |
Wrap the shipments section in an element with `order-condition="has-fulfillments"`: it hides while the order has no shipment.
```html
```
**Printing.** To print the order, wrap the part to print in a Custom element with the tag `printable-element`, and give it an ID in its element settings, such as `order`. A Custom element with the tag `print-button` and `data-target="order"` prints that part alone. See [`print-button`](https://www.smootify.io/docs/reference/elements/print-button).
The rows of the order list can hold `line-item` and `order-fulfillment` too, for example to show small product images in each order.
## 4. Buy again
A Custom element with the tag `button` and `data-action="buy-again"` puts the products of an order back in the cart, each with its variant, its quantity and its properties, such as an engraving or an uploaded file. It works inside `customer-order`, in the list, and inside `order-page`.
```html
```
- **Straight to checkout.** Add `data-buy-now="true"` to the button to send the customer to checkout instead of the cart.
- **While it works.** The elements inside the button with `data-state="default"` and `data-state="loading"` swap, and the button gets the class `is-loading`. Then the mini cart opens, as after any addition.
- **What stays out.** Products that no longer exist, the products of a Shopify bundle (the order does not say which bundle they formed), and products priced by the [product configurator](https://www.smootify.io/docs/features/product-configurator) or [name your price](https://www.smootify.io/docs/features/name-your-price). A product Shopify refuses, such as a sold-out one, is not added either.
- **Subscriptions** come back as one-time purchases.
- **Nothing to buy.** When nothing in the order can be bought again, the button hides itself.
## Before you publish
> [!STUCK]
> - **The order page sends you to the home page.** It was opened without an order in its address, or by a customer the order does not belong to. Open it from an `order="url"` link. A visitor who is not logged in goes to the home page too unless `accountPageRedirectTo` is `"passwordless-login"`.
> - **The order link leads to a missing page.** The order page is not at `/account/order`. Move it there, or set `orderPageUrl`.
> - **Statuses show codes such as `PAID`.** Add the `SmootifyStatusLabels` script with your words.
> - **Buy again or the pagination does nothing.** It is a Webflow Button, which is a link. Use a Custom element with the tag `button`.
> - **The Buy again button is missing on an order.** Nothing in that order can be bought again: its products are deleted, part of a bundle, or had a custom price.
> - **The products you designed disappear.** Only the first `line-item` is the template. Design one; Smootify repeats it.
## Coming from 1.x
- The order rows, their children and their images no longer shimmer on their own while loading. Add `skeleton` to the placeholders you want.
- Buy again is new in 2.0.
---
# Subscriptions in the account
> Let customers see and manage their subscriptions on your site. Covers the subscription list, the subscription page with its products, status labels, and the forms that pause, resume and cancel a subscription.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/accounts/subscription-listing
URL: https://www.smootify.io/docs/build/account-subscriptions
Updated: 2026-10-10
Customers who buy by subscription manage it from their account on your site: they see what they get and when, and they pause, resume or cancel it without writing to you. This page builds the subscription list, the page of a single subscription, and the three forms that change it.
> [!NOTE]
> Customer accounts must be set up first: see [Customer accounts](https://www.smootify.io/docs/build/account). The subscriptions come from a Shopify subscription app that creates the selling plans: see [Subscriptions](https://www.smootify.io/docs/features/subscriptions).
## How it fits together
What Smootify needs:
- for the list: a `customer-subscriptions` element with one `customer-subscription` inside, the template of each subscription;
- for the subscription page: a page at `/account/subscription` with a `subscription-page` element on it;
- the pause, resume and cancel forms inside `customer-subscription` or `subscription-page`, so they know which subscription they change.
What is up to you:
- which details you show, and the design;
- where the forms go: in each row of the list, on the subscription page, or both.
## 1. The subscription list
1. Add a Custom element with the tag `customer-subscriptions`.
2. Inside it, add a Custom element with the tag `customer-subscription`. Design it as one subscription: Smootify repeats it once per subscription, newest first.
3. Inside it, add the values of step 2, and a link to the subscription page with `subscription="url"`.
```html
#1001
Active
Next charge: 1/1/2026
Manage
```
- **How many.** `limit` on `customer-subscriptions` sets the subscriptions per page. Without it, the list shows 5.
- **More subscriptions.** Pagination buttons work as in the order list: Custom elements with the tag `button` and `data-action="previous"`, `"next"` or `"load"`, inside `customer-subscriptions` and outside the row. See [Orders and buy again](https://www.smootify.io/docs/build/orders).
- **No subscriptions.** Show an empty state with `customer-condition="no-subscriptions"` on the same page. It works only on a page that has the list. If the list cannot load, neither `has-subscriptions` nor `no-subscriptions` shows, and a next page that fails to load leaves the subscriptions already on screen.
- **While it loads.** Add `skeleton` to the placeholders you want to shimmer. See [loading](https://www.smootify.io/docs/reference/attributes/loading).
## 2. Subscription values
These work in a row of the list and on the subscription page.
| Attribute | What it shows |
|---|---|
| `subscription="name"` | the name of the order that started it, such as `#1001` |
| `subscription="status"` | the status, such as Active or Paused |
| `subscription="nextBillingDate"` | the date of the next charge |
| `subscription="billingInterval"` | how often it is charged, such as `1 Month` |
| `subscription="createdAt"` | the date it started |
| `subscription="shippingAddress"`, `subscription="billingAddress"` | the addresses, one line under the other |
| `subscription="subtotalPrice"`, `subscription="totalPrice"` | amounts of the order that started it |
| `subscription="note"` | the note |
- **The amounts** (`subtotalPrice`, `shippingPrice`, `totalTax`, `totalRefunded`, `totalPrice`) are those of the order that started the subscription, not of the next charge.
- **Links.** `subscription="url"` links to your subscription page. `subscription="statusUrl"` links to the order that started it, in the customer’s Shopify account, and `subscription="customerUrl"` to their Shopify profile.
- **Status labels.** Without labels, the status shows Shopify’s code with a capital letter only at the start, such as `Active`. For your own words, add the codes as Shopify sends them, such as `ACTIVE`, `PAUSED` or `CANCELLED`, to the `SmootifyStatusLabels` script shown in [Orders and buy again](https://www.smootify.io/docs/build/orders). One script holds the order and subscription labels.
The full list is in [subscription values](https://www.smootify.io/docs/reference/attributes/subscription).
## 3. The subscription page
1. In Webflow, create a page in the `account` folder with the slug `subscription`, so its address is `/account/subscription`. For another address, set it in the option `subscriptionPageUrl`.
2. Add a Custom element with the tag `subscription-page`, and put the subscription values inside it.
The page shows the subscription that `subscription="url"` links to. Opened without it, or for a subscription that is not the customer’s, the page goes to your home page.
**Products.** Inside it, add a Custom element with the tag `subscription-line-item`. Design it as one product: Smootify repeats it once per product of the subscription, and removes the other copies you make.
```html
Active
Product
1
$0.00
```
Inside `subscription-line-item`, `line-item` takes `name`, `title`, `variantTitle`, `sku`, `quantity`, `price` and `image` (on an Image). See [subscription line values](https://www.smootify.io/docs/reference/attributes/subscription-line-item).
## 4. Pause, resume and cancel
Each action is a Custom element with a Form Block inside it, and a Form Button in the Form:
- `subscription-pause` pauses the subscription;
- `subscription-activate` resumes a paused subscription;
- `subscription-cancel` cancels it.
Put them inside `subscription-page`, or inside `customer-subscription` to act from the list.
```html
Paused
Something went wrong
Resumed
Something went wrong
Cancelled
Something went wrong
```
Smootify keeps only the forms that fit the subscription’s status, and removes the others:
| Status | Forms that stay | Forms removed |
|---|---|---|
| `ACTIVE` | `subscription-pause`, `subscription-cancel` | `subscription-activate` |
| `PAUSED` | `subscription-activate`, `subscription-cancel` | `subscription-pause` |
| `CANCELLED` | none | all three |
| `EXPIRED` | none | all three |
- **After the change**, the page reloads and shows the new status. If Shopify refuses the change, its message shows in the form’s Error message.
- **A second step.** Cancelling takes one click. To ask the customer to confirm, put the cancel form in a popover opened by a first button.
## Before you publish
> [!STUCK]
> - **The subscription page sends you to the home page.** It was opened without a subscription in its address. Open it from a `subscription="url"` link, logged in.
> - **The pause or cancel form is missing.** The subscription's status does not allow it, such as a cancelled subscription.
> - **The totals do not match the next charge.** They are the amounts of the order that started the subscription.
> - **The status shows `Active` in English.** Add the subscription codes to `SmootifyStatusLabels`.
> - **The products you designed disappear.** Only the first `subscription-line-item` is the template. Design one; Smootify repeats it.
## Coming from 1.x
- The amounts showed 0 in 1.x. 2.0 shows the amounts of the order that started the subscription.
- `subscription="canceledAt"` is not read.
- The rows no longer shimmer on their own while loading. Add `skeleton` to the placeholders you want.
---
# Customer data and newsletter
> Let logged-in customers see and change their own data, and collect newsletter sign-ups from any visitor. Covers the name form, customer metafields shown and edited, the email subscription, store credit and the newsletter form.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/accounts/customer-editor
URL: https://www.smootify.io/docs/build/customer-data
Updated: 2026-10-10
Beyond orders and addresses, an account can hold the customer’s own data: their name, preferences you store in customer metafields, their email subscription and their store credit. This page builds the forms that show and change them. It ends with a newsletter form that works for any visitor, logged in or not.
> [!NOTE]
> Customer accounts must be set up first: see [Customer accounts](https://www.smootify.io/docs/build/account). The newsletter form and file fields also need the Server plan.
## 1. The customer’s name
Add a Custom element with the tag `customer-user-update`, with a Form Block inside it. The Form needs two Text fields, whose **Name** setting is `firstName` and `lastName`, and a Form Button. Without both fields, the element removes itself.
Smootify fills the fields with the customer’s current name. When the customer saves, the Success message shows, every `[customer]` value on the page updates without a reload, and after 5 seconds the form comes back with the new values. `customer-condition="no-names"` can show a reminder to customers who have not given both names yet.
## 2. Show customer metafields
Customer metafields hold the data you add to a customer in Shopify: a shoe size, a loyalty level, a birthday. `customer-metafield="shoe_size"` on a Text block shows the metafield `custom.shoe_size` of the logged-in customer, anywhere on any page.
**Shopify set-up.** Create the definition under **Settings › Custom data › Customers**, and set its **Customer accounts** access to **Read**, or to **Read and write** if customers edit it (step 3):
In the Shopify admin, under Settings › Custom data › Customers:
- **Shown on the page.** A definition such as Loyalty level, `custom.loyalty_level`, Single line text, with **Customer accounts access** set to **Read**. `customer-metafield="loyalty_level"` shows its value, such as Gold.
- **Edited by the customer.** A definition such as Shoe size, `custom.shoe_size`, Integer, with **Customer accounts access** set to **Read and write**. A field named `shoe_size` in `customer-metafields-editor` shows and saves it, such as 38.
Without customer accounts access the value stays empty, and the editor saves only with Read and write.
- **Namespace.** Add `namespace="…"` when the namespace is not `custom`.
- **Types.** Every type renders as on the product page: text, numbers, dates, files, lists, references. See [Metafields on the page](https://www.smootify.io/docs/build/metafields) and [metafield types](https://www.smootify.io/docs/reference/metafield-types).
- **Show or hide.** `if-customer-metafield="…"` shows an element only when the metafield is set (for a number, above 0; for a true-or-false, true). `unless-customer-metafield="…"` shows it only when the metafield is not set. Add `data-check-value="…"` to compare with a value instead. See [customer metafield conditions](https://www.smootify.io/docs/reference/conditions/customer-metafield).
- **B2B.** `company-metafield="…"` and its `if-` and `unless-` forms read the metafields of the customer’s company.
- **Logged out.** These elements are taken out of the page for a visitor who is not logged in.
The attributes are in [customer metafields](https://www.smootify.io/docs/reference/attributes/customer-metafield).
## 3. Let customers edit their metafields
Add a Custom element with the tag `customer-metafields-editor`, with a Form Block inside it. Add one field per metafield. The field’s **Name** setting is the key of the metafield, as Shopify shows it, such as `shoe_size`, or the same words with spaces, such as `Shoe size`: Smootify turns the Name into the key. The editor saves in the namespace `custom`.
```html
Saved
Something went wrong
```
- **Fields.** Text fields, Text areas, Select fields, Radio buttons and Checkboxes work. For a Select field or Radio buttons, the option’s value (a Radio button’s **Choice value**) is what is saved. A Checkbox alone, for a true-or-false metafield, saves true when ticked and false when not.
- **Prefilled.** Smootify fills each field with the value saved in Shopify, and picks the matching option.
- **Every field needs a definition.** A field whose Name matches no customer metafield definition makes the save fail. Its definition needs **Read and write** access for customer accounts.
- **Fields that are not saved.** A field whose Name starts with `_`, such as `_accepted`, is never saved. Use it for a field the form needs but Shopify does not, such as a checkbox to accept your terms, marked **Required**.
- **Emptying a field deletes the value** in Shopify. A field hidden by a condition is left as it is.
- **After saving**, the Success message shows, and after 5 seconds the form comes back with the new values, unless the form has a Redirect URL.
- **Errors.** When Shopify refuses a value, for example text in a number metafield, its message shows in the form’s Error message.
**Files.** With the Server plan, a [`file-input`](https://www.smootify.io/docs/reference/elements/file-input) in the editor uploads a file to Shopify and saves it in a File metafield. Its `name` is the metafield’s key, and `data-multiple` allows several files for a list of files. The files the customer saved before show in it, with their name, and with their preview when the item template has `upload="preview"`. Only images get a preview: a saved PDF or another file shows its name alone. Removing the last one empties the metafield. See [File uploads](https://www.smootify.io/docs/features/file-uploads).
## 4. Email subscription
Two forms let a logged-in customer change their email marketing subscription, with the email of their account. To collect new subscribers from any visitor, use the newsletter form of step 6:
- **Change a customer's subscription**: `customer-subscribe-email` and `customer-unsubscribe-email`. For logged-in customers, with the email of their account. Wrap each form in its condition: `customer-condition="email-not-subscribed"` around the subscribe form, `customer-condition="email-subscribed"` around the unsubscribe form.
- **Collect new subscribers**: `newsletter-subscribe` (step 6). For any visitor, with the email they type. Needs the Server plan, Newsletter Subscription turned on in the Smootify dashboard, and a Text field with the Type setting Email.
Each of the two account forms is a Custom element with a Form Block and a Form Button inside it. Wrap each one in an element with the matching condition, so the customer sees only the form that applies. The conditions follow at once when the customer saves.
```html
You are subscribed
Something went wrong
You are unsubscribed
Something went wrong
```
## 5. Store credit
Shopify can give a customer store credit, which they spend at checkout. Add a Custom element with the tag `store-credit` to show it. A customer with no store credit sees a balance of 0 and no movements, so the card can stay on the account page.
```html
Your credit: $0.00
1/1/2026
Order payment
$0.00
```
- **Balance.** `store-credit="balance"` shows the balance. `store-credit="expirableBalance"` shows the part of it that expires. Wrap it in an element with `if-credit="expires"`, which is removed when no credit expires.
- **Transactions.** `store-credit-transaction` is the template of one movement, repeated for each. Inside it: `amount`, `balanceAfterTransaction`, `event` (what happened, such as an order payment, a subscription payment or a refund), `createdAt` and `expiresAt`. An element with `if-credit-transaction="expires"` is removed from the movements that do not expire.
- **No credit yet.** An element with `customer-condition="no-store-credit"` shows only while the customer has no balance and no movements, such as “No credit yet. A refund or a credit from us shows here.” Put the movements in an element with `customer-condition="has-store-credit"`. To hide the whole card instead, give `store-credit` itself `customer-condition="has-store-credit"`.
- **Your own words.** The `event` texts are in English. The `SmootifyStoreCreditEventsLabel` script changes them, built like the status labels in [Orders and buy again](https://www.smootify.io/docs/build/orders), with Shopify’s codes such as `ORDER_PAYMENT` as keys.
See [`store-credit`](https://www.smootify.io/docs/reference/elements/store-credit) and [store credit values](https://www.smootify.io/docs/reference/attributes/store-credit). To give credit on its own, the Flow library has [Store credit on a paid order](https://www.smootify.io/flows/store-credit): Shopify Flow credits a share of every paid order above an amount you choose. To show in the cart what an order will earn, a short script does it: see [the store credit a cart earns](https://www.smootify.io/docs/developers/events#example-the-store-credit-a-cart-earns).
## 6. Newsletter form for any visitor
`newsletter-subscribe` turns a Webflow form into a newsletter sign-up. The visitor does not need an account: Smootify adds the email to your Shopify customers, subscribed to email marketing. It needs the Server plan, and **Newsletter Subscription** turned on in the Smootify dashboard.
1. Add a Custom element with the tag `newsletter-subscribe`, and put a Form Block inside it.
2. In the Form, add a Text field and set its **Type** setting to Email, then a Form Button.
3. Optional: add `tags="newsletter, footer"` to `newsletter-subscribe` to tag the new customers in Shopify, for example to know which form they used.
```html
Thanks for subscribing
Something went wrong
```
After the submit, the Success message shows, and the form’s Redirect URL setting is followed if it has one. The subscribers are in Shopify, under **Customers**:
In Shopify, under Customers, for a form with `tags="newsletter, footer"`:
- **A new email**, such as nora@example.com: a new customer with that email, subscribed to email marketing, with the tags newsletter and footer.
- **An email Shopify knows**, such as Anna Rossi's anna@example.com: the customer is marked as subscribed; the tags are not added, so Anna keeps only her own tags.
## Before you publish
> [!STUCK]
> - **The name form disappears.** It has no Text fields named `firstName` and `lastName`.
> - **The metafields editor does not save.** A field's Name matches no customer metafield definition, or the definition's customer accounts access is not **Read and write**.
> - **A customer metafield stays empty.** Its definition's customer accounts access is off.
> - **The newsletter form does nothing.** The site is not on the Server plan, Newsletter Subscription is not turned on, or you are on `.webflow.io` during a trial.
> - **A newsletter form disappears.** Its Form has no Text field with the Type setting Email.
> - **Tags are missing on some subscribers.** They were already customers: the tags go only on new customers.
---
# Predictive search
> A search field that shows products and search suggestions as the visitor types, in a Webflow Dropdown, a popover or right on the page. Covers smootify-search, its regions, the result template and the link to the search page.
Status: Ready (Smootify 2.0)
Add-on: Predictive Search
1.x page: https://docs.smootify.io/docs/search-filters
URL: https://www.smootify.io/docs/build/predictive-search
Updated: 2026-10-07
Predictive search shows results while the visitor types: up to 10 products, and the searches Shopify suggests for those words. It usually sits in the navbar, with the results in a Webflow Dropdown or a popover. For the full list of results, pair it with a [search results page](https://www.smootify.io/docs/build/search-page).
> [!NOTE]
> Turn on the **Predictive Search** add-on in the Smootify dashboard. The Lite and Server plans include it. On your `.webflow.io` address it works without it, so you can build and test first. Shopify's Search & Discovery app can tune the results, for example with synonyms, but it is not required.
## How it fits together
What Smootify needs:
- a `smootify-search` element;
- inside it, a Text field whose **Name** setting is `query`. It sits in a Form Block, as every Webflow field does.
What is up to you:
- where the results appear: in a Webflow Dropdown, in a popover, or in place on the page, such as in a menu panel;
- which parts you use: the result list, the suggestions, the empty message, the content shown before the visitor types;
- how a result looks: a simple result template, or a full product card.
```html
Results
```
## 1. Add the search field
1. Add a Custom element with the tag [`smootify-search`](https://www.smootify.io/docs/reference/elements/smootify-search).
2. Inside it, add a Form Block with one Text field. Smootify searches with it whatever its **Name**. With two text fields, set the Name of the search one to `query`.
Smootify searches a short moment after the visitor stops typing, and when they press Enter. The Form Button is optional.
`limit` on `smootify-search` sets how many products show, up to 10, which is also the default. Shopify returns at most 10 products for a predictive search.
## 2. Choose where the results appear
The results can appear in one of three places:
| Where | Put the results | Opens and closes |
|---|---|---|
| A Webflow Dropdown | inside the Dropdown list; the Text field can sit inside the Dropdown or next to it | Smootify opens the Dropdown as the visitor types |
| A popover | inside a popover (see [Popovers](https://www.smootify.io/docs/build/popovers)) | opens when the visitor clicks the field, so the suggestions for an empty field show, and as they type; a `button` with `data-action="close"` anywhere in `smootify-search`, such as next to the field, closes it |
| In place | anywhere in `smootify-search`, with no Dropdown or popover | each part shows and hides itself where it is, such as in a full-screen menu |
## 3. The regions
These Custom elements show and hide themselves with the state of the search. All are optional:
| Tag | Shown while | Holds, for example |
|---|---|---|
| `search-no-query` | the field is empty | your own suggestions, such as popular searches |
| `search-with-query` | the field has words in it | the results, the empty message, Shopify's suggestions |
| `search-empty` | the words found no product | a message such as "No products found" |
| `search-suggestions` | Shopify has search suggestions for the words | Shopify's suggestions; put it inside `search-with-query` |
In a Webflow Dropdown without `search-with-query`, the Dropdown list stays hidden until there are results or the empty message.
## 4. The results
There are two ways to show a result:
- **A result template**, `search-result`: a Custom element with `search="…"` values inside, copied once per product. Prices with `search="price"` and `search="compare-at-price"`: a `smootify-price` inside it has no product to read.
- **A product card**, `smootify-product` with `data-id="search"` inside `search-with-query`, designed as a product card: it shows everything a product card shows, such as `smootify-price` or an add-to-cart button, but each card loads the whole product from Shopify.
**A result template.** Add a Custom element with the tag [`search-result`](https://www.smootify.io/docs/reference/elements/search-result). It is the template: Smootify copies it once per product, and removes other `search-result` elements you designed. Inside it, `search="…"` fills an element with a value of the product:
- `search="title"`, `search="vendor"`, `search="type"`: text;
- `search="url"` on a Link block or a Text link: the product page’s link;
- `search="image"` on an Image: the product’s main image;
- `search="price"`: the lowest price of the product;
- `search="compare-at-price"`: its crossed-out price, removed when it is not on sale;
- `search="collection"`: the first collection’s name, with its link on a Text link.
`image`, `compare-at-price` and `collection` are removed when the product has none. A result that cannot be bought gets the class `is-not-available`. Every value is in [predictive search attributes](https://www.smootify.io/docs/reference/attributes/predictive-search).
**A product card.** Instead of `search-result`, put a `smootify-product` with `data-id="search"` inside `search-with-query`, and design it as a [product card](https://www.smootify.io/docs/build/product-cards). The first card is the template.
```html
Product name
$0.00
```
## 5. Suggestions, the typed words and the search page
```html
SuggestionSee all results
```
- **Shopify’s suggestions.** Inside `search-suggestions`, a Text link with `search="suggested-query"` is the template of one suggestion. A click searches it. Put `search-suggestions` inside `search-with-query`: suggestions only exist once the visitor has typed.
- **Your own suggestions.** In `search-no-query`, Text links with `search="suggested-query"` are fixed suggestions, such as popular searches. A click searches their text. Keep them outside `search-suggestions`.
- **The typed words.** `search="query"` on text shows what the visitor typed, for example in “Results for shoes”.
- **The search page.** A link with `search="search-page"`, set in Webflow to your search page, gets the typed words added to its address, so the search page opens with the same search. A Webflow Button works here too, since it is a link.
## While it loads
While a search runs, `smootify-search` has the class `is-loading`. Nothing shimmers by default: style that class, or add `skeleton` to the elements in the result template. Smootify also sets `has-query`, `has-products`, `is-empty` and `has-suggestions` on `smootify-search`. See [state classes](https://www.smootify.io/docs/reference/state-classes).
## Before you publish
> [!STUCK]
> - **Nothing happens when typing.** `smootify-search` has two text fields, and neither has the **Name** `query`.
> - **Never more than 10 results.** That is Shopify's limit for predictive search. Add a link with `search="search-page"` to show all results.
> - **Prices are empty in the results.** A `smootify-price` inside `search-result` has no product to read. Use `search="price"`, or product cards.
> - **Attributes on the second result do nothing.** Only the first `search-result` is the template.
> - **Suggestions never show.** `search-suggestions` is inside `search-no-query`, which hides as soon as the visitor types. Move it into `search-with-query`.
> - **It works on `.webflow.io` but not on the live domain.** The Predictive Search add-on is not turned on for the site.
## Coming from 1.x
- The results no longer shimmer while loading, and Smootify no longer adds a hidden placeholder result: style `is-loading` or add `skeleton`.
- Inside `search-result`, use `search="price"` and `search="compare-at-price"` instead of `smootify-price`.
- `limit` above 10 is capped at 10.
---
# Search results page
> A page that lists every product matching a search, from the words in its address or typed in its field, with a count, pagination and an empty state. Covers smootify-search-page and how visitors reach it.
Status: Ready (Smootify 2.0)
Add-on: Search
1.x page: https://docs.smootify.io/docs/search-filters/search-page
URL: https://www.smootify.io/docs/build/search-page
Updated: 2026-10-10
The search results page lists every product that matches a search, page by page. The words come from the page address (`?q=` followed by the words) or from a field on the page. Unlike [predictive search](https://www.smootify.io/docs/build/predictive-search), it has no limit of 10 results.
> [!NOTE]
> Turn on the **Search** add-on in the Smootify dashboard. The Lite and Server plans include it. On your `.webflow.io` address it works without it, so you can build and test first.
## How it fits together
What Smootify needs:
- a `smootify-search-page` element;
- inside it, one product card: a `smootify-product` element. Smootify repeats it once per result. Without a card, the block is removed.
What is up to you:
- the page layout and the card design;
- the optional parts: a search field, the count, the searched words, the empty state, the pagination.
```html
```
## 1. Create the page
1. Create a static page, such as `/search-page`. Webflow keeps `/search` for its own site search, so pick another slug.
2. Add a Custom element with the tag [`smootify-search-page`](https://www.smootify.io/docs/reference/elements/smootify-search-page). It can wrap the whole page or only the results section.
Visitors reach the page in three ways:
- from predictive search, with a link that has `search="search-page"`: see [Predictive search](https://www.smootify.io/docs/build/predictive-search);
- from any link to the page with `?q=` and the words, such as `/search-page?q=linen+shirt`;
- by typing in the page’s own field.
The address follows the search, so a results page can be shared.
## 2. The search field
Add a Form Block inside `smootify-search-page`, with a Text field of Type **Plain** or a Search field (`type="search"`, as Webflow’s Search element writes it). Its Name does not matter: Smootify renames it and fills it with the words from the address. Typing searches after a short pause, and Enter searches at once.
The field is optional: without it, the page shows the results for the words in the address.
The page can have more than one field, such as a form at the top and another above the results: each one searches the words typed in it. A field inside a result card does not start a search.
## 3. The results
Add a `smootify-product` with `data-id="search"` and design it as a [product card](https://www.smootify.io/docs/build/product-cards). The first `smootify-product` is the template: each result gets a copy of it, so give each element of the card a Smootify attribute, such as `product="title"`.
You can use a Collection list as the grid, with placeholder cards (`data-id="loader"`) in its items. Its first Collection item becomes the template, and the other cards keep their place until the first results replace them, so the page does not jump while it loads. See [placeholder cards](https://www.smootify.io/docs/build/product-cards).
Attributes on `smootify-search-page`:
- `limit`: products per page, 50 by default, 250 at most;
- `animation`, `duration`, `easing` and `stagger`, and `disable-scroll`, as on search & discovery: see [Filters, sorting and pagination](https://www.smootify.io/docs/build/filters).
**Products that cannot be bought.** `unavailable-products` on `smootify-search-page` says where they go. Here results 3 and 5 are sold out:
| `unavailable-products` | Products that cannot be bought |
|---|---|
| `LAST` (default) | come after the others |
| `HIDE` | are left out |
| `SHOW` | keep their place in the results |
The search covers products only.
## 4. Count, words, empty state and reset
Before a search and after it, the page shows different parts: an element with `search="no-query"` shows only while there is no query, such as “What are you looking for?” with a few suggested searches; one with `search="with-query"` shows only once there is one, such as the “Results for … · 12 pieces” line; `search="empty-state"` shows only when a search finds nothing. `smootify-search-page` also gets the class `has-query` while there is a query and `is-empty` when it finds nothing. All optional, anywhere inside `smootify-search-page`: `search="count"` and `search="query"` on text, `search="empty-state"` on a Div block. What they show for three searches:
| Search | `search="count"` (on text) | `search="query"` (on text) | `search="empty-state"` (on a Div block) |
|---|---|---|---|
| `?q=linen`, 12 results | 12, the number of results | linen, the searched words | hidden |
| `?q=xyzzy`, no results | 0 | xyzzy | shown |
| no words in the address or the field | | | shown: the page searches nothing |
A reset button, a Custom element with the tag `button` and `type="reset"`, clears the search.
Every attribute is in [search page attributes](https://www.smootify.io/docs/reference/attributes/search-page).
## 5. Pagination
The pagination buttons work as on search & discovery. Each is a Custom element with the tag `button` and one of these `data-action` values:
| Buttons | Shows |
|---|---|
| `data-action="previous"` and `data-action="next"` | one page at a time |
| `data-action="page"` | one page at a time, with numbers to jump to, such as 1 … 4 5 6 … 12 |
| `data-action="load"` | adds the next page below the products shown, on a click |
| `data-action="load-in-view"` | adds the next page below when the button scrolls into view |
A button is hidden when there is no such page. Use `previous` and `next`, or one of the load buttons. Buttons inside a result card, such as the previous and next of a booking calendar, are not pagination buttons.
Page numbers work as on search & discovery too: one `button` with `data-action="page"` as the template of the numbers, `filter="page-ellipsis"` for the gaps, `filter="page-current"` and `filter="page-total"` for “Page 3 of 12”, and `page-range` on `smootify-search-page`. See [Page numbers](https://www.smootify.io/docs/build/filters#page-numbers). The address always follows the page, such as `?page=3`. A search counts at most 1,000 results, a Shopify limit.
## A search page with filters
`smootify-search-page` has no filters. For search results with filters and sorting, use [search & discovery](https://www.smootify.io/docs/build/filters) on the page instead, with a `filter-search` field. It reads the same `?q=` from the address, so the links from predictive search keep working. It needs the Search & discovery add-on, and without words it lists the whole catalog.
## Before you publish
> [!STUCK]
> - **The block disappears.** It has no `smootify-product` card inside.
> - **The page opens empty.** The address has no `?q=`. A form that sends its words under another name, such as Webflow's own Search element, is not read.
> - **Typing in the field does nothing.** Only a Text field of Type **Plain** or a Search field is read; an Email or Number field is not.
> - **Every card shows the same name.** Those elements are bound to CMS fields only. Add `product="title"` and the other attributes.
> - **Sold-out products are always at the end.** That is the default. Set `unavailable-products="SHOW"` to keep them in their place.
## Coming from 1.x
- The page no longer adds placeholder cards before the first search: the cards of your Collection list keep their place until the results replace them.
- Nothing shimmers by default: add `skeleton` where you want it.
---
# "Navbar: cart, search and account"
> Where the store parts go in a navbar - the cart and its count, the search, the login and account links, and the country switcher - and what each one must be inside.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/build/navbar
Updated: 2026-10-01
A store’s navbar usually holds the cart, the search, the login and the country switcher, on every page. This page says where each part goes and what it must be inside. Each part has its own guide for the details.
## How it fits together
What Smootify needs:
- the cart count and the button that opens the cart inside the same `smootify-cart` as the mini cart;
- each other part as its own element: `smootify-search`, `passwordless-login`, `country-switcher`. None of them needs to be inside another.
What is up to you:
- which parts the navbar has, their order and their design;
- whether it is a Webflow Navbar or your own layout. Put it in a component, so it is the same on every page.
Each part, and what it holds:
| Part | Holds | Needs |
|---|---|---|
| `smootify-cart` (the cart and its count, see [Build the cart](https://www.smootify.io/docs/build/cart)) | the count, the open button and the mini cart panel, all in the same `smootify-cart` | nothing else |
| `smootify-search` (search, see [Predictive search](https://www.smootify.io/docs/build/predictive-search)) | a Text field named `query` and the results, in a Webflow Dropdown or a popover | the Lite or Server plan, or the Predictive search add-on |
| `passwordless-login` (login and account, see [Customer accounts](https://www.smootify.io/docs/build/account)) | a Form Block with a Form Button; next to it, `logout-form` and an account link | customer accounts set up in Shopify |
| `country-switcher` (country and currency, see [Markets](https://www.smootify.io/docs/features/markets)) | a Webflow Dropdown or a popover; its first link is the template of each country | nothing else |
A full example on Webflow’s Navbar element, with the search, the account and the cart:
```html
```
The search, the account and the cart sit outside the Nav Menu, next to the Menu Button, so they stay visible on phones.
## 1. The cart and its count
Build the mini cart as in [Build the cart](https://www.smootify.io/docs/build/cart), and put its `smootify-cart` in the navbar.
- **One cart holds everything.** `cart="count"` works only inside `smootify-cart`, and an open button opens only the cart it is inside. Put the cart icon, the count, the open button and the panel in the same `smootify-cart`.
- **Don’t add a second cart for the count.** A `smootify-cart` with no Dropdown and no popover inside it counts as a cart page, and turns off `data-open` on the mini cart.
- **Which way to open it.** Any of the ways in [Build the cart](https://www.smootify.io/docs/build/cart) works in a navbar. A dialog and a popover open above the whole page. A Dropdown opens under its toggle.
- **On small screens.** In a Webflow Navbar, what is inside the Nav Menu goes into the collapsed menu with the links. To keep the cart visible on every screen size, put it outside the Nav Menu, as above.
- **On the cart page.** The page’s own cart turns off `data-open`, so the mini cart stays closed when a product is added there.
A link to your cart page is a normal Webflow link.
## 2. Search
Add a `smootify-search` to the navbar for results that show as the visitor types. It holds a Text field named `query` and the results, which can open in a Webflow Dropdown or a popover. It needs the Lite or Server plan, or the Predictive Search add-on turned on in the Smootify dashboard.
See [Predictive search](https://www.smootify.io/docs/build/predictive-search) for the parts and [`smootify-search`](https://www.smootify.io/docs/reference/elements/smootify-search) for every attribute.
## 3. Login and account
Login works with Shopify’s Customer Accounts, set up as in [Customer accounts](https://www.smootify.io/docs/build/account). Without that set-up, the login does nothing.
- **Log in.** Add a Custom element with the tag `passwordless-login`, with a Form Block inside it. The Form needs only a Form Button, such as “Log in”. A click takes the visitor to Shopify’s login page, then back to the site, logged in.
- **Account link.** A Text link to your account page with `customer-condition="logged-in"` shows only to logged-in visitors. `customer="first-name"` inside it shows their first name.
- **Log out.** Add a Custom element with the tag `logout-form`, with a Form Block and a Form Button inside it.
- **Show one or the other.** Wrap the login in an element with `customer-condition="not-logged-in"`.
```html
```
What the visitor sees, from the page load to the login:
- **While Smootify checks** whether the visitor is logged in: the elements with `customer-condition` show as a placeholder shimmer.
- **Logged out**: the elements with `customer-condition="not-logged-in"` show, such as the login; those with `customer-condition="logged-in"` are taken out of the page until the login.
- **Logged in**: the elements with `customer-condition="logged-in"` show, such as an account link with `customer="first-name"`; those with `customer-condition="not-logged-in"` are hidden.
Every value is in [customer conditions](https://www.smootify.io/docs/reference/conditions/customer).
## 4. Country and currency
Add a `country-switcher` to the navbar. It is built on a Webflow Dropdown or a popover: the first link inside it is the template of each country. In the toggle, `data-prop="flag"`, `data-prop="name"` and `data-prop="currency-iso-code"` show the current country and currency. A pick changes the country, and prices follow.
**Languages with Webflow Localization.** If your site uses Webflow Localization, the language changes with Webflow’s own locale switcher: put it next to the country switcher. When a visitor picks a country whose code matches one of your locales, such as Italy and an Italian locale, the country switcher opens that locale’s page for them, so the address changes too.
See [Markets, currencies and languages](https://www.smootify.io/docs/features/markets). To suggest the right country to a new visitor, see [Market suggestion](https://www.smootify.io/docs/features/market-suggestion).
## Before you publish
> [!STUCK]
> - **The count stays at 0.** `cart="count"` is outside `smootify-cart`.
> - **The cart button does nothing.** It is outside the `smootify-cart` that holds the panel, or it is a Webflow Button, which is a link. Use a Custom element with the tag `button` inside the same `smootify-cart`.
> - **The mini cart does not open after an add.** The page has a second `smootify-cart` with no Dropdown and no popover, such as one used only for the count.
> - **The cart is missing on phones.** It is inside the Nav Menu, which collapses into the menu button.
> - **Log in does nothing.** Customer Accounts is not set up: see [Customer accounts](https://www.smootify.io/docs/build/account).
---
# Popovers
> Which Smootify parts can open in a popover, how to build one with Webflow's Popover element or two custom attributes, and the rules for option pickers in a popover.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/build/popovers
Updated: 2026-10-08
A popover is a panel that opens above the page, such as a menu, a list of choices or a drawer. The browser opens and closes it: Escape and a click outside close it, and it moves the focus for keyboard and screen reader users. Every panel Smootify opens or closes can be a popover, from the mini cart to the option pickers, the filters and the cookie banner.
> [!NOTE]
> Popovers work in Safari 17, Chrome 114, Firefox 125 and later. The Webflow Dropdowns, dialogs and details elements you may already use keep working.
## Build a popover
There are two ways, and Smootify works the same with both:
- **Webflow's Popover element**, on sites that have it: the panel is the Popover, placed with its position settings; the trigger is any Webflow Button or Link Block whose link settings have Type **Popover**, the Popover as Target and an Action (Toggle, Show or Hide). A close button is the same with Hide. Put the Popover where the guide of each part says.
- **Two custom attributes**, on any site: the panel is a Div block with `popover="auto"` and an ID, such as `size-list`; the trigger is a Custom element with the tag `button` and `popovertarget="size-list"`.
**Webflow’s Popover element**, step by step:
1. Add the Popover element where the guide of the part says, such as inside `smootify-cart`, and put the panel’s content inside it. Place it with its position settings.
2. The trigger is any Webflow Button or Link Block. In its link settings, set Type to **Popover**, choose the Popover as Target, and the Action: Toggle, Show or Hide. Webflow publishes it as a button that opens the Popover.
3. A close button inside the panel is a Button or Link Block with the same settings and the Action **Hide**.
**Two custom attributes**, step by step:
1. The panel is a Div block with the custom attribute `popover` set to `auto`. Give it an **ID** in its element settings, such as `size-list`.
2. The trigger is a Custom element with the tag `button` and the custom attribute `popovertarget` set to the panel’s ID, `size-list`.
3. Inside a form, such as the add-to-cart form, also give the trigger `type="button"`. Without it, the trigger submits the form.
4. To close the panel from inside, add a button with the same `popovertarget` and `popovertargetaction="hide"`. Escape and a click outside also close it.
**How it looks.**
- The browser shows the panel above everything, in the middle of the window. Place it with your own styles, such as a fixed position on the right for a drawer.
- Smootify takes the browser’s padding and border off every popover, and the class of your panel sets its own. There is no backdrop: the page behind stays as it is. For a backdrop, such as behind a drawer, set the CSS variable `--smootify-backdrop-color` on the panel’s class, for example `rgba(0, 0, 0, 0.5)`. See [CSS](https://www.smootify.io/docs/developers/css).
- **A panel that is a popover only on small screens**, such as filters in a column on desktop and in a drawer on mobile: on the panel’s class, set `--smootify-closed-display: flex` at the desktop breakpoint, with `position: static`, `inset: auto`, `margin: 0` and the column’s width, since the browser places every popover fixed in the middle. Set `--smootify-closed-display: none` at tablet, so it is a drawer again.
- **The page under a drawer.** A popover that covers the page, such as a cart drawer or a full-screen menu, takes the custom attribute `data-overflow-body`: the page stops scrolling while it is open, and its scrollbar keeps its space so nothing jumps. A popover anchored to its trigger, such as a cart dropdown, leaves it out. It works on a `dialog` too.
- Smootify does not animate a popover. It keeps the transitions you design.
## Which parts can use a popover
Each of these parts can open in a popover:
| Element | What the popover holds | Guide |
|---|---|---|
| `smootify-cart` | the mini cart | [Build the cart](https://www.smootify.io/docs/build/cart) |
| `variant-swatches` | the value buttons; closes after a pick | [Product page](https://www.smootify.io/docs/build/product-page) |
| `variant-popover` | the values of an option | [Product page](https://www.smootify.io/docs/build/product-page) |
| `addon-popover` | the variants of an add-on | [Product add-ons](https://www.smootify.io/docs/features/add-ons) |
| `configurator-popover` | a configurator option | [Product configurator](https://www.smootify.io/docs/features/product-configurator) |
| `dynamic-property` (Dropdown type) | the choices | [Dynamic properties](https://www.smootify.io/docs/features/dynamic-properties) |
| `filter-popover`, `sort-popover` | a filter's values, the sort choices | [Filters](https://www.smootify.io/docs/build/filters) |
| `country-switcher`, `location-switcher` | the countries, the locations | [Markets](https://www.smootify.io/docs/features/markets) |
| `smootify-search` | the results; opens as the visitor types | [Predictive search](https://www.smootify.io/docs/build/predictive-search) |
| `store-availability` | the other locations, loaded when it opens | [Pickup](https://www.smootify.io/docs/features/store-availability) |
| `klaviyo-back-in-stock` | the alert form | [Back in stock](https://www.smootify.io/docs/features/back-in-stock) |
| `smootify-consent` | the cookie banner | [Cookie consent](https://www.smootify.io/docs/features/cookie-consent) |
| the market suggestion | is the popover itself; Smootify opens it a few seconds after the page loads | [Market suggestion](https://www.smootify.io/docs/features/market-suggestion) |
On a collection page, `auto-close-details="true"` on `smootify-search-discovery` closes every popover inside it each time the results change, such as a filters panel. That includes a popover inside a product card.
## Pickers in a popover
These rules apply to `variant-popover`, `addon-popover`, `filter-popover`, `sort-popover`, `configurator-popover`, `country-switcher`, `location-switcher` and the Dropdown type of `dynamic-property`:
- **The trigger goes inside the element.** Its text shows the current choice, so it changes when the visitor picks. A fixed label, such as Size, goes outside it.
- **The first link or button inside the popover is the template** of every value. Smootify copies it and removes the links or buttons next to it. A button with `popovertarget` does not count.
- **A pick closes the popover**, as do Escape and a click outside. A close button inside needs `popovertarget` and `popovertargetaction="hide"`, or it becomes the template.
The attributes are the ones of the Dropdown version, such as `data-option` on `variant-popover`.
```html
```
Here the popover has the ID `size-list` in its element settings. The label “Size” sits outside the trigger, because the trigger’s text becomes the chosen value.
In `country-switcher` and `location-switcher`, a Webflow Dropdown wins over a popover: with both inside, Smootify uses the Dropdown.
## Popovers in repeated blocks
A popover can sit inside a block Smootify repeats, such as a cart line, an address, an order or a product card. Each copy gets its own ID, and its trigger follows, so each trigger opens its own panel. Design the popover once.
In a mini cart, keep popovers out of the lines unless the cart is in a dialog or a details element: a popover anywhere else inside `smootify-cart` makes Smootify open the whole cart as a popover.
## Before you publish
> [!STUCK]
> - **The trigger adds to cart or submits the form.** It is inside a form without `type="button"`.
> - **A close button became a value, or disappeared.** It is the first button in the popover, so it became the template. Close with a button that has `popovertarget` and `popovertargetaction="hide"`, or leave it to Escape and the click outside.
> - **The label on the trigger is replaced.** The trigger's first text shows the choice. Put a fixed label outside the trigger, or after the text of the choice.
> - **The choice never shows on the trigger.** The trigger is outside the picker element.
> - **The picker disappears.** It has no popover, or the popover has no link or button inside.
> - **The trigger does nothing.** Its `popovertarget` is not the exact ID of the panel.
> - **The panel opens in the middle of the window.** It has no position of its own: place it with your styles, or with the position settings of Webflow's Popover element.
> - **The panel never shows on desktop.** A closed popover is hidden: set `--smootify-closed-display` on its class at that breakpoint.
---
# Toasts
> Short messages you design in Webflow that appear for a few seconds when something happens - a product added to the cart, a wishlist change, a form sent, a link copied, an error from the cart.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/build/toasts
Updated: 2026-10-12
A toast is a short message that appears in a corner of the page for a few seconds, then goes away. It tells visitors that something worked when nothing else on the page changes: a product added to the cart on a site without a mini cart, a product saved to the wishlist, a form sent, a link copied. It also shows the errors Shopify gives for the cart. You design each toast in Webflow, and Smootify shows a copy of it when the event happens.
## How it fits together
- **`smootify-toasts`** is the box that holds the toasts, once per page. Smootify shows it above everything else on the page, drawers and dialogs too.
- **A template per event**, inside it: a Div block with the custom attribute `toast` set to the event’s name. Smootify hides the templates and shows a copy of one each time its event happens.
- **Custom attributes inside a template** mark where Smootify writes the product image, its title, the price or the message.
There is no toast for an event without a template. You choose the events by adding only the templates you want. A page without `smootify-toasts`, or with no template in it, shows no toast.
## 1. Add the toasts box
Add a Custom element with the tag `smootify-toasts`. It goes once per page, so put it in a component that is on every page, such as your navbar or footer component.
Give it a class and place it with Webflow’s position settings, for example **Fixed**, bottom right, with a 16 px offset. Until you do, Smootify places it bottom right. Use flex, vertical, with a gap, to stack the toasts.
Two optional custom attributes on `smootify-toasts`:
- **`data-duration`**: how long a toast stays, in milliseconds. The default is `4000`. With `0` a toast stays until the visitor closes it.
- **`data-max`**: how many toasts show at once. The default is `3`. When one more arrives, the oldest goes.
## 2. Design a template for each event
Inside `smootify-toasts`, add a Div block for each event you want and give it the custom attribute `toast` with the event’s name. Design it as the toast should look: its class and everything inside it are the toast’s. In the Designer the templates stay visible, so you design them in place. On the published site Smootify hides them.
| Template | Shows when | Smootify fills |
|---|---|---|
| `toast="added-to-cart"` | a product is added to the cart, from any button; not when a mini cart opens for that add | image, title, variant-title, quantity, price, count |
| `toast="wishlist-added"`, `toast="wishlist-removed"` | a wishlist toggle saves or removes a product | image, title, variant-title |
| `toast="form-sent"` | a Smootify form is sent: newsletter, back in stock, account and server forms | message: the text of that form's Success message |
| `toast="link-copied"` | a share button copies its link; not when the phone's share sheet opens | none: write the text in Webflow |
| `toast="cart-error"` | Shopify refuses a cart change: an add, a quantity, a discount code | message: Shopify's text |
| `toast="error"` | the fallback for errors when the page has no cart-error template | message |
Inside a template, mark the parts Smootify fills with the custom attribute `toast-item`:
- **`toast-item="image"`** on an Image: Smootify sets the product image and its alt text.
- **`toast-item="title"`** and **`toast-item="variant-title"`**: the product name and the chosen variant, such as Blue / M.
- **`toast-item="quantity"`** and **`toast-item="price"`**: how many were added and what they cost together, in the store’s money format. The price is the total for that quantity, not the price of one: write the line as “Qty 2 · €98.00”, not “2 × €98.00”.
- **`toast-item="count"`**: how many lines were added at once, for a bundle or a product with add-ons.
- **`toast-item="message"`**: the text of the message. For `form-sent` it is the text of that form’s Success message, so each form says its own thing.
Everything else in a template is yours: write it in Webflow, such as “Added to cart” or “Link copied”. A part Smootify has no value for is hidden, such as the variant title of a product with only one variant. Design those parts so the toast looks right without them.
```html
```
## 3. Add the buttons
- **Close.** A Custom element with the tag `button` and the custom attribute `data-action="close"` closes its toast. Give it an aria-label, such as “Close”, when it shows only an icon.
- **Open the cart.** A Link block with the custom attribute `data-action="open-cart"` opens the page’s mini cart and closes the toast. Set its link to your cart page: on a page without a mini cart, the link takes the visitor there.
## 4. Animate them
Each toast gets combo classes you can style and animate:
- **`is-visible`** right after the toast enters the page. Give the template opacity 0 and a small offset, and the combo class `is-visible` opacity 100% and no offset, with a transition: the toast slides in.
- **`is-leaving`** just before it goes. Smootify waits for its transition or animation to end, one second at most, then removes it.
- **`is-error`** on the `cart-error` and `error` toasts, for an error look if they share a class with the others.
The timer stops while the pointer is over a toast, while the focus is in it or while a finger touches it, so visitors have time to read it and click.
## When to use them
- **A site without a mini cart.** The `added-to-cart` toast tells visitors the product is in the cart, and its “View cart” link takes them there. On a site with a mini cart that opens on add to cart, the cart itself answers, and Smootify does not show the toast for that add.
- **The wishlist.** A heart that fills is easy to miss on a phone: a toast with the product’s name confirms it.
- **Forms and share buttons.** Visitors see that the form was sent or the link copied, without a Success message in the layout.
- **Cart errors.** A quantity or a code Shopify refuses shows up wherever the visitor is on the page.
For your own messages from custom code, see `Smootify.toast` in [JavaScript API](https://www.smootify.io/docs/developers/javascript-api).
## Before you publish
> [!STUCK]
> **No toast shows.** The template is missing, or its name is misspelled. Smootify shows only the events that have a template. On your `.webflow.io` address the debugger lists a name that is not an event as one only custom code can show.
> **The toast shows in the middle of the page.** `smootify-toasts` has no position. Give its class the Fixed position and the offsets you want.
> **No toast after adding to the cart.** The mini cart opened for that add, so Smootify did not show the toast. That is on purpose.
> **An empty line in the toast.** The line is not marked with `toast-item`, or it holds text you wrote. Smootify hides only the `toast-item` parts it has no value for.
> **Two toasts boxes on the page.** Only one `smootify-toasts` belongs on a page. Keep it in a single component that is on every page.
---
# Magic Box
> A box the customer fills with products you choose, under rules you set, that reaches the cart and checkout as one line with its own title, image and discount.
Status: Ready (Smootify 2.0)
Add-on: Magic Box
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/magic-box
URL: https://www.smootify.io/docs/features/magic-box
Updated: 2026-10-11
Magic Box lets the customer build a box: a case of 6 wines, a sampler of 3 coffees, a PC with one part from each category. The customer picks products from the lists you design, and the box goes to the cart as **one line**, with its own title and image. At checkout it stays one line, and it can carry a discount. This page covers the Shopify side, the box, its rules, categories and the box panel.
> [!NOTE]
> Turn on **Magic Box** in the Smootify dashboard of the site. It is included in every plan, the free one too, but it always needs to be turned on, also on your `.webflow.io` address. Turning it on adds the **Magic Box** metaobject definition to Shopify and the checkout function that merges the box into one line.
## How it fits together
Everything goes inside one Custom element, `smootify-magic-box`.
What Smootify needs:
- a `smootify-magic-box` element;
- inside it, the products the customer can pick, each with its own add-to-cart form. Inside the box, these forms add to the box instead of the cart;
- inside it, a `smootify-magic-box-cart`: the box panel. It holds a `box-item` template, repeated once per product in the box, and a Form Block that adds the whole box to the cart. Without it, the box is removed.
What is up to you:
- how the products are listed: a Collection list, several lists, a Slider or Tabs;
- where the box panel sits (a sidebar, a sticky bar, under the products) and what it shows;
- the layout, classes and styles of every part.
A fixed box of 6 looks like this in the Navigator:
```html
{Name}
Added
Something went wrong
Product
1
Your box is empty
Added
Something went wrong
```
## 1. Give the box a title, an image and a discount
In the cart and at checkout, the box line shows a title and an image of its own. There are two ways to set them.
**A Magic Box entry in Shopify.** In Shopify, go to **Content > Metaobjects > Magic Box** and create an entry with a **Title**, an **Image** and, if you want one, a **Discount Percentage**. On `smootify-magic-box`, set `data-handle` to the entry’s handle. The merchant can then change the title, the image or the discount in Shopify without touching the site.
**A static box.** Set `data-title` and `data-image` on `smootify-magic-box`. The image must be a Shopify file: upload it in Shopify under **Content > Files** and copy its URL. Checkout ignores an image from anywhere else.
> [!WARNING]
> The discount can only come from a Magic Box entry. A static box has no discount.
The discount applies to every box the customer can add to the cart under its limits, not only to a full one. For a discount only on a full box, give the box the same `data-min` and `data-max`, such as `data-min="12" data-max="12"`.
The discount is a percentage off the whole box, applied at checkout.
## 2. Build the box
1. Add a Custom element with the tag `smootify-magic-box` around the products and the box panel.
2. Inside it, list the products. Any list works: a Collection list of [product cards](https://www.smootify.io/docs/build/product-cards), or a list loaded from Shopify without the CMS ([product lists](https://www.smootify.io/docs/build/product-lists)). Each card needs a `smootify-add-to-cart` with a Form Block inside. Its button adds the product to the box. Variant pickers and a quantity field work as on the [product page](https://www.smootify.io/docs/build/product-page).
3. Inside `smootify-magic-box`, add a Custom element with the tag `smootify-magic-box-cart`. Put inside it:
- a Custom element with the tag `items-list`, holding one Custom element with the tag `box-item`. The `box-item` is the template: Smootify repeats it for each product in the box. `items-list` shows only while the box has products;
- optionally, a Custom element with the tag `empty-state`, shown only while the box is empty;
- a Form Block with a submit button. It adds the whole box to the cart.
Inside `box-item`:
- `box-item="title"`, `box-item="image"`, `box-item="price"`, `box-item="total"` (price × quantity), `box-item="quantity"` and `box-item="category"` show the product in the box;
- the `product` and `variant` values also work, such as `variant="title"` for the variant name. See [box item values](https://www.smootify.io/docs/reference/attributes/box-item);
- a Custom element with the tag `button` and `data-action="remove"` takes the product out of the box.
After a product goes into the box, the quantity field of its form goes back to 1.
## 3. Set the size of the box
These attributes go on `smootify-magic-box`:
| Attribute | What it does | Default |
|---|---|---|
| `data-min` | the fewest products the box can hold | 1 |
| `data-max` | the most products the box can hold | 100 (also the highest value) |
| `data-step` | the box size must be a multiple of this | 1 |
| `data-unique` | the same product can go in only once | off |
The box can be bought when the number of products is **between `data-min` and `data-max` and a multiple of `data-step`**. The add button stays disabled until then. Some sizes and the boxes they allow:
- **A fixed size.** `data-min="6"` and `data-max="6"`: exactly 6 products.
- **Sizes you choose.** `data-min="6"`, `data-max="12"`, `data-step="6"`: 6 or 12, nothing in between.
- **A free mix with a limit.** `data-max="10"` alone: from 1 to 10 products.
- **Sizes that do not share the step.** With `data-min="10"`, `data-max="25"` and `data-step="15"`, the box can be bought with 15 products only: 10 and 25 are not multiples of 15. A box of 10 or 20 needs `data-step="10"`.
> [!WARNING]
> Every size must be a multiple of the same step. A box of 1, 3 or 6 cannot be written with one step.
`data-unique` and the category rules below are on whenever they have a value, even `false`. Write `true`, and remove the attribute to turn a rule off.
When a product does not fit, the product’s form shows an error in its Error message. You can change the text of each error:
- `data-full-error`: the box is full. Default “Box is full”;
- `data-too-many-error`: the quantity is more than what fits. `{quantity}` in the text becomes the number that still fits;
- `data-only-one`: the product is already in the box and `data-unique` is on. Default “You can add only 1 item inside the box”;
- `data-only-one-per-category`: the category already has a product. `{category}` becomes the category name. Default “You can add only 1 item of {category} inside the box”.
## 4. One product per category
For a box that needs one piece from each group (a base, a sauce and a topping; a PC with a motherboard, a GPU and RAM), split the products into categories.
1. Wrap each group of products in a Custom element with the tag `smootify-box-category`.
2. Give it the custom attribute `data-name` with the category name, such as `Sauce`.
```html
{Name}
Added
Something went wrong
```
Then choose the rules on `smootify-magic-box`. For a pizza with a Base, a Sauce and a Topping:
- `data-unique-per-category="true"`: one product per category. A second pick in the same category shows an error in the Error message of the product's form.
- `data-auto-remove-category="true"`, with the one above: a new pick replaces the old one in its category instead of showing an error. Use it for a configurator.
- `data-all-categories="true"`: the box can be bought only when every category has a product. Until then, the add button stays disabled.
**Steps without interactions.** Put each category in a slide of a Webflow **Slider**, or in a pane of a Webflow **Tabs** element. When the customer adds a product, the Slider or Tabs moves on: to the next empty category with `data-all-categories`, otherwise to the next slide or tab. When the customer removes a product, it goes back to that product’s category.
**Completed categories.** A category gets the class `is-complete` while the box holds at least one of its products, and so do the slide and the tab link that show it when every category in them has one. To show a check mark inside a tab link, put it in an element with `box-condition="category-complete"`; `box-condition="category-incomplete"` is the opposite.
In the box panel, `data-prop="missingCategories"` lists the categories still empty, separated by commas. `separator` changes the separator, and `separator="repeat"` makes one copy of the element per missing category.
## 5. Show progress and the price
Inside `smootify-magic-box-cart`, outside the `box-item`, these elements follow the box:
```html
0 / 6
Missing: Base, Sauce
$0.00
$0.00
You save $0.00
```
- `data-prop="currentQuantity"`, `"minQuantity"`, `"maxQuantity"`, `"leftQuantity"` (how many more fit): numbers;
- `data-prop="leftQuantityWidth"` on a Div block: its width follows how full the box is, for a progress bar. `data-prop="leftQuantityPercentage"` shows the same as text;
- `data-prop="discount-percentage"` and `data-prop="discount-amount"`: the discount of the box, as a percentage and as money;
- a `smootify-price` shows the box total after the discount in `data-prop="price"`, and the total before the discount in `data-prop="compareAtPrice"` when there is one;
- `box-condition="can-purchase"` and `box-condition="cannot-purchase"` show an element only while the box can, or cannot yet, be bought.
`smootify-magic-box-cart` also gets classes you can style as combo classes. For a box of 6 wines at $20.00 each, the panel goes through these states:
| State | Classes on `smootify-magic-box-cart` | What shows |
|---|---|---|
| Empty | `is-empty`, `is-disabled` | `empty-state` instead of `items-list`, `box-condition="cannot-purchase"`; the add button is disabled |
| Filling, 4 of 6 | `is-disabled` | `items-list`, `box-condition="cannot-purchase"`; the add button is disabled |
| Ready, 6 of 6 | `is-purchasable` | `box-condition="can-purchase"`; the add button is enabled; `data-prop="price"` $120.00 |
| Ready, with a 10% discount from a Magic Box entry | `is-purchasable`, `is-saving` | `data-prop="price"` $108.00 after the discount, `data-prop="compareAtPrice"` $120.00 before it, `data-prop="discount-amount"` $12.00 |
The full lists are in [Magic Box values](https://www.smootify.io/docs/reference/attributes/magic-box) and [Magic Box conditions](https://www.smootify.io/docs/reference/conditions/magic-box).
**The add button.** A Form Button works, or a Custom element with the tag `button` and `type="submit"`. It is disabled until the box can be bought. A second Custom element with the tag `button`, `type="submit"` and `name="buy-now"` adds the box and goes straight to checkout. `data-buy-now="true"` on `smootify-magic-box-cart` sends every submit to checkout.
**Extra fields.** A field in the box’s form, such as a gift message, is saved with the box, under its field name, and shows with the box in the cart. A name that starts with `_` is saved but not shown.
## 6. In the cart and at checkout
The box is one line in the cart, with the box title and image. In the [cart](https://www.smootify.io/docs/build/cart), a `box-item` inside the `cart-item` template repeats once per product in the box, with the cart line values such as `box-item="title"` and `box-item="quantity"`. The customer cannot change the quantity of a box: its quantity field is removed. With a `box-item` in the template, `cart-item="options"` lists only the box’s own fields, such as a gift message. Without one, it lists the products as text (“1 x Wool Socks”), as 1.x did.
At checkout Shopify shows the box as one line, with its title, image and discount.
**Subscriptions.** A box cannot be sold as a subscription: Shopify does not merge cart lines that have a selling plan. See [subscriptions](https://www.smootify.io/docs/features/subscriptions) for what you can build instead.
Developers can add a box to the cart from code with [`addBoxToCart`](https://www.smootify.io/docs/reference/api/methods/addboxtocart). They can also change the box title and image as the box fills, for example “Box of 4 wines”: see [the Magic Box example](https://www.smootify.io/docs/developers/events#example-a-magic-box-title-that-follows-its-contents).
## Before you publish
On your `.webflow.io` address, the Smootify debugger lists the Magic Box elements and tells you when they are not running. See [the debugger](https://www.smootify.io/docs/start/debugger).
> [!STUCK]
> - **Products go straight to the cart, one at a time.** Magic Box is not turned on in the Smootify dashboard. It needs to be on, also on `.webflow.io`.
> - **The whole box disappears.** `smootify-magic-box-cart` is missing, or `data-handle` does not match the handle of a Magic Box entry in Shopify.
> - **The add button never turns on.** The number of products is not between `data-min` and `data-max`, or not a multiple of `data-step`, or a category is empty with `data-all-categories`.
> - **There is no discount.** The box is static (`data-title`, `data-image`). Only a Magic Box entry carries a discount.
> - **The box image is missing at checkout.** The `data-image` URL is not a Shopify file. Upload it under **Content > Files**.
> - **A rule is on though you wrote `false`.** These rules turn on with any value. Remove the attribute instead.
## Coming from 1.x
- The default texts of `data-only-one` and `data-only-one-per-category` say “inside the box”, where 1.x said “inside the cart”.
---
# Product add-ons
> Offer other products next to the main one, in its add-to-cart form, with checkboxes, selects, dropdowns, popovers or swatches, and send them to the cart as one line with the product.
Status: Ready (Smootify 2.0)
Add-on: Magic Add-ons
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/magic-add-ons
URL: https://www.smootify.io/docs/features/add-ons
Updated: 2026-10-08
Add-ons are other products the customer can add together with the one on the page: a gift wrap, an extended warranty, a case for a phone. They sit in the product’s add-to-cart form, and they go to the cart **together with the product**, as one line. This page covers the Shopify side, the five kinds of add-on picker, prices and quantity limits.
> [!NOTE]
> In the Smootify dashboard, turn on **Magic Add-ons** and **Magic Box**. Both are free. Magic Add-ons adds the product metafields **Add ons** and **Combinations** and the **Combinations** metaobject definition to Shopify. Magic Box adds the checkout function that merges the product and its add-ons into one line.
## 1. Choose the add-ons in Shopify
1. In Shopify, open the main product and fill its **Add ons** metafield with the products to offer. Any other product metafield that holds a list of products works too, such as one your store already uses for accessories.
2. In **Settings > Custom data**, check that the **Add ons** and **Combinations** product metafields and the **Combinations** metaobject have **Storefront access**. Smootify creates them with it, but a definition that already existed in the store keeps its own setting.
An add-on is an ordinary Shopify product, with its own price, variants and stock.
## 2. Add the add-ons to the form
Add-ons go inside the Form of the product’s `smootify-add-to-cart`. Only the fields inside the Form reach the cart.
1. Inside the Form, add a Custom element with the tag `smootify-product`. Give it `data-id="metafields.custom.add_ons"` and `data-parent-id`, bound to the **Shopify ID** field of the page’s product. Smootify repeats this element once per product in the **Add ons** metafield. For another metafield, write its namespace and key instead, such as `data-id="metafields.custom.accessories"`: its type must be a list of products, and its definition needs Storefront access.
2. Inside it, put one add-on picker (next section) and whatever you want to show about the add-on: `product="title"`, an image, a `smootify-price`.
```html
Added
Something went wrong
```
To offer one fixed product instead of the metafield list, give the inner `smootify-product` that product’s id in `data-id`.
What is up to you: the layout of the list (a grid, a row of cards, a Dropdown), what each add-on shows, and which picker each one uses.
**The fields of a picker take any Name.** Webflow asks for a Name on every checkbox, select or quantity field: write what you like, such as `addon`. Smootify renames the picker’s fields itself, and they never reach the cart line as a property, not even when the customer adds to cart before the add-on has loaded.
## 3. Pick a picker
Each add-on needs one of these Custom elements inside its `smootify-product`:
| Picker | Built with | What it adds, and how to choose no add-on |
|---|---|---|
| `addon-checkbox` | a Webflow Checkbox | the add-on's current variant, while checked. The label text stays as you write it |
| `addon-select` | a Select field | the variant chosen in the select. Smootify writes one option per variant; an option you add without a value, such as "No thanks", means no add-on |
| `addon-dropdown` | a Webflow Dropdown | the variant chosen in the dropdown. The first Dropdown link is the template; `show-price` adds the price after each name. With `allow-unselect`, a "None" entry comes first; without it, the first variant is chosen from the start |
| `addon-popover` | an element with a popover | the variant chosen in the popover. The first link or button in the popover is the template; `show-price` and `allow-unselect` work as in the dropdown |
| `addon-swatches` | Custom elements with the tag `button` | the value or variant of the chosen button, which gets the class `is-active`. The first button is the template: one button per value for a product with one option, one per variant for more. A click on the chosen button removes the add-on; `allow-unselect="false"` prevents that and chooses the first value from the start |
`single-addon-swatch` on `smootify-add-to-cart` keeps only one swatch add-on chosen at a time. The swatch buttons of a product with one option take the swatch parts of the [variant swatches](https://www.smootify.io/docs/build/swatches) (`option="title"`, `option="image"`, `option="bg-color"`).
```html
Choose
```
A `smootify-price` inside a swatch button shows the price of that button’s value. A checked checkbox gives its `smootify-product` the class `is-checked`.
## 4. Prices and quantities
**The total.** With add-ons chosen, `data-prop="total"` of the main `smootify-price` includes them. `bundle="total"`, anywhere in `smootify-add-to-cart`, shows the same total: the product and its chosen add-ons. An Image with `bundle="images"` is copied once per chosen add-on, with that add-on’s picture, each copy stacked over the one before. Both stay hidden while the form has no add-on picker.
**Quantity.** An add-on without its own quantity field follows the quantity of the main product. To let the customer choose it, put a Custom element with the tag `quantity-input` inside the picker, with a Text field inside, as on the [product page](https://www.smootify.io/docs/build/product-page). In an `addon-checkbox`, the field starts at 0, and a quantity above 0 checks the box.
**Limits for all the add-ons together.** On the Text field of an add-on’s `quantity-input`, these custom attributes limit the sum of the add-on quantities, for each unit of the main product:
- `global-min`: at least this many add-ons;
- `global-max`: at most this many;
- `global-total`: exactly this many.
Put them on one add-on’s field only. While the add-ons are outside the limits, the add button is disabled and `smootify-add-to-cart` gets a class. Inside the form, `condition="below-bundle-limits"` and `condition="above-bundle-limits"` show a message only in that state, and `bundle-limit` shows the numbers. A box of chocolates with `global-total="3"` needs exactly 3 add-ons:
| Chosen | Class on `smootify-add-to-cart`, add button | Condition and numbers that show |
|---|---|---|
| 2 of 3: below the limit | `is-below-bundle-limits`; the button is disabled | `condition="below-bundle-limits"`; `bundle-limit="current-total"` 2, `bundle-limit="missing-units"` 1 |
| 3 of 3: within the limit | no class; the button is enabled | neither condition; `bundle-limit="current-total"` 3 |
| 4 of 3: above the limit | `is-above-bundle-limits`; the button is disabled | `condition="above-bundle-limits"`; `bundle-limit="current-total"` 4, `bundle-limit="above-units"` 1 |
**Add-ons and subscriptions.** Add-ons are for one-time purchases. While the customer picks a [subscription](https://www.smootify.io/docs/features/subscriptions) plan, each add-on picker takes the `is-disabled` class, its fields are disabled and the add-on stays out of the total and the cart. Switching back to one-time brings the choice back. On product pages that sell both, style `.is-disabled` as a combo class, for example with lower opacity or by hiding the block, or show a note such as “Gift wrap is for one-time orders”.
## 5. One line in the cart
When the customer adds the product with add-ons, they go to the cart together. With Magic Box turned on, the cart and checkout show them as one line, with the product and its add-ons inside. In the [cart](https://www.smootify.io/docs/build/cart), a `box-item` inside the `cart-item` template lists them. The quantity of that line cannot be changed in the cart.
The line takes its title and image from the first of these that applies:
1. **A combination.** In Shopify, under **Content > Metaobjects > Combinations**, an entry with a **Title**, an **Image** and the **Variants** of the add-ons, without the main product, listed in the main product's **Combinations** metafield. When the chosen add-on variants are exactly those, the line takes that title and image. It needs `use-magic-box="true"` on `smootify-add-to-cart`: without it, Smootify does not read the combinations.
2. **`box-title` and `box-image`** on `smootify-add-to-cart`, when no combination matches. The image must be a Shopify file (**Content > Files**): checkout ignores an image from anywhere else.
3. **The main product**, otherwise: the line keeps its name and image.
For a tablet with a pen and a keyboard as add-ons, three combinations cover every choice: the pen, the keyboard, and the pen with the keyboard.
## 6. Keep add-on products out of lists and search
An add-on such as a gift wrap is a product of its own, so it can show up wherever the store lists products. Keep it out in two places:
- **Search and the CMS.** In Shopify, give the add-on the metafield `seo.hidden` with the value `1`. To set it in the admin, first add a product metafield definition with namespace `seo`, key `hidden` and type integer, with **Storefronts** access on. Shopify then leaves the product out of its search, and so do Smootify’s predictive search and search page. The change takes a few minutes. The CMS sync skips it too, so it gets no page and no item in your Collection lists; an item imported before goes with the next full sync from Smootify Studio (see [CMS collections and sync](https://www.smootify.io/docs/start/cms)). The add-on still loads by its id, so it keeps working on the product page.
- **Collections and product lists.** Keep the add-on out of your collections, including automated ones. A collection page lists only the products in it. A product list that runs a query of its own, such as best sellers, newest products or a custom query, can still show the add-on: the store’s best sellers (`data-id="best-seller"`) come from the whole store. Use a custom query that leaves it out, such as `data-query="-product_type:Add-on"` with `data-sort="BEST_SELLING"`, or pick the products by collection with `data-id="specific-collection"`.
## Before you publish
> [!STUCK]
> - **No add-on shows.** The product's **Add ons** metafield is empty, or the metafield has no Storefront access, or `data-parent-id` is not bound to the `cms:Shopify ID` field.
> - **An add-on disappears.** It is inside an element hidden by Webflow's conditional visibility: the add-to-cart form removes those elements, pickers included.
> - **Add-ons are not added.** The pickers are outside the Form, so their fields never reach the cart.
> - **The product and its add-ons are separate lines at checkout.** Magic Box is not turned on in the Smootify dashboard.
> - **The combination title never shows.** `use-magic-box="true"` is missing, or the combination lists the main product, or its variants do not match the chosen add-ons exactly.
> - **An add-on does nothing.** A subscription plan is picked: add-ons go only with the one-time purchase, and the picker has `is-disabled`.
> - **An add-on is always added.** An `addon-dropdown` or `addon-popover` without `allow-unselect` starts on the first variant.
---
# Fixed bundles (Shopify Bundles)
> Sell the fixed bundles and multipacks made with the Shopify Bundles app, and list what each bundle holds on the product page and in the cart.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/features/fixed-bundles
Updated: 2026-10-01
A fixed bundle is a product made of other products, always the same ones: a starter kit, a pack of three, a gift set. You create it in Shopify with the Shopify Bundles app. For Smootify it is an ordinary product: it syncs to the CMS, it has a product page, and it goes to the cart with the usual add-to-cart form. This page shows how to list what the bundle holds.
> [!NOTE]
> The bundles come from the **Shopify Bundles** app, installed on the Shopify store. There is nothing to turn on in Smootify.
## Fixed bundle, Magic Box or add-ons
There are three ways to sell products together. Pick the one that matches who decides what goes in:
| The customer… | Use | Set up in |
|---|---|---|
| buys a set you decided | a fixed bundle (this page) | the Shopify Bundles app |
| fills a box with products they choose | [Magic Box](https://www.smootify.io/docs/features/magic-box) | Webflow and a Magic Box entry |
| adds extras to one product | [add-ons](https://www.smootify.io/docs/features/add-ons) | the product's **Add ons** metafield |
## 1. Build the bundle’s page
The bundle has its product page like any other product: see [build the product page](https://www.smootify.io/docs/build/product-page). Its price, options and stock come from Shopify. With the Shopify Bundles app, the stock follows the stock of the products inside.
## 2. List what the bundle holds
Add `variant="components"` to an element inside `smootify-product`. Smootify makes one copy of it for each product in the bundle of the selected variant. Inside the copy, these attributes show that product:
- `component="title"`: its name;
- `component="variant"`: its variant, such as “Red / M”. Empty for a product without variants;
- `component="quantity"`: how many of it the bundle holds;
- `component="image"` on an Image: its picture;
- `component="url"` on a link: its product page, on that variant.
```html
```
A copy without any `component` element inside shows a plain text such as “2× Travel mug”. When the selected variant is not a bundle, there is no copy. The list follows the variant: pick another option and it shows that variant’s contents.
What is up to you: which parts you show, and the layout of the list. See [bundle component values](https://www.smootify.io/docs/reference/attributes/component).
## 3. In the cart
The bundle is one line in the cart, with the bundle’s name. In the [cart](https://www.smootify.io/docs/build/cart), a `box-item` inside the `cart-item` template repeats once per product in the bundle, with the cart line values such as `box-item="title"` and `box-item="quantity"`:
```html
Product
1 × Item
```
On a line that is not a bundle, the `box-item` is removed.
**Quotes and custom prices.** When the cart becomes a Shopify draft order (a [quote](https://www.smootify.io/docs/features/quotes), or a cart with a price set by [name your price](https://www.smootify.io/docs/features/name-your-price) or the [product configurator](https://www.smootify.io/docs/features/product-configurator)), the bundle keeps its products, and the order’s additional details list them for the merchant under “Bundle (1)”.
**Buy again.** The buy-again button of the [order pages](https://www.smootify.io/docs/build/orders) leaves bundles out: an order does not say which bundle its products came from.
## Before you publish
> [!STUCK]
> - **The list of contents is empty.** The selected variant is not a bundle in Shopify, or the element with `variant="components"` is outside `smootify-product`.
> - **Each copy shows only "2× Name".** The copy has no `component` element inside. Add the ones you need.
> - **The bundle has no contents in the cart.** The `cart-item` template has no `box-item` inside.
---
# Dynamic properties
> Fields the customer fills before adding to the cart, such as an engraving or a choice, decided product by product in Shopify and designed once in Webflow.
Status: Ready (Smootify 2.0)
Add-on: Dynamic Properties
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/dynamic-properties
URL: https://www.smootify.io/docs/features/dynamic-properties
Updated: 2026-10-11
Dynamic properties are the questions a product asks before it goes to the cart: the text to engrave, a name and number for a shirt, a delivery time, a confirmation box. The merchant decides in Shopify which product asks what. In Webflow you design each kind of field once, on the product template, and each product shows only its own. The answers travel with the cart line to the order.
> [!NOTE]
> Turn on **Dynamic Properties** in the Smootify dashboard. It is free. It adds the **Dynamic Property** metaobject definition and the **Dynamic Properties** product metafield to Shopify. In Shopify, under **Settings > Custom data**, check that both have **Storefront access**: Smootify creates them with it, but a definition that already existed in the store keeps its own setting.
Properties are not variant options. Options make variants, with their own price, SKU and stock. A property changes nothing about the product: it only records the customer’s answer. To make an answer change the price, see the [product configurator](https://www.smootify.io/docs/features/product-configurator).
## 1. Create the properties in Shopify
In Shopify, go to **Content > Metaobjects > Dynamic Property** and create one entry per question. Its fields:
| Field | What it does |
|---|---|
| **Title** | links the entry to Webflow: it is the `label` of the element. It is also shown as the field's label |
| **Property Name** | the name the answer is saved under, shown in the cart, at checkout and on the order |
| **Property Type** | Text, Number, Email, Url, Select, Dropdown, Radio, Checkbox or Swatches |
| **Placeholder** | the placeholder of a text field, or the first, empty entry of a select or dropdown |
| **Values** | the choices of a Select, Dropdown, Radio or Swatches property |
| **Images** | for Swatches: one image per value, in the same order as **Values** |
| **Required?** | the customer must answer before adding to the cart |
| **Custom attributes for the input** | extra rules for a text field, one entry per rule, written `name:value`, such as `maxlength:20` or `min:1` |
On stores set up with an earlier version, the **Title** field may be called **Label**: it works the same way.
Then open each product and pick its properties in the **Dynamic Properties** metafield.
## 2. Add the fields in Webflow
On the product template page, inside the Form of `smootify-add-to-cart`:
1. Add a Custom element with the tag `dynamic-property`.
2. Give it the custom attribute `label`, with the **Title** of the Shopify entry, written exactly the same.
3. Inside it, put the Webflow element its type needs (see below).
```html
```
Smootify gives the field the **Property Name**, the placeholder, the **Required?** setting and the custom attributes from Shopify. A Field Label inside the element takes the **Title** as its text. You design the rest.
**Design once for every product.** Add one `dynamic-property` for each property your products may use. On a product that does not have that property, the element is removed. With `delete-parent`, its parent element is removed instead: use it when the field sits in a wrapper with its own heading or spacing.
### The element for each type
Each **Property Type** needs its own Webflow element inside `dynamic-property`:
| Property Type | Put inside `dynamic-property` | How it shows the choices |
|---|---|---|
| Text, Number, Email, Url | a Text field, with its Type set in its settings | the Placeholder is the field's placeholder |
| Select | a Select field | Smootify writes its options. The Placeholder, when set, is the first entry, with no value |
| Dropdown | a Webflow Dropdown with one Dropdown link, or an element with a popover | the first Dropdown link, or the first link or button inside the popover, is the template of every value; the toggle, or the popover's trigger, shows the choice. The Placeholder, when set, is the first, empty entry |
| Radio | one Radio button | it is the template of every value; its label takes each value |
| Checkbox | one Checkbox | its label takes the **Title**; when checked, the answer is saved as ✓ |
| Swatches | one Custom element with the tag `button` | it is the template of every value: an element with `option="title"` inside it gets the value, an Image with `option="image"` the value's image from **Images**. Without an `option="title"` element, the value replaces everything inside the button. The chosen button gets the class `is-active` |
For a popover in place of the Webflow Dropdown, see [popovers](https://www.smootify.io/docs/build/popovers). A required Dropdown or Swatches property left empty gives the class `is-invalid` to the element that shows it, so you can style the error.
**Show the answer elsewhere.** A text element with the custom attribute `form-value` and the field’s Name in Webflow, inside the same `smootify-product`, shows the answer as the customer types, such as the recipient’s name on a gift card preview: a text element with `form-value="Recipient name"` after “for”. Give it `data-fallback="Anna"` to show a sample name until the customer writes one. For a choice, it shows the chosen label. It reads any field of the add to cart form, a plain field as well as a dynamic property.
## 3. What reaches the order
The answer is saved on the cart line under the **Property Name**. It shows in the [cart](https://www.smootify.io/docs/build/cart), at checkout and on the order in Shopify.
**Hidden properties.** A **Property Name** that starts with `_`, such as `_production_code`, is saved on the order but not shown in the cart or at checkout. Use it for data the merchant needs and the customer does not.
Developers can check or complete the answers before they reach the cart, for example to reject an engraving that is too long: see [filters](https://www.smootify.io/docs/developers/filters).
## Before you publish
> [!STUCK]
> - **No property shows.** The definitions are missing because Dynamic Properties was never turned on, or the metafield or the metaobject definition has no Storefront access, or the product's **Dynamic Properties** metafield is empty.
> - **One property never shows.** Its `label` is not written exactly like the entry's **Title**.
> - **The answer is not in the cart.** The `dynamic-property` is outside the Form, or the entry has no **Property Name**.
> - **A choice property is empty.** The entry's **Values** field is empty.
> - **Swatch images are mixed up.** The **Images** are not in the same order as the **Values**.
> - **The field disappears on some products.** That is expected: those products do not have the property. Add `delete-parent` to remove its wrapper too.
---
# Product configurator
> Prices calculated from the customer's choices with a formula, such as a price per letter, per square metre or per material, shown on the page and charged at checkout.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Product configurator
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/product-configurator
URL: https://www.smootify.io/docs/features/product-configurator
Updated: 2026-10-10
The product configurator calculates the price from what the customer chooses: 30 cents per engraved letter, a price per square metre, a wood that costs 40% more. Each choice is a form field with a small formula. The price on the page follows the choices, and the customer pays that price at checkout. It covers what Shopify variants cannot: free sizes, long lists of options, extras that add up.
> [!NOTE]
> The configurator needs the **server** plan and **Product configurator** turned on in the Smootify dashboard. Without them, the configurator fields are removed from the page and `configurator-share` does nothing. It runs on the site's licensed domain. On your `.webflow.io` address it runs once the site is paid, not during a trial.
## How it fits together
What Smootify needs:
- the configurator fields inside the Form of the product’s `smootify-add-to-cart`;
- one Custom element per field, with one input inside;
- a formula for each field, or for each choice of a field.
The price starts from the price of the selected variant. Each field that is **on** (filled in, checked, chosen) applies its formula, in the order the fields appear on the page, and passes the result to the next one. Here, with a variant at $120.00 per square metre and the fields of step 2:
| Step | Price |
|---|---|
| the variant price: `price` starts here | $120.00 |
| `configurator-dimension`, `price * width * height`, with 1.5 × 0.8 m | $144.00 |
| `configurator-select`, the choice `Walnut\|price * 1.4` | $201.60 |
| `configurator-checkbox`, `price + 5`, not checked: off, so skipped | $201.60 |
| `smootify-price`, `data-prop="price"` | $201.60 |
| `smootify-price`, `data-prop="total"`: the price times the quantity, 2 | $403.20 |
Each field is a Custom element with one of these tags:
| Element | Put inside | On while | Formula |
|---|---|---|---|
| [`configurator-input`](https://www.smootify.io/docs/reference/elements/configurator-input) | a Text field or a Text area | it holds a valid value | `data-formula`, which can use `length` |
| [`configurator-checkbox`](https://www.smootify.io/docs/reference/elements/configurator-checkbox) | a Checkbox | it is checked | `data-formula` |
| [`configurator-radio`](https://www.smootify.io/docs/reference/elements/configurator-radio) | Radio buttons | a radio is chosen (clicking it again unchecks it) | in each radio's Choice value, after a `\|` |
| [`configurator-select`](https://www.smootify.io/docs/reference/elements/configurator-select) | a Select field | a choice with a value is chosen | in each option's value, after a `\|` |
| [`configurator-dropdown`](https://www.smootify.io/docs/reference/elements/configurator-dropdown) | a Webflow Dropdown and a Select field | a choice is made | in the options of the Select field, after a `\|` |
| [`configurator-popover`](https://www.smootify.io/docs/reference/elements/configurator-popover) | a popover and a Select field | a choice is made | in the options of the Select field, after a `\|` |
| [`configurator-dimension`](https://www.smootify.io/docs/reference/elements/configurator-dimension) | Text fields with the Type Number | every one holds a valid number | `data-formula`, which can use each field's Name |
| [`configurator-file-input`](https://www.smootify.io/docs/reference/elements/configurator-file-input) | an upload area | at least one file is uploaded | `data-formula`, which can use `file` |
| [`configurator-field`](https://www.smootify.io/docs/reference/elements/configurator-field) | configurator fields, to group them | never: it only groups | a `data-formula` on it does nothing |
What is up to you: which fields a product uses, their order, labels, wrappers and styles.
Each field’s value is also saved on the cart line under the field’s Name, like any other field of the form, so the merchant sees the choices on the order.
## 1. Write the formulas
A formula is a calculation that gives the new price. It can use:
- `price`: the price so far, starting from the variant price;
- `quantity`: the quantity in the form;
- `length`: in a `configurator-input`, the number of characters typed;
- in a `configurator-dimension`, the Name of each number field, such as `width` and `height`;
- `file`: in a `configurator-file-input`, the number of files uploaded.
| Formula | Price |
|---|---|
| `price + 5` | 5 more |
| `price + 0.3 * length` | 0.30 more per character |
| `price * width * height` | the variant price per square metre |
| `price * 1.4` | 40% more |
| `max(price * width * height, 50)` | per square metre, at least 50 |
**Where the formula goes.** On a field with one value, in the custom attribute `data-formula`. On a field with choices (radio, select, dropdown, popover), in the value of each choice, after a `|`: `Walnut|price * 1.4` is the choice Walnut, with its formula. Write it in the **Choice value** setting of a Radio button, or in the value of a Select field option. The customer and the order see only `Walnut`.
**How formulas are read.**
- `+`, `-`, `*`, `/` and `^` (power) work as in maths, and so do functions such as `round(…)`, `ceil(…)`, `min(…, …)` and `max(…, …)`.
- `%` is the remainder of a division, not a percentage. A `%` at the end of a number is ignored, so `price * 10%` multiplies by 10. For 10% more, write `price * 1.1`.
- A name that does not exist counts as 0.
- A formula that cannot be read changes nothing. A result that is not a number, such as a division by 0, keeps the price as it was.
The formula builder writes a formula from a field and a rule in words, tries it with a price, and warns about names that count as 0 and about `%`:
_The formula builder of this page writes a formula from a field and a rule, and tests it with a price: open the page in a browser to use it. The rules it writes:_
| Rule | Formula |
|---|---|
| add an amount, such as 5 | `price + 5` |
| an amount per character of a `configurator-input` | `price + 0.3 * length` |
| an amount per file of a `configurator-file-input` | `price + 2 * file` |
| the variant price per unit of area of a `configurator-dimension` | `price * width * height` |
| an amount per unit of area | `price + 40 * width * height` |
| a percentage more, such as 40% | `price * 1.4` |
| a percentage less, such as 10% | `price * 0.9` |
| a fixed price | `25` |
| at least a price, such as 50 | `max(…, 50)` around the formula |
| rounded to a whole number, or up | `round(…)`, `ceil(…)` around the formula |
On a field with choices, each choice is its name, a `|` and its formula: `Walnut|price * 1.4`. A choice without a formula keeps the price.
## 2. Build the fields
```html
From 0.5 to 3 m
From 0.5 to 3 m
$0.00
$0.00
```
- **One input per element.** Each Custom element handles one input. For three questions, use three elements.
- **One Name per field.** Give every field its own Name in its settings. Two fields with the same Name count as one.
- **Dimensions.** Only Text fields with the Type Number count; Smootify removes the others. Their Names are the names in the formula, so use one plain word, such as `width`. `min`, `max` and `step` custom attributes set what is valid; for decimals, set `step`, such as `0.01`.
- **Preselected values count.** A value a field already has when the page opens is in the price from the start: a `value`, a checked checkbox or radio, a `selected` option set in the Designer, and what the browser puts back on Back. For a step that should start off, leave it empty and unchecked.
- **Numbers start at their minimum.** An empty Number field with `min`, in `configurator-dimension` or `configurator-input`, starts at its minimum, and the price with it: a desk from 100 × 60 cm opens at 100 × 60, priced. Its placeholder shows only if the customer empties it.
- **Radios.** Clicking the chosen radio again unchecks it and turns its formula off.
- **Dropdown and popover.** The Select field inside holds the choices and their formulas, and Smootify hides it. The first Dropdown link, or the first link or button inside the popover, is the template of every choice. See [popovers](https://www.smootify.io/docs/build/popovers).
- **Files.** `configurator-file-input` is an upload area built like the [file uploads](https://www.smootify.io/docs/features/file-uploads). Its files are saved on the line like a `file-uploader`’s.
**Error messages.** A Custom element with the tag `error-message` shows only while its field is not valid, for example when the customer tries to add a size out of range. Put it anywhere inside `configurator-input` or `configurator-radio`. In `configurator-dimension`, put it right after its number field. The element and the field also get the class `is-invalid`. The browser shows no message of its own, so every field with limits or a required value needs its error state, with a text that says the limits, such as “Between 60 and 90 cm”: without it, Add to cart stops and the customer does not know why, or what to type.
```html
Write the text to engrave
```
**Required fields.** Turn on **Required** in a field’s settings (on a field inside a component, where Webflow cannot keep it, give the input the custom attribute `data-required`; `False` turns it off), with `min` and `max` on a size, so that Add to cart and Buy now stop on an empty field and the browser points to it. On a product template that every product shares, put the configurator in a block shown only on the products that have one, such as one with `if-metafield`: while a condition hides the block, its required fields do not count, and the other products add to the cart as usual.
## 3. Prices by quantity
A price table by quantity, such as a lower price per item from 10 pieces, goes inside the `quantity-input` of the form, as a Code Embed that the tool below copies for you. Each row applies from its quantity, included, up to the next row: with rows at 10 and 50, one item costs the first price from 10 to 49 pieces and the second from 50 on. Below the first row, the variant price applies.
The price of the row replaces the variant price, then the field formulas apply. It is charged even when no field changes the price. Build the table, copy it, and paste it in the Designer with `quantity-input` selected:
_The prices-by-quantity tool of this page builds a Webflow Code Embed from a table of quantities and prices, copied as a Webflow element: open the page in a browser to use it._ Each row is a quantity and the price of one item from that quantity on, such as 10 → 12.50 and 50 → 10.00: from 10 to 49 items one costs 12.50, from 50 on 10.00, below 10 the variant price. Paste it inside the `quantity-input` of the form.
## 4. On the page and at checkout
A `smootify-price` in the product shows the configured price in `data-prop="price"`, and the price times the quantity in `data-prop="total"`.
**Add to cart.** The line goes to the cart with the configured price. When the fields leave the price as it was, the line is an ordinary one.
**Checkout.** A cart with a configured price checks out through a Shopify draft order: the customer pays on the draft order’s invoice page, not on the usual checkout. Every line of the cart goes into that order, except subscription lines. The cart is emptied when the customer leaves for the invoice.
**Buy now.** A Custom element with the tag `button`, `type="submit"` and `name="buy-now"` goes straight to the invoice for this product and its add-ons. The rest of the cart stays as it was.
The buy-again button of the [order pages](https://www.smootify.io/docs/build/orders) leaves configured products out: a cart cannot carry their price.
**Show the choices in the page.** A summary such as “How your price is made” can follow what the customer chooses, as they type. Inside the `smootify-product` of the form, add these custom attributes to text elements. A field is named by its **Name** in Webflow’s settings, such as `Engraving` or `Gift wrap`; for a dimension, by the Name of one of its number fields.
| Custom attribute | Shows |
|---|---|
| `form-value="Engraving"` | what the customer wrote or chose: the text, the label of the chosen option or of a checked box, the names of the files |
| `form-value="Engraving"` + `data-variable="length"` | the value the formula reads, such as the number of characters (`file` for the number of files) |
| `configurator-price="base"` | the price of the chosen variant |
| `configurator-price="Engraving"` | what that choice adds to the price |
| `configurator-price="total"` | the configured price of one item |
| `configurator-formula="width * depth / 10000"` | a calculation with the same names as the formulas, with `price`, `base` and `quantity`; `data-digits` sets the decimals (2 by default) |
| `if-configurator="Engraving"` | shown only while that choice counts: filled, checked or chosen, and not hidden by a condition. `unless-configurator` is the opposite |
```html
Top € 0.00
Surface 0 × 0 cm = 0 m² € 0.00
Engraving “”, 0 characters € 0.00
Your desk € 0.00
```
**Before the customer chooses.** Add `data-fallback` with a text to show in its place: on `form-value`, while the field is empty (with `data-variable`, also while the field does not count in the price); on `configurator-price="Engraving"`, while that choice does not count (without it, the price shows 0); on `configurator-formula`, while one of its names has no value yet. `base` and `total` always have a value. An empty `data-fallback` leaves the element empty, and a row hidden by `if-configurator` stays hidden.
**Two fields, one Name.** On a template every product shares, two configurator fields can have the same Name, such as the engraving of a desk and of a shelf in blocks shown on different products. Each counts on its own, and the summary reads the one on show.
The same `form-value` shows the answers of [dynamic properties](https://www.smootify.io/docs/features/dynamic-properties).
## 5. Save and share a configuration
A customer who configures a desk rarely buys it on the first visit. `configurator-share` gives them a link to the same configuration, to open again on another device or to send to someone else.
Add a Custom element with the tag `configurator-share` inside the product’s `smootify-add-to-cart`, with a Custom element with the tag `button` inside. A click builds a link to this page with every choice in it, one parameter per field under its Name (`?Width=180&Finish=walnut`), plus the variant when the product has more than one. On phones it opens the share sheet; elsewhere it copies the link. With `data-copy` it always copies. `data-title` is the title of the share. Style the moment with the combo classes `is-copied` or `is-shared`, which last two seconds.
```html
```
Whoever opens the link sees the fields already filled, as if they had typed and chosen them, and the price that follows. A value a field would refuse stays off, such as a width below its `min` or an option the select no longer has. A file the customer uploaded is not in the link: it is uploaded again.
To keep a configuration in your store instead, with your own price check, use a quote: the cart’s quote button turns the configured lines into a draft order. See [Quotes](https://www.smootify.io/docs/features/quotes).
## Before you publish
Test an order on the licensed domain, from the product page to the invoice page. The invoice page already has the discount codes of the cart, and accepts more codes and gift cards. It is not the usual checkout: check the payment methods the customer sees there.
> [!STUCK]
> - **The fields disappear.** The site is not on the server plan, or Product configurator is not turned on.
> - **The price does not change on `.webflow.io`.** During a trial, server add-ons run only on the licensed domain.
> - **The price does not change on a gift card.** Shopify issues a gift card for its variant's value whatever was paid, so the configurator leaves a gift card product's price alone. For a gift card at any amount, see [Name your price](https://www.smootify.io/docs/features/name-your-price#a-gift-card-of-any-amount).
> - **A formula does nothing.** It is on `configurator-field`, it cannot be read, or its field is not on (empty, unchecked, invalid).
> - **The price is 10 times too high.** The formula uses `%` as a percentage. Write `price * 1.1` for 10% more.
> - **A size field is never valid.** The number has decimals and the field has no `step`, or the value is outside `min` and `max`.
> - **Add to cart does nothing on a configurable product.** A field is out of range or empty and the page has no `error-message` or `is-invalid` style to say so.
> - **An error message always shows.** In `configurator-dimension`, it is not right after its number field.
> - **A product without a configurator cannot be added to the cart.** Its required fields are in a block hidden with CSS or `display: none`. Hide the block with a condition, such as `if-metafield`, or with Webflow's conditional visibility: hidden that way, the fields stop being required.
> - **Two choices count as one.** The fields share the same Name.
> - **The shared link opens with empty fields.** The fields have no Name, or a different one from when the link was made: the link names each choice after its field.
## Coming from 1.x
- Formulas no longer need square brackets: `price + 1` and `[price] + 1` give the same result.
- A row of the prices by quantity applies from its quantity: a 1.x row at 10 started at 11. To keep the 1.x thresholds, build the table again with each quantity one higher.
- The **Draft Orders** add-on is no longer needed for the configurator.
- `configurator-file-input` uses the upload markup of [file uploads](https://www.smootify.io/docs/features/file-uploads) instead of the 1.x upload widget.
- Buy now with a configured price no longer empties the cart, and the formulas of radios and selects work without a `data-formula` on the element.
---
# Name your price
> Let the customer type the amount they pay, between a minimum and a maximum, without new variants, and what happens to that price from the cart to the order.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Name your price
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/name-your-price
URL: https://www.smootify.io/docs/features/name-your-price
Updated: 2026-10-09
With Name your price, the customer types the amount they pay: a donation, a [gift card of any amount](#a-gift-card-of-any-amount), a “pay what you want” download, or an amount agreed with the shop beforehand, such as a deposit. You set the lowest and highest amount. The product stays one product in Shopify: no variant is created for each amount. This page covers the price field and what happens to the price from the cart to the order.
> [!NOTE]
> Name your price needs the **server** plan and **Name your price** turned on in the Smootify dashboard. It runs on the site's licensed domain. On your `.webflow.io` address it runs once the site is paid, not during a trial.
## 1. Add the price field
Inside the Form of the product’s `smootify-add-to-cart`:
1. Add a Custom element with the tag `name-your-price`.
2. Inside it, add a Text field and set its Type to **Number** in its settings.
3. On the Text field, add the custom attributes `min` and `max`: the lowest and highest amount you accept.
4. On `name-your-price`, add `data-invalid-price` with the message for an amount out of range. Without it, the message is “Invalid price”.
```html
Added
Something went wrong
```
> [!WARNING]
> Always set `min`. An empty field starts at the minimum, and the price shown with it. Without `min`, an empty field is a valid amount of 0: the product can be added to the cart for free.
The product keeps a price in Shopify, which can be 0. With the field above, the page shows:
With `min="5"` and `max="500"` on the Text field:
| The field | What the customer sees |
|---|---|
| empty | the product's price in Shopify, until they type an amount |
| 25, quantity 2 | a `smootify-price` shows $25.00 as the price and $50.00 as the total; Add to cart adds it at $25.00 |
| 600 | the `data-invalid-price` message in the form's Error message; nothing is added |
- The amount is not saved as a property of the line: the customer sees a price, not a filled-in field.
- Any `smootify-price` of the product follows the amount as it is typed, such as one in a gift card preview or inside the Buy now button.
- [Add-ons](https://www.smootify.io/docs/features/add-ons) in the same form go in with the product, at their own price.
## 2. From the cart to the order
**Add to cart.** The line goes to the cart with the amount the customer chose. The Smootify cart shows that amount in the line and in the totals.
**Checkout.** A cart with a chosen amount does not go through the usual Shopify checkout. It becomes one Shopify draft order, and the customer pays on its invoice page. Two ways lead there:
| Way | What goes into the draft order | The cart |
|---|---|---|
| Add to cart, then check out | the whole cart, the lines at the normal price too, except subscription lines; the customer pays on the draft order's invoice page | emptied when the customer leaves for the invoice: coming back without paying, they find it empty |
| Buy now: a Custom element with the tag `button`, `type="submit"` and `name="buy-now"` | this product and its add-ons, at once | stays as it was |
On the order, the line carries the chosen price, like any other price.
The [product configurator](https://www.smootify.io/docs/features/product-configurator) uses the same checkout for its calculated prices.
The buy-again button of the [order pages](https://www.smootify.io/docs/build/orders) leaves these lines out: a cart cannot carry a chosen price.
Developers can change the draft order before it is created, or stop the customer from leaving, with the `draft_order_data` and `checkout_url` [filters](https://www.smootify.io/docs/developers/filters).
## A gift card of any amount
Shopify issues a gift card for the value of its variant, whatever the customer paid. So `name-your-price` does not work on a Shopify gift card product: it removes itself, with an error on your testing domains.
For a gift card at any amount, sell a normal product instead:
1. Create a product, such as “Gift card”, with one variant at your lowest amount. Do not charge taxes on it: the tax is charged when the card is spent. It needs no shipping.
2. Add `name-your-price` to its page, as above.
3. Add the fields for the gift with [dynamic properties](https://www.smootify.io/docs/features/dynamic-properties): the recipient’s email and a message.
4. Import the [Gift card at any amount](https://www.smootify.io/flows/gift-card-any-amount) workflow in Shopify Flow. On **Order paid**, it creates a Shopify gift card of the amount paid for the recipient, with the message, and Shopify emails them the code.
The card the recipient gets is a normal Shopify gift card, so it can be spent everywhere, the invoice page included.
## Before you publish
Test an order on the licensed domain, with Add to cart and with Buy now, up to the invoice page. The invoice page already has the discount codes of the cart, and accepts more codes and gift cards. It is not the usual checkout: check the payment methods the customer sees there.
> [!STUCK]
> - **The product goes in at its normal price, with no error.** The `name-your-price` element has no Number Text field inside, so it removed itself. Or the site is not on the server plan, or Name your price is not turned on: the element is removed then too.
> - **Products can be added for free.** The Text field has no `min`.
> - **The price field disappears on a gift card.** The product is a Shopify gift card product: see [A gift card of any amount](#a-gift-card-of-any-amount).
> - **Any amount is accepted.** `min` and `max` are on `name-your-price` instead of the Text field.
> - **It works on the live site but not on `.webflow.io`.** During a trial, server add-ons run only on the licensed domain.
> - **The customer comes back to an empty cart.** The cart is emptied when they leave for the invoice.
> - **A subscription is missing from the order.** Subscription lines do not go into the draft order. Sell them in a separate order.
## Coming from 1.x
- The **Draft Orders** add-on is no longer needed.
- Add-ons in the same form now go to the cart with the product. 1.x added the main product only.
---
# File uploads
> Let the customer upload files with a product, such as a logo or a print file, into your store's Shopify Files, with an upload area you design in Webflow.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Product File Upload
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/product-file-upload
URL: https://www.smootify.io/docs/features/file-uploads
Updated: 2026-10-11
With file uploads, the customer adds files to the product before buying it: a logo to engrave, a print file, a photo for a canvas. The files go straight into your store’s **Shopify Files**, not to another service, and the order line says which files belong to it. You design the upload area with Webflow elements. This page covers `file-uploader`, its parts, the limits and the error messages.
> [!NOTE]
> File uploads need the **server** plan and **Product File Upload** turned on in the Smootify dashboard. They run on the site's licensed domain. On your `.webflow.io` address they run once the site is paid, not during a trial.
## How it fits together
Put a Custom element with the tag `file-uploader` inside the Form of the product’s `smootify-add-to-cart`. It must be inside the Form: that is where it writes the files of the line.
- Give it the custom attribute `name`: the name the files are listed under on the order, such as `Artwork`. Without it, the name is `File`.
- Every part inside is optional. The file picker is added for you: Webflow has no plain file field, so Smootify creates a hidden one.
```html
```
The file uploads as soon as the customer picks it. While a file is still uploading, the product cannot be added to the cart: the form waits for it.
## 1. Design the upload area
Add these custom attributes to Webflow elements inside `file-uploader`:
- **`upload="dropzone"`** on a Div block: the area the customer clicks or drops files on. Without it, the whole `file-uploader` is the area.
- **`upload="list"`** on a Div block: it holds one copy of the item per file.
- **`upload="item"`** inside the list: the template of one file. Without it, Smootify makes a plain one.
- **`upload="errors"`** on a Text block: the messages of the files that were refused. It is hidden while there are none.
Inside the item:
- `upload="name"` and `upload="size"`: the file name and size;
- `upload="progress"`: on a Text block, the upload progress as a percentage;
- `upload="preview"` on an Image or a Div block: a preview of an image file, when `data-preview` is on;
- `upload="error"`: why this file failed;
- `upload="remove"` on a link or a Custom element with the tag `button`: removes the file.
What is up to you: the layout, the text of the drop area, which parts the item shows.
**States you can style.** `file-uploader` and each item get these classes. Add them as combo classes:
| Class | On | When |
|---|---|---|
| `is-dragover` | `file-uploader` | a file is dragged over it |
| `is-uploading` | `file-uploader` and the item | a file is uploading: `upload="progress"` counts up, and the form waits for it before adding to the cart |
| `has-files`, `is-done`, `is-image` | `has-files` on `file-uploader`; `is-done` on the item, and `is-image` when it is an image | the file is uploaded: the item shows its name, size and, with `data-preview`, the preview |
| `is-full` | `file-uploader` | the file limit of `data-multiple` is reached |
| `is-invalid` | `file-uploader` | a file was refused: `upload="errors"` shows each refused file and why, such as "page.html: File is of invalid type" |
| `is-error` | the item | the upload failed: its `upload="error"` shows why, such as "Error during upload" |
For a progress bar drawn with CSS, see [CSS](https://www.smootify.io/docs/developers/css).
## 2. Set the limits
These attributes go on `file-uploader`:
| Attribute | What it does | Default |
|---|---|---|
| `data-multiple` | lets the customer upload several files. The value is the most they can upload | one file; 5 with `data-multiple` |
| `data-required` | a file is needed before adding to the cart | off |
| `data-only-images` | accepts images only: JPEG, PNG, WebP, HEIC and GIF | every type except HTML |
| `data-preview` | shows the image in `upload="preview"` | off |
| `max-file-size` | the largest file, with its unit, such as `5MB` or `500KB`: a number alone is in bytes | `20MB` |
| `min-file-size` | the smallest file, such as `50KB` | none |
| `min-resolution` | the fewest pixels of an image, width × height | none |
Without `data-multiple`, a new file replaces the one before. `min-resolution` counts all the pixels: `min-resolution="1000000"` asks for at least one million pixels, such as 1000 × 1000.
`data-required` goes on `file-uploader`, not on a field inside it.
### Shopify’s limits
The files go into your store’s Shopify Files, and Smootify applies Shopify’s own limits, no stricter. No attribute can raise them:
- **Every file:** at most 20 MB. A `max-file-size` above `20MB` does not help: Shopify refuses the file and the upload fails.
- **Types:** every type except HTML: images, PDF, ZIP, AI, PSD, DOCX, CSV and the others. With `data-only-images`, only JPEG, PNG, WebP, HEIC and GIF.
- **Images:** JPEG, PNG, WebP, HEIC and GIF files go in as Shopify images, at most 25 megapixels (width × height, such as 5000 × 5000), with a width and a height no more than 100 times each other. Other image types, such as SVG or TIFF, go in as plain files, without these checks.
- **Videos:** an MP4, MOV or WebM file of up to 20 MB goes in as a plain file, which the merchant opens from the order. It is not a Shopify video.
See [Shopify’s file limits](https://help.shopify.com/en/manual/shopify-admin/productivity-tools/file-uploads).
**Bigger files.** For print files above 20 MB, or videos to keep as videos, file uploads are not the right tool: use an external upload service with the limits you need, such as storage on Cloudflare. If you need one, the team behind Smootify can build it for you on your own Cloudflare account, as an agency project: see [Agency](https://www.smootify.io/agency).
## 3. Error messages
Each refusal has an English message, shown in `upload="errors"`, or in the item’s `upload="error"` when the upload itself fails. To write your own, add `data-error-` followed by the code to `file-uploader`, such as `data-error-max-size="This file is over 5 MB"`.
| Code | When |
|---|---|
| `type` | the file type is not accepted |
| `max-size`, `min-size` | the file is too large, or too small |
| `resolution-low`, `resolution-high` | the image has too few, or too many pixels |
| `ratio` | the image is more than 100 times wider than high, or higher than wide. Default “Image is too narrow or too wide” |
| `max-files` | the customer tries to add more files than `data-multiple` allows |
| `upload` | the upload failed |
| `required` | the customer adds to the cart without a file and `data-required` is on |
| `busy` | the customer adds to the cart while a file is still uploading |
## 4. What reaches the order
The cart line gets the file names under your `name` (`Artwork` in the example). The customer sees them in the cart and at checkout.
The order also holds a link to each file, hidden from the customer. The merchant opens the order and clicks the link to get the file. The link opens the Shopify admin, so it works only for the store’s staff.
**Other places to upload.** The same upload parts work in a `file-input`, which saves the files in a field instead of a cart line: in the cart form ([discounts, notes and progress bars](https://www.smootify.io/docs/build/cart-extras)), in a [form that creates a metaobject](https://www.smootify.io/docs/features/metaobject-creator) and in the [customer’s own data](https://www.smootify.io/docs/build/customer-data). A `configurator-file-input` also counts the files in a price formula: see the [product configurator](https://www.smootify.io/docs/features/product-configurator).
## Before you publish
> [!STUCK]
> - **Nothing happens when the customer picks a file.** The site is not on the server plan, Product File Upload is not turned on, or you are on `.webflow.io` during a trial.
> - **The order has no files.** `file-uploader` is outside the Form of `smootify-add-to-cart`.
> - **A file is refused with no clear reason.** It is an HTML file, which Shopify does not accept, or `data-only-images` is on and the file is not a JPEG, PNG, WebP, HEIC or GIF image. Tell customers which types you accept.
> - **A big file fails to upload.** It is above 20 MB, Shopify's limit, even with a higher `max-file-size`. For bigger files, use an external upload service: see Shopify's limits, above.
> - **The product cannot be added.** A file is still uploading, or `data-required` is on and no file was uploaded.
> - **`data-required` does nothing.** It is on a field inside instead of on `file-uploader`.
> - **The file link seems broken.** It opens the Shopify admin, which needs a staff login.
## Coming from 1.x
- The upload area is no longer the 1.x widget with its own look: it is your markup, with the `upload` parts above. Styles written for the old widget no longer apply, and `max-file-size` is new.
- Every type Shopify accepts can be uploaded, except HTML: 1.x accepted only PNG, JPG, WebP, CSV, PDF and JSON. Images go up to 25 megapixels, and GIF counts as an image.
---
# Subscriptions
> Sell products by subscription with the selling plans of a Shopify subscription app, show plan prices, and know what works with boxes and add-ons.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/features/subscriptions
Updated: 2026-10-08
A subscription lets the customer receive a product again and again: coffee every two weeks, supplements every month. The plans come from a subscription app on Shopify. Smootify shows them on the product, sells them, and lets customers manage their subscriptions from their account. This page covers the plan picker, prices with plans, and what you can and cannot do with boxes.
> [!NOTE]
> The store needs a subscription app installed on Shopify. The app creates the **selling plans** (such as "Every 2 weeks, 10% off") and attaches them to products. Smootify shows and sells the plans; it does not create them.
## How it fits together
In Shopify, plans come in **groups**: a group such as “Subscribe and save” holds one or more plans, such as “Every 2 weeks” and “Every month”.
- On the product page, `subscription-swatches` lets the customer choose between a one-time purchase and a plan. It is built on a Webflow **Tabs** element: one tab for the one-time purchase, one tab per group, one Radio button per plan.
- The prices on the page follow the chosen plan.
- The chosen plan goes to the cart with the line, and the customer pays at the usual Shopify checkout.
- In the account, the customer sees, pauses, resumes and cancels subscriptions: see [subscriptions in the account](https://www.smootify.io/docs/build/account-subscriptions).
## 1. The plan picker
Inside `smootify-product`, usually in the add-to-cart Form, add a Custom element with the tag `subscription-swatches` and put a Webflow Tabs element inside it.
```html
```
What Smootify does with the Tabs:
- **The first tab and pane** are the one-time purchase. Clicking the tab removes the plan.
- **The second tab and pane** are the template of a group: Smootify copies them once per group and removes any other tab you designed.
- **In the template pane, the first Radio button** is the template of each plan. Its label takes the plan’s name. Use Webflow’s Radio Button element (`w-radio`, with its Field Label): a radio you build from a Div and a custom input is not repeated, and its plan never reaches the cart.
- **Clicking a group’s tab** chooses that group’s first plan, so the price changes at once; the radios then change the plan. When one of the group’s plans is already chosen, the tab keeps it.
- **Prices inside the tabs and radios.** A `smootify-price` inside `subscription-swatches` shows the price of its own option, whatever plan is chosen: in the one-time tab or radio the price without a plan, in a group’s tab the price of the group’s first plan, with the full price struck through. The product’s main price follows the choice.
- `subscription-group="name"` in the tab shows the group’s name.
The picker shows only the plans the selected variant can use, and changes with them. Design these four cases:
| Case | What the picker does |
|---|---|
| A group with several plans | the tab shows the group's name (`subscription-group="name"`), the pane one Radio button per plan; the tab and pane get the class `more-options`, and elements with `group-condition="more-options"` on or inside them show |
| A group with one plan | the tab shows the plan's name instead of the group's, and a click on it chooses the plan; the tab and pane get the class `one-option`, and elements with `group-condition="one-option"` on or inside them show |
| A product sold only by subscription | the one-time tab and its pane are removed; the picker starts on the first plan |
| No plan for the selected variant | `subscription-swatches` is removed when the product or the selected variant has no plan; a group without a plan for the selected variant is dropped |
**Start on a plan.** `auto-select-plan` with a group’s name chooses that group’s first plan when the page opens. Add `auto-select-index` to choose another plan of the group: `1` is the second. See [`subscription-swatches`](https://www.smootify.io/docs/reference/elements/subscription-swatches).
What is up to you: the look of the tabs and radios, the texts, and whether you show the group names at all.
## 2. Prices with plans
Every `smootify-price` of the product follows the chosen plan, except one with `data-ignore="subscription-swatches"`, which keeps the one-time price. Here, a coffee costs $20.00, and its plans take 10% off every 2 weeks and 5% off every month:
| | One-time purchase | A plan chosen ("Every 2 weeks") |
|---|---|---|
| `smootify-price` | $20.00, the one-time price | $18.00, the plan's price; with a discount, its `data-prop="compareAtPrice"` shows the one-time price, $20.00 |
| `smootify-price` in a plan's Radio button | its plan's price | its plan's price ($18.00, $19.00) |
| `smootify-price` with `data-ignore="subscription-swatches"` | $20.00 | $20.00 |
| Shows | `condition="no-subscription"` | `condition="subscription"`; `smootify-product` gets the class `has-subscription` |
The prices and the line under them, as in the second card:
```html
$0.00
$0.00
One-time purchase: $0.00
```
When a plan changes its price after some deliveries, the page shows the first price.
To show the store’s subscription policy, add `policy="subscription"` to a Rich text element: see [policies](https://www.smootify.io/docs/reference/attributes/policy).
## 3. Cart, checkout and account
- In the [cart](https://www.smootify.io/docs/build/cart), a subscription line gets the class `is-subscription`, and the plan’s name is listed with the line’s options (`cart-item="options"`).
- The customer pays at the usual Shopify checkout.
- The customer manages subscriptions in the account: see [subscriptions in the account](https://www.smootify.io/docs/build/account-subscriptions).
- The buy-again button of the [order pages](https://www.smootify.io/docs/build/orders) adds subscription products back as one-time purchases.
> [!WARNING]
> A cart with a price set by [name your price](https://www.smootify.io/docs/features/name-your-price) or the [product configurator](https://www.smootify.io/docs/features/product-configurator) checks out through a Shopify draft order. Subscription lines do not go into that order, and the cart is emptied when the customer leaves for the invoice. Keep subscriptions and custom prices in separate orders.
## 4. Boxes and subscriptions
A box built with [Magic Box](https://www.smootify.io/docs/features/magic-box) cannot be sold as a subscription: it becomes one line at checkout through a Shopify function that merges cart lines. Two other ways to sell a box take plans, the second with fields such as [dynamic properties](https://www.smootify.io/docs/features/dynamic-properties):
- **A Magic Box**: cannot have plans. Shopify does not merge lines that have a selling plan, and the box needs that merge to become one line. The same goes for a product with add-ons sent as one line.
- **Ready-made boxes**: each box is a product of its own in Shopify, such as "Coffee trio", with selling plans. It sells by subscription like any product. The customer's free choice stays a one-time Magic Box.
- **One box product, choices as fields**: one product, such as "Box of 6", with selling plans; the customer writes or picks the contents in fields of its add-to-cart form, such as dynamic properties, and the choices travel with the line. The box has one price, so every product the customer can choose must cost the same. Shopify sells the box, so the stock of the chosen products does not go down: the merchant needs a Shopify Flow workflow, or another tool, that reads the order and lowers it.
**Add-ons.** A product with plans and [add-ons](https://www.smootify.io/docs/features/add-ons) sells the add-ons only with the one-time purchase. While a plan is picked, the add-on pickers take `is-disabled` and the add-ons stay out of the cart.
## Before you publish
> [!STUCK]
> - **The subscription tab changes nothing, and only one plan shows.** The plan template is not a Webflow Radio Button. Replace it with one, keep its classes, and put the plan's name in its Field Label.
> - **The plan picker disappears.** The product, or the selected variant, has no selling plan. Plans come from the subscription app on Shopify.
> - **The one-time tab disappears.** The product can only be bought by subscription.
> - **A tab shows a plan's name instead of the group's.** The group has only one plan: the tab shows that plan and chooses it. Use `group-condition` to design that case.
> - **A box sold with plans does not reach checkout as one line.** Shopify does not merge lines that have a selling plan. Use ready-made box products instead.
> - **The add-ons are not added with a plan.** Add-ons go only with the one-time purchase: style the pickers' `is-disabled` class so the customer sees it.
> - **A subscription is missing from the order.** The cart also had a custom price and went through a draft order, which leaves subscription lines out.
---
# Slot booking
> Let customers book a time slot from a calendar you design in Webflow — an appointment without payment, a paid appointment, or a pickup or delivery window in the cart — with the schedule and the bookings managed in the Shopify admin.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Booking
URL: https://www.smootify.io/docs/features/booking
Updated: 2026-10-13
With Booking, customers pick a time slot from a calendar you design in Webflow. The same calendar works three ways: an appointment booked with a form and no payment, an appointment paid with a product, or a pickup or delivery window chosen in the cart. You set the opening hours and the places of each slot on the product, in the Shopify admin, and you see and manage the bookings there. Smootify counts the places, so two customers never get the last one.
> [!NOTE]
> Booking needs the **server** plan and **Booking** turned on in the Smootify dashboard. It runs on the site's licensed domain. On your `.webflow.io` address it runs once the site is paid, not during a trial.
## 1. Set the schedule in Shopify
Every bookable thing is a product: a haircut, a consultation, a pickup at your shop. Open the product in the Shopify admin. The **Smootify booking** block on its page shows the next bookings; on a product that is not bookable yet it says so, with a **Set up booking** button. **Edit schedule** opens the schedule, the **Booking schedule** action, which is also in the product’s **More actions** menu:
- **Mode:** appointment, pickup or delivery. It also names the slot on the order: “Appointment”, “Pickup” or “Delivery”.
- **Weekly hours**, and the **closed days** or days with **special hours**, such as a holiday.
- **Slot length** and **places per slot**: how long a slot lasts and how many customers it takes.
- **Notice:** how long before a slot it can still be booked. **Days ahead:** how far in the future customers can book.
- **Hold time**, from 5 to 120 minutes: how long a slot stays held for a customer, after a paid appointment is added to the cart or while a pickup or delivery window goes through checkout.
From the same block you cancel a booking with **Cancel** next to it. **Book by hand** opens the **Book a slot by hand** action, also under **More actions**: it books a slot for a phone call or a customer at the counter, even beyond the places, with a warning. A paid booking that found its slot already full shows as a conflict in the block. Every booking is an entry of the **Booking** metaobject, under **Content › Metaobjects**.
Then turn on the checkout rule: in **Settings › Checkout › Checkout rules**, add **Smootify booking**. It stops the checkout of a bookable product that has no slot, or a slot already past. It needs no new permission.
## 2. Design the calendar
`booking-calendar` shows the slots of one product for a number of days. Add a Custom element with the tag `booking-calendar`. Outside a product, give it the custom attribute `data-product-id` with the Shopify ID of the bookable product; inside a `smootify-product` it takes that product. `days` sets how many days it shows at once (7 by default).
What Smootify needs inside it:
- a **day** template, with the custom attribute `booking="day"`: Smootify repeats it for each day of the range;
- inside the day, a **slot** template with `booking="slot"`, repeated for each slot of the day. Make it a Custom element with the tag `button`: a full slot is then disabled for the keyboard too.
Everything else is up to the design:
- **On the day:** `booking="weekday"` (Mon), `booking="day-number"` (12), `booking="month"` (October) or `booking="date"` (Monday 12 October), in the site’s language.
- **On the slot:** `booking="time"`, its time in the time zone of the store’s location; `booking="left"`, the places left; `booking="capacity"`, its places.
- **Anywhere in the calendar:** `booking="range"` (the days shown), `booking="selection"` (the slot picked), `booking="error"` (why a slot could not be held, such as a window someone else took before checkout), and two Custom elements with the tag `button` and `data-action="prev"` or `"next"` to move by a range of days.
```html
12 – 18 October
Mon
12
```
**Style the states with combo classes.** On a day: `is-today`, `is-closed` (no slot that day) and `is-full` (every slot taken). On a slot: `is-selected` when picked and `is-full` when it has no place left; a full slot is also disabled. A click picks a slot, a second click lets it go. `prev` is disabled, with `is-disabled`, on the range that starts today. On the calendar: `is-loading`, `is-error`, `has-selection`, and `is-not-bookable` on a product without a booking schedule.
**One product template for bookable and other products.** Put the calendar in a block shown only on bookable products, such as one with `tagged="bookable"`. While the block is hidden, the calendar asks for nothing.
**A service with its own page.** A visit or a consultation is often kept out of the shop: hidden from search with `seo.hidden` and left out of the CMS sync, so it has no product page. Book it from a static page, such as `/book-a-visit`: put a `smootify-product` with `data-id` set to the service’s Shopify ID, and the calendar, the price and Add to cart inside it. Link the page from the navbar or the footer. Every link to `/products/` of the hidden product goes to a 404.
The availability is kept for a minute, so moving between weeks or opening the page again asks Shopify nothing. A customer’s own booking updates the places at once.
## 3. Choose where the slot goes
The calendar books the slot according to what is around it.
### An appointment without payment
Put the calendar and a Form Block inside a Custom element with the tag `booking-form`. The form books the slot picked; without one it answers “Choose a slot”. The fields whose **Name** is `name`, `email`, `phone` or `note`, in any case (Webflow’s own Name and Email fields work), are saved with the booking; other fields are not sent, and a field is required when you make it Required in Webflow. When the customer is logged in, the booking is linked to them as well. Webflow’s Success and Error messages show the answer. When the slot fills up in the meantime, the Error message shows and the calendar shows its places again.
```html
Monday 12 October
Booked: see you then.
That slot is no longer free.
```
### A paid appointment
Put the calendar inside the product’s add to cart, in its form. When the customer adds the product, the slot is held for the quantity and written on the product’s lines, named “Appointment” or as the `property` attribute of the calendar says. Without a slot, or with a full one, the product is not added and the calendar shows why. If Shopify refuses the addition, or the network fails, the slot is given back right away, so it does not stay held for nobody.
```html
```
### A pickup or delivery window
Put the calendar inside `smootify-cart`, with `data-product-id` set to the product that holds your pickup or delivery schedule. Picking a slot writes it on the cart, as “Pickup” or “Delivery” with the day and time, next to the cart attributes already there. It holds no place yet, so a customer who tries a few windows blocks none of them. At checkout Smootify holds the picked window for the payment. If someone else took it in the meantime, the checkout stops: the calendar shows the reason in `booking="error"`, shows its free places again and comes into view, and the cart form says the slot is no longer available.
**Make the window required** with the custom attribute `data-required` on the calendar, as a delivery usually needs: the checkout stops until the cart has a window of that product. A picked window is enough, and the calendar comes into view with its first free slot when it is missing. The message shows in `booking="error"` and in the cart form’s error, and the calendar gets `is-error`. It is “Choose a slot” unless you write your own in `data-error-required`, such as “Choose a delivery window”. Without `data-required`, or with `data-required="false"` as a Webflow boolean property turned off writes it, the customer can check out with no window.
```html
Mon
```
## 4. Confirm the bookings with Shopify Flow
A paid slot becomes a booking when the order is placed. Smootify’s app gives Shopify Flow two templates; install both from Flow’s templates, on any Shopify plan, Basic included:
- [**Confirm the slots booked with Smootify**](https://www.smootify.io/flows/confirm-booking), on Order created;
- [**Give back the slots of a cancelled order**](https://www.smootify.io/flows/release-booking), on Order cancelled.
When a slow payment finds its slot already full, Flow’s trigger **Smootify booking conflict** starts: use it to tell your staff, so they can move the booking or refund the order.
Every attribute and element is in the Reference: [`booking-calendar`](https://www.smootify.io/docs/reference/elements/booking-calendar) and [`booking-form`](https://www.smootify.io/docs/reference/elements/booking-form).
## Before you publish
Book a slot on your published site in each place you use the calendar, then check the booking on the product’s Smootify booking block.
> [!STUCK]
> - **The calendar shows no days and has the class `is-not-bookable`.** The product has no booking schedule. Set one in the Booking app, or hide the calendar on that product.
> - **The calendar disappears.** It has no `booking="day"` template, or the site's licence has no Booking. Add the template, and turn on Booking in the Smootify dashboard.
> - **The checkout lets a booked product through without a slot.** The checkout rule is off. Add Smootify booking in Settings › Checkout › Checkout rules.
> - **Customers check out without a pickup or delivery window.** The cart's calendar has no `data-required`. Add it.
> - **Orders do not confirm their slots.** The Flow template Confirm the slots booked with Smootify is not installed or not turned on.
---
# Quotes and B2B
> Turn the cart into a quote request that reaches Shopify as a draft order, and show business customers their company, their quantity rules and their prices.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Draft Orders
1.x page: https://docs.smootify.io/docs/cart/draft-orders
URL: https://www.smootify.io/docs/features/quotes
Updated: 2026-10-11
With quotes, the cart does not go to checkout: it sends a request. You receive a draft order in Shopify, with the lines, variants and quantities the customer chose. You set your price, send the invoice, and the customer pays it in Shopify’s checkout. This page builds the quote cart and covers what Smootify shows to business (B2B) customers.
> [!NOTE]
> Quotes need the **Server** plan and **Draft Orders** turned on in the Smootify dashboard. They run on the site's licensed domain. On your `.webflow.io` address they run once the site is paid, not during a trial.
## How it fits together
A quote is a cart. You build it as in [Build the cart](https://www.smootify.io/docs/build/cart), then choose one of two ways:
| Way | Built with | Use it when |
|---|---|---|
| A quote cart | `data-draft="true"` on `smootify-cart`: every submit of its form sends a quote | the store sells only on quote, or on a quote page of its own |
| A quote button in a normal cart | a Custom element with the tag `button`, `type="submit"` and `name="draft-order"`, next to the checkout button, which keeps working | some customers buy and others ask |
What Smootify needs:
- a `smootify-cart` with its Form Block, as for any cart;
- a Text field whose **Name** setting is `email`, so the draft order has an address to send the invoice to.
What is up to you: the layout, the other fields you ask for, and the texts. Write them as a request, not as an order.
## 1. Build a quote cart
```html
Your quote is empty
Thank you. We will send you a quote by email.
Something went wrong
```
In a quote cart, the parts of the Form Block change meaning: the Success message is not the empty cart here. Two conditions work inside `smootify-cart`, `cart-condition="items-in-quote"` and `cart-condition="no-items-in-quote"`. The cart’s four states:
| State | What shows |
|---|---|
| The quote has lines | the **Form**: the lines, the email field and the Form Button that sends the request; `cart-condition="items-in-quote"` shows |
| The request is sent | the **Success message**: write it as "request received", so the customer does not think they have bought; Smootify empties the cart, and `cart-condition="no-items-in-quote"` stays hidden. To open a thank-you page instead, set the Form's **Redirect URL** |
| The quote is empty | `cart-condition="no-items-in-quote"`, outside the Form; the Form and the Success message are hidden, and `smootify-cart` has the class `has-empty-quote` |
| Shopify refuses the request | the **Error message**, with Shopify's errors, as in any cart |
## 2. Or add a quote button to a normal cart
In a cart without `data-draft`, add a Custom element with the tag `button`, the custom attribute `type="submit"` and the custom attribute `name="draft-order"`, inside the Form next to the checkout button. The email field goes in the same Form.
```html
```
A Webflow Button cannot do this: it publishes a link, and a link does not submit the form.
## 3. What the draft order holds
Each field of the form has its place in the draft order:
A quote request and the draft order it creates, under **Orders > Drafts** in the Shopify admin:
| In the quote cart's Form | In the draft order |
|---|---|
| the lines, each with its variant, quantity and properties (such as an engraving) | **Products**: the same lines, variants, quantities and properties |
| a Text field with the Name `email` | **Customer**: the Shopify customer with that email, created when there is none |
| a Text area with the Name `note` | **Notes**: the note of the draft order |
| any other field, such as `Company` or `Phone` | **Additional details**, with the field's Name as the label |
| the currency the customer saw | the currency of the draft order |
- **The lines** keep their properties, such as an engraving or an uploaded file. A Magic Box arrives as the products inside it. A Shopify bundle arrives as its components, with a “Bundle” detail listing them.
- **The customer** is the Shopify customer with the email in the form. If there is none, Shopify creates one with that email. Without an email field, a logged-in customer’s account is used.
Subscription lines are left out: a draft order does not carry the selling plan. See [Subscriptions](https://www.smootify.io/docs/features/subscriptions).
## 4. Send the quote from Shopify
In your Shopify admin, the request is under **Orders > Drafts**, with the discount codes the customer had applied to the cart. Change the prices or add a discount, then send the invoice. The customer gets an email with a link to Shopify’s checkout, at the price you set. Discount codes can still be used there.
To get an email for each new request, import the ready-made [Quote request notification](https://www.smootify.io/flows/draft-order-notification) workflow in the free **Shopify Flow** app. It reacts when a draft order is created: after importing it, replace the default email with the address that should receive the requests.
## 5. Business customers (B2B)
Shopify’s B2B features, with companies, catalogs and quantity rules, are on every paid Shopify plan. Outside Shopify Plus, a store can have at most 3 active B2B catalogs, and it needs Shopify’s new Markets: see [Shopify’s B2B plans](https://help.shopify.com/en/manual/b2b/getting-started/plan-features). When a customer who belongs to a company logs in with [customer accounts](https://www.smootify.io/docs/build/account), Smootify asks Shopify for products and the cart as that company’s buyer, using the company’s first location. The prices and quantity rules Shopify sets for that company then apply on the site.
**Show the company.** `company="name"` shows the name of the customer’s company, anywhere on the page. `company-metafield="…"` shows a metafield of the company, such as a customer code or the name of their sales contact, and `if-company-metafield` shows an element only when that metafield has a value. For a customer without a company, these elements are removed, and so are the `if-company-metafield` elements for a visitor who is not logged in. `unless-company-metafield` does the opposite: it shows its element when the company has no value for that metafield, and to guests, who have no company. Use it for the guest’s Add to cart next to the company’s Add to quote list. See [customer attributes](https://www.smootify.io/docs/reference/attributes/customer).
**Show the quantity rules.** Inside `smootify-product`:
- `variant="quantity-min"`: the smallest quantity that can be ordered;
- `variant="quantity-max"`: the largest, hidden when there is no maximum;
- `variant="quantity-increment"`: the pack size the quantity must be a multiple of.
**Buy for some, quote for others.** Put the quote button inside an element with `customer-tagged="wholesale"` (use your own tag). Only logged-in customers with that tag see it. Everyone else keeps the checkout button.
```html
```
To order the same list again later, customers can use **Buy again** on a past order: see [Orders and buy again](https://www.smootify.io/docs/build/orders). A minimum order value needs a developer: see [Filters](https://www.smootify.io/docs/developers/filters).
## Coming from 1.x
`window._filterDraftOrderData` still works but is deprecated. The [`draft_order_data` filter](https://www.smootify.io/docs/reference/api/filters/draft-order-data) replaces it and applies to every draft order, not only quotes: see [Filters](https://www.smootify.io/docs/developers/filters).
## Before you publish
On your `.webflow.io` address, press Alt+Shift+D (Option+Shift+D on a Mac) to turn on Smootify’s [debugger](https://www.smootify.io/docs/start/debugger), then send a test request and open it in Shopify.
> [!STUCK]
> - **The draft order has no email.** The field's Name setting is not exactly `email`. Webflow renames fields when you copy a form, such as `Email` or `Email 2`. Set the Name back to `email`.
> - **The quote cart goes to checkout.** Draft Orders is not available on this page: check the Server plan, that Draft Orders is turned on in the dashboard, and that the page is on the licensed domain. Without it, Smootify turns a quote cart back into a normal cart, and a quote button does nothing.
> - **Customers think they have ordered.** The Success message still reads like an order confirmation. Rewrite it as a request received.
> - **"Your quote is empty" never shows.** The element with `cart-condition="no-items-in-quote"` is inside the Form, which is hidden while the cart is empty. Move it outside the Form, inside `smootify-cart`.
> - **The quote button opens a page or does nothing.** It is a Webflow Button, which is a link. Use a Custom element with the tag `button` and `type="submit"`.
> - **A subscription is missing from the draft order.** Smootify leaves subscription lines out of a quote.
---
# Wishlist
> A button that saves products for later, a wishlist page, a counter and a link to share the list, kept in the visitor's browser and, for logged-in customers, in their account.
Status: Ready (Smootify 2.0)
Add-on: Wishlist
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons
URL: https://www.smootify.io/docs/features/wishlist
Updated: 2026-10-10
A wishlist lets visitors save products, or single variants, to buy later. Smootify gives you four parts: a toggle that saves or removes an item, a list of the saved items for a wishlist page, a counter for the navbar, and a button that shares the list with a link. Each part works on its own, so you use only the ones your design needs.
> [!NOTE]
> Turn on **Wishlist** in the Smootify dashboard. It is free on every plan. On your `.webflow.io` address it works for testing before you turn it on; on your own domain, `wishlist-toggle` and `wishlist-share` do nothing until it is on. Only turning it on adds the customer metafield **Wishlist Data** (`custom.wishlist_data`) to Shopify, where the list of a logged-in customer is saved. In Shopify, under **Settings > Custom data > Customers**, check that this definition has **Customer accounts access** set to **Read and write**: Smootify creates it that way, but a definition that already existed keeps its own setting.
## How it fits together
- **`wishlist-toggle`** goes inside a `smootify-product`: on the product page or in a product card. A click saves the product, a second click removes it.
- **`smootify-product` with `data-id="wishlist"`**, or **`smootify-variant` with `data-product-id="wishlist"`**, is the card template of the wishlist page. Smootify repeats it once per saved item.
- **`wishlist="count"`** shows how many items are saved, anywhere on the page.
- **`wishlist-share`** shares a link to the wishlist page with the saved items, anywhere on the page.
**Where the list is kept.** It depends on whether the visitor is logged in with [customer accounts](https://www.smootify.io/docs/build/account):
| Who | Where the list is kept | On another device |
|---|---|---|
| A visitor | in their browser | not there |
| A customer logged in with customer accounts | in their browser and in their account, in the customer metafield **Wishlist Data** (`custom.wishlist_data`, with Read and write customer accounts access) | the same list |
| A customer who logs in | Smootify keeps the list that changed last, the one in the browser or the one in the account | the two lists are not merged |
## 1. Add the toggle
Add a Custom element with the tag `wishlist-toggle` inside `smootify-product`. Put what the visitor clicks inside it, such as a heart icon or a Custom element with the tag `button`. The whole toggle is the click area.
```html
```
Outside a `smootify-product`, the toggle is removed. The cards of the wishlist page are the exception: see below.
**Show the state.** Style the toggle’s class as a combo class, for example to fill the heart. To animate it, put a Webflow Rive element inside the toggle: Smootify plays its animations by name, from the toggle’s `data-animation-on` and `data-animation-off` custom attributes. The two states:
| State | What changes |
|---|---|
| A product not saved | the toggle has no class; when a product is removed, a Rive element inside plays the animation named in `data-animation-off` (`off` by default) |
| A product saved | the toggle has the class `is-in-wishlist`; a `button` inside it gets `is-active` and screen readers hear it as pressed; a Rive element plays the animation named in `data-animation-on` (`on` by default) |
**Save the variant.** With `data-use-variants` on the toggle, the click saves the selected variant instead of the product, such as the hoodie in Blue / M. Saved variants count in `wishlist="count"` and show on the wishlist page, like saved products.
## 2. Build the wishlist page
On a page of its own, add a Div block for the grid, and inside it a `smootify-product` with the custom attribute `data-id="wishlist"`. Design it as one product card: Smootify copies it once per saved product, and the copies go next to it, in the same Div block. Only the first `smootify-product` with `data-id="wishlist"` on the page is used.
```html
Product
$0.00
Your wishlist is empty
```
Inside the card, everything a product card can show works: see [Product cards](https://www.smootify.io/docs/build/product-cards). The cards follow the order in which the items were saved. A `wishlist-toggle` in a card removes the item that card shows, product or variant, and the card goes away.
**Cards for the saved variants.** To show saved variants as variant cards, add a `smootify-variant` with the custom attribute `data-product-id="wishlist"` and design it like a [variant card](https://www.smootify.io/docs/build/variants-list). Smootify copies it once per saved variant, and only the first one on the page is used. You can use one template or both, and nothing saved is left out:
| Templates on the page | A saved product shows as | A saved variant shows as |
|---|---|---|
| only `smootify-product` with `data-id="wishlist"` | its product card | the card of its product, with that variant selected |
| only `smootify-variant` with `data-product-id="wishlist"` | the card of its first variant that can be bought (or its first variant) | its variant card |
| both | a card of the `smootify-product` template | a card of the `smootify-variant` template |
```html
Product
Blue / M
$0.00
```
Here the toggle works inside the variant card too, without a `smootify-product` around it.
**Empty or not.** Two conditions work anywhere on the page, for these two states:
| State | What changes |
|---|---|
| A wishlist with items | `condition="has-items-in-wishlist"` shows, anywhere on the page; the body has the class `has-wishlist`; each card has the class `is-wishlist-item` |
| An empty wishlist | `condition="no-items-in-wishlist"` shows, anywhere on the page |
## 3. Show the count
Add `wishlist="count"` to a text element, anywhere on the page, such as next to a wishlist link in the navbar. It shows the number of saved items and updates on each click.
```html
```
What you type is a placeholder until the list is read. The full list of parts is in [`wishlist-toggle`](https://www.smootify.io/docs/reference/elements/wishlist-toggle), [`wishlist-share`](https://www.smootify.io/docs/reference/elements/wishlist-share), [the wishlist cards](https://www.smootify.io/docs/reference/elements/smootify-product-data-id-wishlist) and [wishlist attributes](https://www.smootify.io/docs/reference/attributes/wishlist).
## 4. Share the list
`wishlist-share` sends a friend, a partner or a colleague a link to the wishlist page with the visitor’s saved items, for gift ideas or a choice to approve. Whoever opens the link needs no account.
1. Add a Custom element with the tag `wishlist-share`, with a Custom element with the tag `button` inside, anywhere: on the wishlist page, in the navbar or in the account.
2. Give it the custom attribute `data-page` with the path of your wishlist page, such as `/wishlist`. Without it, the link opens the page it is on.
```html
```
A click opens the device’s share sheet, on phones, or copies the link. With `data-copy` it always copies the link. `data-title` is the title of the share, by default the page’s title. Style the moments with combo classes: `is-copied` or `is-shared` for two seconds after the click, and `is-empty`, with its button disabled, while the list is empty.
**The shared page.** The link opens your wishlist page with the shared items in place of the visitor’s own, with the same card templates and `wishlist="count"`. The visitor’s own list is not touched: on a shared list, the toggles in the cards save to the visitor’s own wishlist, “add it to mine too”. Two conditions tell the two lists apart in the design:
- `condition="shared-wishlist"` shows an element only on a shared list, for a heading such as “A list for you”;
- `condition="own-wishlist"` shows it only on the visitor’s own list, for the share button itself.
The link is a snapshot: it holds the items saved when it was shared, up to 100. Items saved later need a new link, and products that are no longer sold in the visitor’s market do not show.
## Coming from 1.x
- **`data-use-variants` now saves the variant.** In 1.x the attribute did nothing and the toggle always saved the product. Remove it to keep the 1.x behaviour.
- **The events have new names.** Scripts that listen to the wishlist should use `smootify:wishlist:changed_product` and the other `smootify:wishlist:…` names. The 1.x names `smootify_wishlist:…` are still sent until 3.0. See [Events](https://www.smootify.io/docs/developers/events).
## Before you publish
> [!STUCK]
> - **The toggle disappears.** It is not inside a `smootify-product`, or, on the wishlist page, inside a card of a wishlist template.
> - **Nothing happens on the live site.** Wishlist is not turned on in the Smootify dashboard.
> - **The list does not follow the customer to another device.** The customer is not logged in with customer accounts, or the **Wishlist Data** metafield does not have **Read and write** customer accounts access in Shopify.
> - **After logging in, the list is different.** The browser list and the account list are not merged: the one changed last is kept.
> - **The shared link shows the visitor's own list.** The page it opens has no wishlist card template. Set `data-page` on `wishlist-share` to the page that has one.
---
# Recently viewed
> Show the products a visitor opened last, on any page, from their browser or, for logged-in customers, from their account.
Status: Ready (Smootify 2.0)
Add-on: Last Viewed
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/last-viewed
URL: https://www.smootify.io/docs/features/last-viewed
Updated: 2026-10-01
Recently viewed products help a visitor go back to what they looked at. Smootify records each product page the visitor opens and gives you a card template to list those products anywhere: on the home page, in the cart, under a product. There is nothing to record by hand.
> [!NOTE]
> Turn on **Last Viewed** in the Smootify dashboard. It is free on every plan. On your `.webflow.io` address it works for testing before you turn it on, but only turning it on adds the customer metafield **Last Viewed** (`custom.last_viewed`, a list of products) to Shopify, where a logged-in customer's list is saved. In Shopify, under **Settings > Custom data > Customers**, check that this definition has **Customer accounts access** set to **Read and write**: Smootify creates it that way, but a definition that already existed keeps its own setting.
## What counts as viewed
- **A product page.** A product is recorded when the visitor opens its page: the template page of your Products collection, at the address your site uses for products (`products` by default, set by the `productsBase` option). A product seen in a card, a search result or the cart is not recorded.
- **The 10 most recent.** The newest comes first. Opening a product again moves it to the front. On a product page, that product is already first in the list.
- **Browser and account.** Every visitor’s list is kept in their browser. For a customer logged in with [customer accounts](https://www.smootify.io/docs/build/account), it is also saved in their account, and the account list is the one shown when there is one.
## Show the list
Add a Div block for the grid, and inside it a `smootify-product` with the custom attribute `data-id="last-viewed"`. Design it as one product card: Smootify copies it once per product, and the copies go next to it. Only the first `smootify-product` with `data-id="last-viewed"` on the page is used.
```html
```
- **`limit`** sets how many cards show, 10 by default. Smootify keeps 10 products, so a higher number shows no more.
- Inside the card, everything a product card can show works: see [Product cards](https://www.smootify.io/docs/build/product-cards). Each card has the class `is-last-viewed-item`.
**Empty or not.** Two conditions work anywhere on the page, as around the heading above:
- **The list has products:** `condition="has-items-in-last-viewed"` shows while the list has at least one product. The body has the class `has-last-viewed` (on the body only, not on the card template).
- **The list is empty**, until the visitor opens a product page: `condition="no-items-in-last-viewed"` shows. The body has no `has-last-viewed`.
More in [the recently viewed cards](https://www.smootify.io/docs/reference/elements/smootify-product-data-id-last-viewed).
## Before you publish
> [!STUCK]
> - **The list stays empty.** The product pages are not at the address Smootify expects. If your Products collection uses another URL, such as `product`, set `productsBase` in the [Smootify options](https://www.smootify.io/docs/reference/options).
> - **Nothing is recorded on the live site.** Last Viewed is not turned on in the Smootify dashboard.
> - **The list does not follow the customer to another device.** The customer is not logged in with customer accounts, or the **Last Viewed** metafield does not have **Read and write** customer accounts access in Shopify.
> - **Never more than 10 cards.** Smootify keeps the 10 most recent products, whatever the `limit`.
---
# Markets, currencies and languages
> Sell in several countries with Shopify Markets. Covers the country and currency switcher, the pickup location switcher, what changes with the country, and languages with Webflow Localization or Weglot.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/internationalization
URL: https://www.smootify.io/docs/features/markets
Updated: 2026-10-10
Smootify shows each visitor the Shopify market of their country: its currency, its prices and the products it sells. You set up the markets in Shopify. On the site, a country switcher lets the visitor change country, and the language follows Webflow Localization or Weglot. This page says what happens when the country changes and what you build for it.
> [!NOTE]
> Set up your markets in Shopify first, under **Settings > Markets**: the countries, their currencies and the products each market sells. Smootify lists only the countries of your markets. Product translations also come from Shopify, for example from the Translate & Adapt app.
## How Smootify picks the country
From the first of these that gives one, in this order:
1. **The visitor's choice.** A country picked in a country switcher, or with Webflow's locale switcher, is remembered in the visitor's browser and wins from then on.
2. **The page's language, when it names a country.** A Webflow locale such as `fr-CA`, or the language Weglot shows.
3. **The browser's language**, such as `it-IT` for Italy, when you turn on `selectMarketBasedOnBrowserLanguage`. It is off by default.
4. **Otherwise**, your store's primary market.
To turn on the browser’s language, paste this in your site’s custom code, in the head, before the Smootify loader:
```html
```
If you already have a `SmootifyUserOptions` script, add the line to it instead: a second script replaces the first. To suggest the right country to a new visitor, see [Market suggestion](https://www.smootify.io/docs/features/market-suggestion).
## What changes with the country
- **Prices and currency.** Every product, card and the cart show the prices Shopify has for that market, in its currency. They reload in place, without reloading the page, and the cart moves to the new country.
- **Taxes.** Prices are shown as Shopify sends them for the market. Whether they include taxes is a setting of your Shopify store.
- **Sales in Europe.** When your primary market is outside the European Economic Area, Shopify hides the compare-at prices it converts for EEA countries. The reason is the EU Omnibus rule: a price reduction shown in the EEA must also show the lowest price of the 30 days before it, the “Omnibus price”, which a converted compare-at price does not carry. That is why the setting is on by default. There a product on sale shows only its price: no strikethrough, and `condition="on-sale"` stays hidden. To show them, untick **Hide compare-at prices for European Economic Area (EEA) customers** under **Settings > Markets > Preferences**, and show the Omnibus price next to every reduction in those countries.
- **Products the market does not sell.** Their `smootify-product` stays empty and gets the class `w-dyn-bind-empty`. Smootify does not redirect the visitor. Style the class, or hide the block, for those products.
- **Search engines.** The product’s structured data carries the price and currency of the current market. See [SEO and structured data](https://www.smootify.io/docs/start/seo).
## 1. Add a country switcher
Add a Custom element with the tag `country-switcher`, anywhere on the page, usually in the navbar. Put a Webflow **Dropdown** inside it.
```html
United States
USD
```
The Dropdown has two parts, each with its own job:
- **The toggle** shows the current country. Its elements with `data-prop` show the current country's values. The first element of the toggle that has no `data-prop` (the dropdown icon aside) gets the current country's name: put a Text block there, first. Any other element without `data-prop` placed before it, such as a wrapper, would have its content replaced by the name.
- **The links.** The first Dropdown link is the template: Smootify copies it once for each country of your markets and removes the other links you designed. The current country's link gets the class `w--current`. A pick changes the country and closes the dropdown.
**Inside the link and the toggle**, the elements with `data-prop` show a country:
- `data-prop="name"`: the country’s name;
- `data-prop="iso-code"`: its code, such as `IT`;
- `data-prop="flag"`: its flag, on an Image, or as the background of any other element;
- `data-prop="currency-iso-code"`, `"currency-symbol"`, `"currency-name"`: its currency.
**The currency elsewhere.** A Custom element with the tag `currency-code`, anywhere on the page, shows the current currency code, for example in the footer. It is filled only while the page has a `country-switcher`.
**A store that sells in one country.** With a single country in your Shopify markets there is nothing to pick, so `country-switcher` removes itself. It fills the `currency-code` elements first, and the ones outside it stay. Add a second country in Shopify and the switcher shows, with nothing to change on the site.
**A popover instead of the Dropdown.** `country-switcher` also works with a popover: the first link or button inside the popover is the template. See [Popovers](https://www.smootify.io/docs/build/popovers). With both inside, Smootify uses the Dropdown.
**Country and language together.** When the country you pick has the same code as one of your store’s languages, such as `IT` for Italy and Italian, the language changes too. With Webflow Localization, the country switcher goes through Webflow:
- it opens the page of your locale for that language, so the address changes too, and from a product page it lands on the same product. A locale with the country, such as `it-IT`, comes before one without, such as `it`;
- if your site has no locale for that language, only the country changes;
- if the visitor is already on that locale, only the country changes.
Every value is in [country switcher attributes](https://www.smootify.io/docs/reference/attributes/country-switcher).
## 2. Add a pickup location switcher
If your store offers local pickup, a `location-switcher` lets the visitor choose their store once, for the whole site. It is built like the country switcher, on a Dropdown or a popover, and the first link is the template of each location.
```html
Pickup location
```
- `location="name"` and `location="address"` show a location, in the links and in the toggle.
- It lists your Shopify locations that offer local pickup. When the store has none, it is removed.
- The choice is remembered in the visitor’s browser and sent to Shopify as their preferred location. The pickup block of product pages then leaves it out of the list of other stores: see [Pickup and store locator](https://www.smootify.io/docs/features/store-availability).
## 3. Languages
**Webflow Localization.** Smootify reads the locale of the page and asks Shopify for the product texts in that language. Shop policies, metaobjects and Magic Boxes come in that language and in the visitor’s market too, so an entry translated in Shopify, for example with Translate & Adapt, shows translated. Webflow’s locale switcher then changes the Shopify language too, and the country when the locale names one, such as `en-GB`, before it opens the page. On product and collection pages, its links use the handle translated in Shopify, and Smootify adds the alternate links search engines read when the page is missing them. Webflow Localization needs a Webflow site plan that includes it.
> [!WARNING]
> With Webflow Localization, the language changes only through Webflow's locale switcher, which also changes the page's address. Do not add another language picker: the page would stay in its Webflow locale, and the page and the Shopify texts would end up in different languages. The country switcher already goes through Webflow's locales.
**Weglot.** Smootify waits until Weglot has translated the page, then asks Shopify in that language, so prices and texts arrive in the right language the first time. Weglot’s language subdomains, such as `fr.example.com`, are covered by the licence of `example.com`.
For developers, the country and the language can also be changed from code: see [JavaScript API](https://www.smootify.io/docs/developers/javascript-api).
## Selling in the EU
Since 19 June 2026, a store that sells online to consumers in the EU must offer a withdrawal button, which works without logging in. A Webflow site can host it on a page of its own or link to a withdrawal app’s page on Shopify: see [EU right of withdrawal](https://www.smootify.io/docs/features/eu-withdrawal).
## Coming from 1.x
- **Products out of the market** are no longer redirected: their block is hidden, and `condition="product-unavailable"` shows your own message on the page, such as one with a country switcher. See [Build the product page](https://www.smootify.io/docs/build/product-page#7-stock-sale-and-other-states).
- **`location-switcher`** reads `location="name"` and `location="address"` in the toggle too, where 1.x read `data-prop`.
## Before you publish
> [!STUCK]
> - **The switcher is gone, or lists no country.** Your store sells in one country only, so there is nothing to switch. Add markets under **Settings > Markets**.
> - **The switcher disappears.** It has no Dropdown link, or no link in its popover: the first link is the template.
> - **The toggle loses its content.** Its first element without `data-prop` gets the country's name. Put the name's Text block first.
> - **The currency in the footer never changes.** `currency-code` works only while the page has a `country-switcher`.
> - **Sales show everywhere but in Europe.** Shopify hides converted compare-at prices from EEA countries when your primary market is outside the EEA, because they lack the Omnibus price: see *Sales in Europe* above.
> - **Everything is sold out in one country.** No shipping zone covers that country, and Shopify reports the products as unavailable there. Add the country to a shipping zone under **Settings > Shipping and delivery**.
> - **A product block is empty in one country.** That market does not sell the product. Add it to the market's catalog in Shopify, or design for the empty block.
> - **The language changes with the country.** The country has the same code as one of your languages.
> - **Webflow's locale switcher is missing.** Webflow Localization needs a site plan that includes it.
> - **The page and the products are in different languages.** With Webflow Localization, the language was changed by something other than Webflow's locale switcher, such as a language picker of your own or custom code. Change it only with Webflow's locale switcher.
---
# Market suggestion
> A popover that tells a new visitor which country, currency and language the store has for them, once per browser, a few seconds after the page loads.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/features/market-suggestion
Updated: 2026-10-12
A visitor from Italy lands on your site and sees prices in dollars. The market suggestion is a popover that Smootify opens once, a few seconds after the page loads, to tell them the store also sells in their country, with its currency and language. You design it in Webflow. With a country switcher inside it, the visitor changes market in one click.
> [!NOTE]
> The suggestion uses the markets you set up in Shopify: see [Markets, currencies and languages](https://www.smootify.io/docs/features/markets). There is nothing to turn on in the Smootify dashboard.
## How it fits together
What Smootify needs:
- a popover: Webflow’s Popover element, or a Div block with the custom attribute `popover` set to `auto`;
- on it, the custom attribute `data-is` set to `market-dialog`.
What is up to you: the text, the layout, where the panel sits, and which parts you show. There is no trigger to add: Smootify opens the popover itself.
```html
You seem to be in Italy.
We ship there, with prices in EUR.
```
The popover can sit anywhere on the page. Put it in a component used on every page, such as the navbar, so it can open wherever the visitor arrives.
## 1. Show the visitor’s market
Inside the popover, add a custom attribute to a Webflow element to fill it:
| Attribute | What it shows |
|---|---|
| `market-dialog="country"` | the country’s name |
| `market-dialog="flag"` | its flag, on an Image, or as the background of any other element |
| `market-dialog="language"` | its first language, written in that language, such as “Italiano” |
| `market-dialog="currency"` | the currency code, such as `EUR` |
| `market-dialog="currency-symbol"` | the currency symbol, such as `€` |
| `market-dialog="currency-name"` | the currency’s name |
When your store sells in the visitor’s country, these show that country. When it does not, they show the country the visitor is on now:
- **Your store sells in the visitor's country**, such as a visitor from Italy and an Italian market: the `market-dialog` values show that country (Italy, EUR, €, Italiano).
- **Your store does not sell there**, such as a visitor from Brazil: they show the country the site shows now (United States, USD, $, English).
Write a text that reads well in both cases.
## 2. Let the visitor change market
A Custom element with the tag `button` and the custom attribute `data-action="continue"` switches to the market the suggestion shows, when it is not the current one, then closes it. Label it with what it does, such as “Shop in Italy”.
Any other way of closing keeps the current market: a close button of the popover, Escape, a click outside. Give the visitor that choice too, with a button such as “Stay here”.
To let the visitor pick another country instead, put a `country-switcher` inside the popover, built as in [Markets](https://www.smootify.io/docs/features/markets). With a switcher inside, `data-action="continue"` only closes the suggestion: the visitor chooses in the switcher.
## 3. When it opens
- **Once per browser.** Once the visitor closes it, by any of the three ways, it does not come back in that browser. Until then, it opens again on the next page.
- **After a delay.** It opens a few seconds after the page loads. The custom attribute `delay` sets the seconds, 3 by default.
- **After the cookie banner.** If Smootify’s [cookie banner](https://www.smootify.io/docs/features/cookie-consent) is showing, the suggestion waits until it closes, then waits its delay again.
- **Only with a country.** If Smootify cannot tell the visitor’s country, nothing opens, and the next page tries again.
- **Only with more than one country.** A store that sells in one country has nothing to suggest: the popover never opens, and opens as usual once you add a market in Shopify.
**How the country is found.** Smootify asks the site’s own domain: Webflow hosting sits behind Cloudflare, which knows the visitor’s country, so the visitor’s IP address is not sent to any other service. Only where that answer is missing, such as on a site hosted elsewhere, it asks the geolocation service country.is.
> [!TIP]
> To see the suggestion again while you test, open the site in a private window.
## 4. Style how it opens
Custom attributes on the popover: `animation-name` sets how it comes in, with one of the first five values below, and `disable-blur` takes the blur off the page behind it. `animation-duration` sets how long the animation lasts, `300ms` by default.
- `animation-name="fade-in"` (the default): the popover fades in.
- `animation-name="slide-in-top"`: it slides in from the top.
- `animation-name="slide-in-right"`: it slides in from the right.
- `animation-name="slide-in-bottom"`: it slides in from the bottom.
- `animation-name="slide-in-left"`: it slides in from the left.
- `disable-blur`: the page behind it is not blurred.
The rest is your design: position, size and colours. More in [the market suggestion](https://www.smootify.io/docs/reference/elements/popover-data-is-market-dialog) and [its attributes](https://www.smootify.io/docs/reference/attributes/market-dialog).
## Before you publish
> [!STUCK]
> - **It never opens.** The visitor already closed it once in this browser: test in a private window. Or your store sells in one country only: add a market in Shopify. Otherwise, check that the element has both `popover` and `data-is="market-dialog"`.
> - **It opens much later than the delay.** The cookie banner was open: the suggestion waits for it to close, then waits its delay again.
> - **Continue does not change the country.** The popover has a `country-switcher` inside: then Continue only closes it, and the visitor picks the country in the switcher.
> - **It shows the current country, not the visitor's.** Your store does not sell in the visitor's country, so there is no other market to suggest.
---
# Pickup and store locator
> Show on the product page where the selected variant can be picked up, list the other stores, and put your stores on a Mapbox map with a search field.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/internationalization/store-availability
URL: https://www.smootify.io/docs/features/store-availability
Updated: 2026-10-01
If your store has shops or warehouses where customers pick up their orders, Smootify can show them. On the product page, `store-availability` says whether the selected variant can be picked up, and where. On a page of its own, `store-locator` shows every store on a map, with a list the visitor can search. This page covers both.
> [!NOTE]
> Both read your Shopify locations that offer local pickup. Add the locations under **Settings > Locations**, turn on local pickup for each one under **Settings > Shipping and delivery**, and set each product's stock per location on the product. The store locator also needs a free Mapbox account, and the **Lite** or **Server** plan on your custom domain. On your `.webflow.io` address it works on every plan.
## 1. Pickup on the product page
Add a Custom element with the tag `store-availability` inside `smootify-product`. Outside it, the element is removed.
```html
Pick up at Store
Not in stock at Store
Usually ready in 24 hours
Store
Address
0
```
**The first store.** Outside the list, the `location` attributes show the first pickup location Shopify gives for the selected variant, and they follow the variant as it changes:
| Attribute | What it shows |
|---|---|
| `location="name"` | the location’s name |
| `location="address"` | its address, on several lines |
| `location="quantity"` | how many are available there |
| `location="pick-up-time"` | Shopify’s pickup time, such as “Usually ready in 24 hours” |
| `location="directions-link"` | on a link: a Google Maps route to it |
**Its states.** Inside `store-availability`, conditions show what the first location has, and classes follow it. Design these states:
| State | What shows | Classes |
|---|---|---|
| The first location has the variant | `condition="available-at-location"` | `is-available` on `store-availability`, `is-available-at-pickup` on `smootify-product` |
| The first location does not have it | `condition="not-available-at-location"` | `is-available-at-pickup` on `smootify-product` |
| No location offers the variant for pickup | `store-availability` is hidden; elsewhere in `smootify-product`, `condition="not-available-as-local-pickup"` shows | none |
| The visitor opens the panel | one `store-location` per store that offers the variant, except the store chosen in a location switcher; the list loads when the panel opens; the button is hidden when there is only one location | |
**The other stores.** For a list of every store that offers the variant:
1. Inside `store-availability`, add a panel: a Div block with the custom attribute `popover` set to `auto`, or a Custom element with the tag `dialog`.
2. Inside the panel, add a Custom element with the tag `store-location`: the template of one store, with the same `location` attributes. Smootify copies it once per store, except the store the visitor chose in a [location switcher](https://www.smootify.io/docs/features/markets).
3. Outside the panel, add a Custom element with the tag `button`, such as “Check other stores”. It opens the panel.
Without a panel, the button is removed. Add `skeleton="text"` inside `store-location` for a placeholder while it loads. With a `dialog`, the custom attributes `animation-name`, `animation-duration` and `disable-blur` on `store-availability` style its opening, as on the [market suggestion](https://www.smootify.io/docs/features/market-suggestion).
Every value is in [pickup attributes](https://www.smootify.io/docs/reference/attributes/pickup-location) and [pickup conditions](https://www.smootify.io/docs/reference/conditions/pickup).
## 2. A store locator page
**Get a Mapbox token.** In your Mapbox account, create a token with the default public scopes, and limit it to your site’s addresses (your `.webflow.io` address and your domain). Copy it: it is public, and it goes in the page.
**Which stores are listed.** The list comes from Shopify, not from Webflow: Smootify asks Shopify for your store’s locations that offer local pickup, up to 200, and makes one `store-location` per location.
- A location is listed only when, in Shopify, it is active under **Settings > Locations**, it fulfils online orders (**Fulfill online orders from this location**), and local pickup is turned on for it under **Settings > Shipping and delivery > Local pickup**. A warehouse or a shop without local pickup is never listed.
- The name, address and phone are the ones set on the location in Shopify. Change them there: the page follows.
- The map places each store at the coordinates Shopify finds for its address. A location whose address Shopify cannot place stays in the list without a marker.
- Stock does not matter here: the locator lists the stores, not the products they have.
**Build the locator.** Add a Custom element with the tag `store-locator` and give it the custom attribute `data-api-key` with your token. Inside it go these parts, each a Custom element with that tag unless the card says otherwise:
| Part | What it is | Without it |
|---|---|---|
| a Custom element with the tag `map` | the map is drawn in it; give it a height in its styles | the store locator is removed |
| a Custom element with the tag `store-location` | the template of one store in the list | there is no list |
| a Custom element with the tag `map-marker` | the marker of each store on the map, designed as you like, such as an icon | the map has no markers |
| optional: a Custom element with the tag `store-popup` | the card that opens over a marker when it is clicked | a click on a marker moves the map and marks its store in the list, with no card |
| optional: a Form Block with a Text field whose **Name** setting is `query` | searches the list as the visitor types, on any text of the store, such as the city or the postal code | the list is not searched |
| optional: an element with `condition="no-stores"` inside `store-locator` | shows while no store matches the search | an empty list |
```html
```
**What the visitor gets.**
- The browser asks the visitor for their position. With it, the stores are sorted by distance and the map starts there. Without it, the map starts at the first store.
- A click on a store in the list moves the map to it, and the store gets the class `is-active`. A click on a marker opens its `store-popup` and marks its store in the list.
- The search field filters the list as the visitor types.
Inside `store-location` and `store-popup`, `location` shows `name`, `address`, `zip`, `phone` (a Text link with it calls the number), `country` and `directions-link`.
**Map options**, as custom attributes on `store-locator`:
- `data-style`: the style URL of a Mapbox Studio style, `mapbox://styles/mapbox/light-v11` by default;
- `data-initial-zoom`: the zoom when the map opens, 13 by default;
- `data-zoom`: the zoom when the map moves to a store, 13 by default;
- `data-fit-bounds`: `true` by default, the map zooms out to show every store; `false` keeps the initial zoom;
- `data-padding`: the space kept around the stores when the map fits them, 100 pixels by default.
More in [`store-locator`](https://www.smootify.io/docs/reference/elements/store-locator) and [its attributes](https://www.smootify.io/docs/reference/attributes/store-locator).
## Before you publish
> [!STUCK]
> - **The pickup block never shows.** No location offers the variant for pickup: turn on local pickup for the location, and check the product's stock there.
> - **The "Check other stores" button disappears.** There is only one pickup location, or the button has no panel to open inside `store-availability`.
> - **The store locator disappears.** It has no `data-api-key`, no `map` element inside, or the store has no location with local pickup.
> - **A store is missing from the locator.** In Shopify, the location is inactive, does not fulfil online orders, or has local pickup turned off.
> - **The map shows no markers.** The `map-marker` template is missing.
> - **The store locator works on `.webflow.io` but not on your domain.** It needs the Lite or Server plan there.
---
# EU right of withdrawal
> Add the withdrawal button that EU Directive 2023/2673 requires from 19 June 2026. Covers what the rule asks, a withdrawal page on your Webflow site with the metaobject creator and Shopify Flow, and a withdrawal app on a page hosted by Shopify.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/internationalization/eu-withdrawal-compliance
URL: https://www.smootify.io/docs/features/eu-withdrawal
Updated: 2026-10-11
Since **19 June 2026**, a store that sells online to consumers in the European Union must offer an electronic withdrawal function, such as a button or a link, that customers use to withdraw from a purchase within their 14 days. This page says what the rule asks and gives two ways to meet it on a Webflow site: a withdrawal page on your site, built with Smootify’s metaobject creator and Shopify Flow, or the form of a withdrawal app on a page hosted by Shopify.
> [!WARNING]
> This page is guidance, not legal advice. Following it does not guarantee that your store meets EU Directive 2023/2673, and Smootify takes no responsibility for fines, penalties or other legal consequences. Check your setup with a lawyer who knows the rules of the countries you sell to.
## What the rule asks
[EU Directive 2023/2673](https://help.shopify.com/en/manual/compliance/legal/eu-right-of-withdrawal) asks for:
- **A visible withdrawal button.** A clearly labelled function that is easy to find and works without logging in.
- **Two steps.** After the withdrawal button, the customer confirms the withdrawal and gives their name and order details.
- **A confirmation on a durable medium, without delay.** Your store sends the customer an acknowledgement of receipt, for example by email, with the content of their request and the date and time it was received.
**Who must comply.** Any business that sells goods, services or digital content online to consumers in the EU, wherever the business is based.
**Exemptions.** Some products are exempt from the 14-day right of withdrawal. Which of your products are exempt is your decision, with your lawyer.
**Penalties.** A store without the withdrawal function can face:
- legal warnings;
- fines of up to 4% of annual turnover in some EU countries;
- a longer withdrawal period: the 14 days can become 12 months and 14 days.
## Shopify’s own option
Shopify’s new customer accounts have self-serve returns, which can help with the rule: see [Shopify’s setup guide](https://help.shopify.com/en/manual/fulfillment/managing-orders/returns/self-serve-returns/setup). They need the customer to log in, and the rule asks for a function that works without logging in, so check with your lawyer whether they are enough for your store.
## Two ways on a Webflow site
Withdrawal apps for Shopify draw their button inside a Shopify theme, and a Webflow site has no theme. So the withdrawal form goes in one of two places:
- **A. A page on your Webflow site.** A Webflow form saves each request in Shopify as a metaobject entry, and a Shopify Flow workflow checks the order and sends the emails. The page has your design and your domain. It needs the **Server** plan and **Metaobject creator** turned on in the Smootify dashboard.
- **B. A page hosted by Shopify.** A withdrawal app shows its form on a page of your Shopify store, and your site links to it. It works on every Smootify plan, and the app sends the confirmation.
## A. A withdrawal page on your site
> [!NOTE]
> This way needs the **Server** plan, **Metaobject creator** turned on in the Smootify dashboard, and the free **Shopify Flow** app. The form runs on the site's licensed domain. See [Collect data with forms](https://www.smootify.io/docs/features/metaobject-creator).
### 1. Create the definition in Shopify
Under **Settings > Custom data > Metaobjects**, add a definition with the type `withdrawal_request` and these fields:
- `name`: Single line text;
- `email`: Single line text;
- `order_number`: Single line text;
- `products`: Multi-line text, for the products the request is about (optional for the customer).
The definition needs no Storefronts access: the site never shows the requests.
### 2. Build the page
Create a page such as `/withdrawal`, open to everyone. Smootify needs only the form; the two steps are up to your design:
1. A first button, “Withdraw from the contract”, opens the form. A Custom element with the tag `details`, holding a Custom element with the tag `summary` and the form, does it without interactions. A popover works too: see [Popovers](https://www.smootify.io/docs/build/popovers).
2. The form: a Custom element with the tag `metaobject-creator` and the custom attribute `data-type="withdrawal_request"`, with a Webflow Form Block inside. Its Form Button, “Confirm withdrawal”, is the second step.
```html
Withdraw from the contract
We have received your withdrawal request. A confirmation is on its way to your email.
Something went wrong. Please try again.
```
- **Field names are the keys.** Each field’s **Name**, in its element settings, is the key of a field of the definition. Webflow renames fields when you copy a form: check every Name after pasting.
- **Required fields.** Turn on **Required** for the name, the email and the order number, and set the email field’s Type to **Email**, in the element settings.
- **The order number.** Ask for it as it shows on the order confirmation, such as `#1001`, and say so in the field’s label or placeholder.
- **No `customer-field`.** It would hide the form from visitors who are not logged in, and the rule asks for a function that works without logging in.
- **The Success message** says the request has arrived and that a confirmation follows by email. The email itself comes from the workflow, step 4.
### 3. Link the page
Link `/withdrawal` from the places your customers look: see [Where to link the withdrawal form](#where-to-link-the-withdrawal-form).
### 4. Build the workflow in Shopify Flow
The workflow sends the customer the acknowledgement of receipt, then finds the order and marks it. Import it ready-made from [EU withdrawal request](https://www.smootify.io/flows/eu-withdrawal-request); its steps are also in [The withdrawal workflow](#the-withdrawal-workflow) at the end of this page.
## B. An app on a page hosted by Shopify
The withdrawal app shows its form on a page of your Shopify store, and your Webflow site links to it. Several apps on the Shopify App Store offer the form, each with a free plan:
- [Revoq - EU Withdrawal Button](https://apps.shopify.com/eu-withdrawal-form): tested and recommended by Smootify;
- [EU Withdrawal Button & Form](https://apps.shopify.com/eu-widerrufsbutton);
- [Retractly: EU Withdrawal](https://apps.shopify.com/revoka).
The steps below use Revoq, the app the Smootify team tested. They work with Smootify’s [Redirect theme](https://www.smootify.io/docs/start/domains) as the live theme of your store. Every page of that theme sends visitors to your Webflow site, so the withdrawal page gets a layout of its own, which does not redirect. The current version of the theme, 1.1.1, does not include this layout: you create it once.
### 1. Create the layout
In Shopify, go to **Online Store > Themes**, open the menu (**…**) of the Smootify Redirect theme and choose **Edit code**. Under **Layout**, add a new layout named `eu`, so the file is `eu.liquid`, and paste:
```liquid
{{ page_title }}
{%- if settings.favicon != blank -%}
{%- endif -%}
{{ content_for_header }}
{{ content_for_layout }}
```
The layout shows only the content of the page, with no theme styles, and `noindex` keeps the page out of search engines: it is a form, not content.
### 2. Create the page and its template
The template must hold the app’s block, so it is made in the theme editor, not in code:
1. Go to **Online Store > Pages** and create a page with any title, such as “EU Withdrawal”.
2. In **Online Store > Themes**, click **Customize** on the Smootify Redirect theme.
3. From the menu at the top, open **Pages** and create a template named `eu`. Save: Shopify creates the file `page.eu.json`.
4. Back in **Online Store > Pages**, open your page and set its **Theme template** to `eu`.
5. In **Edit code**, under **Templates**, open `page.eu.json` and change the value of `"layout"` to `"eu"`:
```json
{
"layout": "eu",
...rest of the file
}
```
The page now uses the `eu.liquid` layout instead of the theme’s own, which would redirect it.
### 3. Add the app’s form
Install [Revoq](https://apps.shopify.com/eu-withdrawal-form) from the Shopify App Store. In the theme editor, open your page and add the **Revoq withdrawal form** app block. Set up the app as you need, such as its look and languages: Revoq comes with all 24 EU languages.
### 4. Link the page
The page’s address is your Shopify store’s address followed by the page’s, such as `https://your-store.myshopify.com/pages/eu-withdrawal`. Open it to check that the form shows and that it does not redirect, then link it: see the next section.
## Where to link the withdrawal form
Customers must find the form easily. Link the page, `/withdrawal` for way A or the Shopify page for way B:
- **In the footer.** A Text link or a Link block in the footer Component, so it is on every page.
- **On your legal pages**, such as the returns page. If that page shows Shopify’s refund policy with the `policy="refund"` attribute, put the link next to it: see [policy](https://www.smootify.io/docs/reference/attributes/policy).
- **In the order confirmation email.** In Shopify, go to **Settings > Notifications > Customer notifications > Order confirmation**, edit the template and add the link.
## The withdrawal workflow
The ready-made file is on [EU withdrawal request](https://www.smootify.io/flows/eu-withdrawal-request): import it in Shopify Flow, set your email service and your address, and turn it on. These are its steps.
For way A. In Shopify Flow, create a workflow with these steps. The emails need an app that sends emails from Flow, such as Workflow Email, or, on the Grow, Advanced and Plus plans, Flow’s **Send HTTP request** to an email service such as Resend. Flow’s **Send internal email** writes only to your staff, not to the customer.
1. **Trigger: Metaobject entry created**, set to the `withdrawal_request` definition.
2. **Send the customer the acknowledgement of receipt**, to the email of the entry, right away and for every request. It repeats what they sent (name, email, order number, products) and the date and time it was received: use the entry’s creation time (`createdAt`), which Shopify sets on its servers when the request is saved, not the time the workflow runs. Show it in your store’s time zone, with the zone written next to it. This email is the durable medium the rule asks for.
3. **Get order data**, searching for the order with the order number of the entry.
4. **Condition:** an order was found and its email is the email of the entry.
5. **If yes:**
- add the tag `withdrawal-requested` to the order, with **Add order tags**;
- write a note on the order with the date and time of the request and the products it names, with Flow’s order note action or a **Send Admin API request** (`orderUpdate`);
- send your store an email with the request’s details and a link to the order in your Shopify admin.
6. **If no:** send your store an email with the request’s details, so someone finds the order and follows up with the customer. The customer already has their acknowledgement from step 2.
Test it with a real order of your own: send the form with its number and email, then check the tag, the note and both emails.
## Before you publish
> [!STUCK]
> - **The withdrawal form is hidden for visitors who are not logged in.** The `metaobject-creator` has `customer-field`. Remove it: the rule asks for a form that works without logging in.
> - **Nothing is saved, and the Error message names a field.** A field's Name is not the key in Shopify. Webflow renames fields when you copy a form: fix each Name.
> - **The form disappears on the live site.** The Server plan or Metaobject creator is missing, or the page is not on the licensed domain.
> - **Requests arrive, but the customer gets no email.** The workflow sends it with **Send internal email**, which writes only to your staff. Use an email app for Flow, or a **Send HTTP request** to an email service.
> - **The Shopify page sends visitors to the Webflow site.** The page's template still uses the theme's layout. Set `"layout": "eu"` in `page.eu.json`, and the page's Theme template to `eu`.
> - **The app's block is missing in the theme editor.** The page is not on the `eu` template, or the app is not installed yet.
---
# Show metaobjects
> Load metaobjects from Shopify straight into Webflow, without the CMS. Covers a paged list of one type, the metaobjects of a product metafield, a single entry, and how their fields show.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/metaobjects/listing
URL: https://www.smootify.io/docs/features/metaobjects
Updated: 2026-10-10
Metaobjects are structured content you keep in Shopify, such as testimonials, size charts, FAQs or store details. Smootify shows them on the site without the Webflow CMS: you design one entry, and Smootify fills it and repeats it. Your client can then edit that content in Shopify, next to the products. This page covers a list of entries, the entries of a product, and a single entry.
> [!NOTE]
> In Shopify, under **Settings > Custom data > Metaobjects**, the definition must have **Storefronts** access turned on. Without it, the entries never reach the site. With **Active-draft status** also turned on, entries saved as draft stay off the site until you make them active.
**Three ways to load them**, each a section below:
| Way | Element and what it needs | Pages |
|---|---|---|
| A list of one type | `smootify-metaobjects` with `data-type`, the definition's type | yes, `data-limit` entries at a time |
| The metaobjects of a product | `smootify-metaobjects` with `data-type` set to `metafield.` and the metafield's namespace and key, and `data-product-id` | yes, like a list of one type |
| One entry | `smootify-metaobject` on its own, with `data-type` and `data-handle` (or `data-id`) | no; when the entry does not exist, the element is removed |
**Two names you need from Shopify.** The **type** of the definition, such as `testimonial`, shown on the definition’s page. And the **key** of each field, such as `author_name` for a field called “Author name”. Here they are on a testimonial definition, with its entries and what the page shows:
In the Shopify admin and on the page:
1. **Settings > Custom data > Metaobjects**: the definition Testimonial, type `testimonial`, with the fields Author name (key `author_name`, single line text), Quote (key `quote`, multi-line text) and Photo (key `photo`, file). **Storefronts** access on; **Active-draft status** on.
2. **Content > Metaobjects > Testimonial**: the entries. Anna R. and Marco B. are active; Lena K. is a draft, so it stays off the site until it is made active.
3. In Webflow, inside `smootify-metaobject`: `metaobject="author_name"` shows Anna R., `metaobject="quote"` her quote, `metaobject="photo"` her photo. The attribute takes the key, not the field's name.
## 1. A list of one type
Add a Custom element with the tag `smootify-metaobjects` and give it the custom attribute `data-type` with the type. Inside it, add a Custom element with the tag `smootify-metaobject`: the template of one entry.
```html
Name
Quote
No testimonials yet
```
- **The template.** Smootify copies the first `smootify-metaobject` once per entry, and removes the others you designed. The copies go next to it, so put it in a Div block you style as a grid or a list.
- **`data-limit`** sets how many entries load at a time, 10 by default.
- **Pages.** Custom elements with the tag `button` and `data-action="next"` or `data-action="prev"` show the next or the previous page in place of the current one. `data-action="load-more"` adds the next entries under the ones shown. Use next and previous, or load more, not both. Each button is hidden when there is no page to go to.
- **Nothing to show.** A Custom element with the tag `empty-state` shows while the list is empty.
While it works, `smootify-metaobjects` has the classes `is-loaded`, `is-empty`, `has-next` and `has-prev`, which you can style as combo classes. See [metaobject list attributes](https://www.smootify.io/docs/reference/attributes/metaobjects-list).
## 2. The metaobjects of a product or a customer
A product metafield that is a list of metaobjects, such as `custom.features`, can feed the same list. On the product page:
- set `data-type` to `metafield.` followed by the metafield’s namespace and key, such as `metafield.custom.features`;
- add `data-product-id`, bound to the **Shopify ID** field of the Products collection.
```html
Feature
```
It pages like a list of one type. For a few entries without pages, a `metafield` attribute on the product page also repeats them: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).
### The logged-in customer’s entries
The entries a customer created with a [metaobject creator](https://www.smootify.io/docs/features/metaobject-creator) that has `customer-field`, such as their gift lists, are in the customer’s metafield `smootify.`. On an account page, set `data-type` to `metafield.smootify.gift_list` and `data-parent-id` to `customer`:
```html
No gift lists yet
```
- The newest entry comes first. An entry deleted in Shopify is skipped.
- A visitor who is not logged in, or a customer with no entry yet, sees the `empty-state`, and the list has `is-empty`.
- A link with `data-handle-param` inside the card links to that entry’s page, such as `/gift-list?l=`.
## 3. One entry
Use a `smootify-metaobject` on its own, outside any list, with the custom attributes `data-type` and `data-handle`, the handle of the entry in Shopify. You can bind `data-handle` to a CMS field, for example to show a different size chart on each product page. `data-id`, with the entry’s id, works instead of the handle.
```html
Size chart
Notes
```
When the entry does not exist, the element is removed.
**The entry named in the page link.** With `data-handle-param`, the handle comes from the page’s URL: `data-handle-param="l"` on the page `/list?l=giulia-birthday` shows the entry `giulia-birthday`. One page in the Designer then shows every entry a form creates, such as a shared gift list, a saved configuration or a quote, with no CMS page for each. `data-id` and `data-handle` stay the fallback when the link has no parameter; without one, or with a handle that does not exist, the element is removed.
```html
Gift list
Message
This list does not exist.
```
To show a message when the entry is not found, style it hidden and show it once the element is gone, with a custom code rule such as `.list:not(:has(smootify-metaobject)) .not-found { display: block; }`. Anyone with the link can read the entry, so keep emails and addresses out of it. Create the metaobject definition without a display name field: Shopify then gives every entry a random handle, and nobody can guess the link of another one.
## 4. Show the fields
Inside a `smootify-metaobject`, `metaobject="key"` shows that field. Every field type renders, as metafields do: text, rich text, numbers, colours, dates, images and other files, links, lists, and references to products or to other metaobjects. A field without a value hides its element.
- **Images** take the size attributes `max-width`, `max-height`, `crop` and `pad-color`: see [image size](https://www.smootify.io/docs/reference/attributes/image-size).
- **Modifiers** work as on metafields: `data-attr`, `data-digits`, `property`, `separator`, and `data-path` for a JSON field. See [Metafields on the page](https://www.smootify.io/docs/build/metafields).
- **Conditions.** `if-metaobject="key"` shows an element only when the field has a value, and `unless-metaobject="key"` only when it has none. Add `data-check-value` to compare with a value. See [metaobject conditions](https://www.smootify.io/docs/reference/conditions/metaobject).
- **Loading.** Elements with `skeleton` shimmer until the entry is filled.
- **Languages.** Entries come in the page’s language and the visitor’s market: an entry translated in Shopify, for example with Translate & Adapt, shows translated. See [Markets, currencies and languages](https://www.smootify.io/docs/features/markets).
How each type renders is in [metafield types](https://www.smootify.io/docs/reference/metafield-types). The attributes are in [metaobject attributes](https://www.smootify.io/docs/reference/attributes/metaobject).
## 5. Product cards from a field
`metaobject="key"` on a product reference field shows the product’s title, image and link. For full product cards, with price, options, add to cart and wishlist, put a `smootify-product` with `data-id="metaobject."` inside the `smootify-metaobject`, where `` is a product reference field or a list of product references of the entry. Design it as one card: Smootify repeats it once per product, in the field’s order.
```html
List title
Product title
$0.00…
```
- **Where it works.** Inside a `smootify-metaobject`: on its own, opened from the page link with `data-handle-param`, or in each card of a `smootify-metaobjects` list, each entry with its own products. Outside one, Smootify removes the card.
- **An empty field** removes the card. A product the visitor’s market does not sell hides, as in any product list.
- **A class to style.** `html` and `body` get `has-metaobject-` when the field has products.
- **In a slider**, the card fills the slider like the other product sources.
**How many are wanted and how many are taken.** When the entry keeps a quantity for each product in a JSON field, such as the `quantities` of a gift list made with [quantities](https://www.smootify.io/docs/features/metaobject-creator), add to the card `data-requested` with that field’s key. `data-fulfilled` names a second JSON field with what is already taken, such as `gifted`, which the Shopify Flow [Gift list: count the gifts](https://www.smootify.io/flows/gift-list-gifts) fills when an order is paid. Inside the card:
- `metaobject-quantity="requested"`, `metaobject-quantity="fulfilled"` and `metaobject-quantity="left"` show the numbers, for “2 of 6 gifted” or “4 left”;
- the card gets `is-partially-fulfilled` while some are taken, then `is-fulfilled` when none is left, and its add to cart is disabled;
- the card’s quantity cannot go above what is left;
- a sold-out product still shows as sold out.
```html
Product title
0 of 1 gifted
```
## Before you publish
> [!STUCK]
> - **The list disappears.** It has no `data-type` or no `smootify-metaobject` inside, or, for a product list, no `data-product-id`.
> - **The list stays empty.** The definition does not have Storefronts access, or `data-type` is not the definition's type.
> - **A field stays empty.** `metaobject` has the field's name instead of its key: "Author name" is `author_name`.
> - **Some entries are missing.** They are saved as draft in Shopify. Make them active.
> - **A single entry disappears.** Its handle or its type is wrong, so Shopify finds no entry.
---
# Collect data with forms
> A Webflow form that saves each submission as a metaobject entry in Shopify, with file uploads, drafts to moderate, and automations in Shopify Flow.
Status: Ready (Smootify 2.0)
Plan: Server plan
Add-on: Metaobject creator
1.x page: https://docs.smootify.io/docs/metaobjects/creator
URL: https://www.smootify.io/docs/features/metaobject-creator
Updated: 2026-10-10
With `metaobject-creator`, a Webflow Form Block saves each submission as a metaobject entry in Shopify, instead of in Webflow’s form submissions. The data sits in the same place as your products, orders and customers. You can show it again on the site, and Shopify Flow can react to it. Use it for warranty registrations, reseller applications, event sign-ups or reviews.
> [!NOTE]
> The metaobject creator needs the **Server** plan and **Metaobject creator** turned on in the Smootify dashboard. It runs on the site's licensed domain. On your `.webflow.io` address it runs once the site is paid, not during a trial.
## A Webflow form or the metaobject creator
The same Form Block, sent to two different places:
| | Webflow form | Metaobject creator |
|---|---|---|
| Where the data goes | Webflow's form submissions | Shopify, as metaobject entries |
| Shown again on the site | no | yes, with [Show metaobjects](https://www.smootify.io/docs/features/metaobjects) |
| Automations | Webflow's form emails | Shopify Flow |
## 1. Create the definition in Shopify
Under **Settings > Custom data > Metaobjects**, add a definition with the fields you want to collect. Turn on:
- **Active-draft status**, to save entries as drafts with `data-draft` and approve them before they show (step 4). Without it, entries are saved without a status, and `data-draft` has no effect;
- **Storefronts** access, if you want to show the entries on the site. Saving works without it.
Write down the definition’s **type**, such as `warranty_registration`, and the **key** of each field, such as `serial_number` for “Serial number”.
## 2. Build the form
Add a Custom element with the tag `metaobject-creator` and give it the custom attribute `data-type` with the type. Put a Webflow Form Block inside it. Without a form, the element is removed.
```html
Thank you. Your product is registered.
Something went wrong
```
**Field names are the keys.** Each field’s **Name**, in its element settings, is the key of a field of the definition, such as `serial_number`, or the same words with spaces, such as `Serial number`: Smootify turns the Name into the key. Webflow renames fields when you copy a form, so check every Name after pasting. From the definition to the entry, for three of the fields above:
1. **Settings > Custom data > Metaobjects**: the definition Warranty registration, type `warranty_registration`, with the fields Serial number (key `serial_number`, single line text), Accepts reminders (key `accepts_reminders`, true or false) and Receipt (key `receipt`, file). **Active-draft status** on, to save entries as drafts with `data-draft`. **Storefronts** access only to show the entries on the site.
2. In Webflow, inside `metaobject-creator`, each field's **Name** setting is a key: a Text field named `serial_number` sends what the visitor types; a Checkbox named `accepts_reminders` sends `true` when checked, and `false` when unchecked; a `file-input` with `name="receipt"` sends the file, which goes to the store's Shopify Files. Empty fields are not sent, and neither are fields whose Name starts with `_`: use those for fields that only help the visitor, such as "repeat your email".
3. **Content > Metaobjects > Warranty registration**: the new entry, with its values. With `data-draft="true"` on `metaobject-creator`, it is a draft until you make it active.
**What each field type takes.** Smootify’s server turns what the form sends into the format of each field of the definition, so most fields need only the plain Webflow element. What the visitor writes, and what Shopify saves:
| Field type in Shopify | In the form | The visitor writes → Shopify saves |
|---|---|---|
| Single line text, Multi-line text | a Text field or a Text area | `Linen` → Linen |
| Rich text | a Text area: each line is a paragraph | two lines → two paragraphs |
| Integer, Decimal | a Text field | `4,5` → 4.5 |
| Rating | a Text field, a Select field or Radio buttons, with a number on the field's scale | `4` → 4 out of 5 |
| Money | a Text field, in your store's currency | `9,90` → 9.90 |
| Dimension, volume, weight | a Text field for the number, and a Select field with `data-unit-for` set to that field's Name, whose options are the units; or one Text field with the number and its unit | `2,5` and `cm` → 2.5 cm |
| True or false | one Checkbox with that Name | checked → true, not checked → false |
| A list of any type | several Checkboxes with the same Name, each with the custom attribute `value`; a Select field with the custom attribute `multiple`; or a Text field with the values separated by commas | Red and Green ticked → Red, Green |
| Date | a Text field, or a Custom element with the tag `input` and `type="date"` | `2026-10-02` → 2 October 2026 |
| Date and time | a Custom element with the tag `input` and `type="datetime-local"` | a date and a time → that date and time |
| URL | a Text field | `smootify.io` → https://smootify.io |
| Product, variant, collection, page or metaobject reference | a Text field, or a Custom element with the tag `input` and `type="hidden"`, holding the id | `9304502009994` → that product |
| Customer reference | `customer-field` on `metaobject-creator` | the logged-in customer |
| File, list of files | `file-input`, with `data-multiple` for a list | the uploaded files, in Shopify Files |
A value already in Shopify’s own format passes as it is. For a measure, the Select field with `data-unit-for` is not sent as a field of its own, so its Name does not matter. A number without a unit is refused by Shopify, with its reason in the Error message.
**How many of each product.** Next to each product checkbox, in the same card, a Number field with the custom attribute `data-quantity-for` set to the checkboxes’ Name (`data-quantity-for="products"`) says how many of that product the entry asks for, such as 6 glasses on a gift list. Give the Number fields the Name of a **JSON** field of the definition, such as `quantities`: Smootify sends, for each checked product, its quantity, at least 1. The products of a gift list page then show how many are gifted: see [Product cards from a field](https://www.smootify.io/docs/features/metaobjects#5-product-cards-from-a-field).
**The product and the variant on the page.** A form inside a `smootify-product`, such as a back-in-stock alert, can save which variant the visitor was looking at: a hidden field with the custom attribute `variant="id"` takes the current variant’s ID and follows every change of option. Give it the Name of a variant reference field of the definition, such as `variant`.
```html
```
**Required fields inside components.** Turn on **Required** in a field’s settings. On a field inside a Webflow component, where Webflow cannot keep it, give the field the custom attribute `data-required` instead; `False` turns it off. This works in every form of the page, a Smootify form or a plain Webflow form such as a contact form, and a field hidden by a condition does not count.
**The Form Block’s messages.** The Success message shows once the entry is saved, and the form hides. When Shopify refuses the entry, the Error message shows Shopify’s reason in its text, such as a field that does not exist or a required field left empty. The Form Button’s **Waiting text** shows while it saves.
**Fields that are not saved.** Every filled field is sent, and a field whose key is not in the definition makes Shopify refuse the whole entry. For a field the entry does not need, such as a checkbox to accept your terms, start its Name with `_`, such as `_accepted`: Smootify does not send it. Mark it **Required** to stop the form until it is checked.
## 3. Upload files
For a file field, add a Custom element with the tag `file-input` inside the Form, with the custom attribute `name` set to the field’s key. The files go to your store’s Shopify Files, and the entry gets them.
- For a field that holds a list of files, add `data-multiple` with the most files allowed, such as `3`. Without a number, the limit is 5.
- You design the upload area with the same parts and limits as the file uploader, such as a drop zone, a list of files and `max-file-size`: see [File uploads](https://www.smootify.io/docs/features/file-uploads).
- While a file is still uploading, the form cannot be sent.
Shopify Files has its own limits on size and file types. For larger files, upload them with another service and give the definition a **URL** field instead of a file field: the form sends the file’s address in a field with that field’s Name. A file field does not take a file stored outside Shopify.
## 4. Moderate with drafts
With `data-draft="true"` on `metaobject-creator`, each entry is saved as a draft. It stays off the site until you make it active in Shopify. Use it for anything visitors write that others will read, such as reviews. Without it, entries are active as soon as they are sent.
## 5. Link the entry to the customer
To know which customer sent each entry, such as a warranty registration or a reseller application:
1. In the definition, add a field of type **Customer** (a customer reference), with a key such as `customer`.
2. On `metaobject-creator`, add the custom attribute `customer-field` with that key: `customer-field="customer"`.
```html
Thank you. Your product is registered.
Something went wrong.
```
- **No field to add.** You do not add the customer to the form. Smootify’s server finds out who is logged in and writes the customer into that field. A value sent with the form under that key is ignored, so nobody can send an entry in another customer’s name.
- **Only for logged-in customers.** Until the customer logs in, the form is hidden and cannot be filled. Once they are logged in, `metaobject-creator` gets the class `is-customer` and the form shows. Put a login link next to it for the others: see [Customer accounts](https://www.smootify.io/docs/build/account).
- **The session must be valid.** When the login has expired, the form shows “Log in to send this form” in its Error message.
**Only for some customers.** To offer the form only to some customers, such as resellers you approved, put it inside an element with `customer-tagged="reseller"`, or with `if-customer-metafield` on a metafield of the customer. A Shopify Flow workflow can then add or remove the tag. These conditions only show or hide the form on the page: see [customer conditions](https://www.smootify.io/docs/reference/conditions/customer).
**The customer’s entries in the account.** With `customer-field`, Smootify’s server also keeps a list of the customer’s entries: each new entry goes first into the customer’s metafield `smootify.`, such as `smootify.gift_list`. The metafield is created the first time, and keeps the 128 newest entries. An account page lists them with `smootify-metaobjects`: see [The logged-in customer’s entries](https://www.smootify.io/docs/features/metaobjects#the-logged-in-customers-entries).
## 6. Share the new entry
Once the entry is saved, `metaobject-creator` gets the class `is-created` and the custom attribute `data-handle` with the entry’s handle. Inside the Form Block’s Success message:
- a Link block with the custom attribute `data-handle-param` links to the entry. Point the link to the page that shows one entry, such as `/gift-list`, and give `data-handle-param` the name of that page’s parameter, such as `l`: Smootify writes `/gift-list?l=` into it. That page shows the entry with `smootify-metaobject` and the same `data-handle-param`: see [The entry named in the page link](https://www.smootify.io/docs/features/metaobjects#3-one-entry);
- a Custom element with the tag `button` and `data-action="share"` shares the same link: the device’s share sheet on phones, otherwise the link is copied. Add `data-copy` to always copy it, and `data-title` to name the share. For two seconds after a click, the button has the class `is-shared` or `is-copied`: show a “Link copied” text with that combo class.
```html
```
## 7. Let the customer edit or delete the entry
`metaobject-editor` opens an entry the logged-in customer created and lets them change it or delete it, such as their gift list or a warranty registration. It takes the same attributes as the form that created the entry: `data-type`, `customer-field` and, for the entry named in the page link, `data-handle-param`.
```html
Saved.
Something went wrong.
```
- **The form opens filled in** with the entry: texts, checkboxes, radios, selects, quantities and `file-input`. Saving changes the entry.
- **Only its owner sees it.** Smootify’s server checks that the logged-in customer is the one in the `customer-field` of the entry. For the owner the element gets the class `is-owner`; for anyone else it stays hidden.
- **Delete.** A Custom element with the tag `button` and `data-action="delete"` deletes the entry and takes it out of the customer’s entries. `data-confirm` asks first with that text, and `data-redirect` opens that page afterwards. Without a redirect, the element gets the class `is-deleted`.
- It needs the Server plan and Metaobject creator, like the form that creates the entries.
## 8. React with Shopify Flow
The free **Shopify Flow** app can start an automation each time an entry is created: send an email, tag a customer, update a metafield, call another service. Install it in Shopify, then create a workflow that starts when a metaobject entry is created. With `customer-field`, the entry already holds the customer, so the workflow can tag them or update their metafields.
Ready-made workflows are in the [Flow library](https://www.smootify.io/flows), each with the definition to create and a file to import: [review moderation](https://www.smootify.io/flows/review-moderation) and [review requests](https://www.smootify.io/flows/review-request), and alerts for [back in stock](https://www.smootify.io/flows/back-in-stock), a [waitlist](https://www.smootify.io/flows/waitlist) and a [price drop](https://www.smootify.io/flows/price-drop).
> [!TIP]
> These forms often collect personal data. Tell visitors what you collect and why, and decide which of your Shopify staff can see the entries.
## Before you publish
> [!STUCK]
> - **Nothing is saved, and the Error message names a field.** A field's Name is not the key in Shopify. Webflow renames fields when you copy a form: fix each Name.
> - **Shopify refuses a measure.** The number has no unit: add a Select field with `data-unit-for`, or let the visitor write the unit.
> - **The form disappears on the live site.** The Server plan or Metaobject creator is missing, or the page is not on the licensed domain: Smootify then removes the element.
> - **Entries are saved but do not show on the site.** The definition has no Storefronts access, or the entries are drafts.
> - **Files are missing from the entry.** The `name` of `file-input` is not the key of the file field.
> - **The share link goes to the page without the entry.** The link's `data-handle-param` is not the parameter that the entry page's `smootify-metaobject` reads, or the link is outside the Success message.
> - **The form never shows.** It has `customer-field` and the visitor is not logged in, or Customer Accounts is not set up on the site: see [Customer accounts](https://www.smootify.io/docs/build/account).
> - **"This form cannot create entries of this metaobject type".** `data-type` is a definition the form cannot write: Shopify's own (`shopify--…`), an app's (`$app:…`), or one of Smootify's, such as the swatch options or the Magic Box. Create a definition of your own.
---
# Product reviews
> Show product reviews and star ratings from Judge.me, Loox or Klaviyo Reviews, build your own stars from Shopify's rating metafields, or keep your own reviews in Shopify with a form, without a review app.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/apps/judge-me-reviews
URL: https://www.smootify.io/docs/features/reviews
Updated: 2026-10-13
Reviews usually come from a review app installed on your Shopify store. The app collects and stores them, and your Webflow site shows them. This page covers three ways to show them: Smootify’s elements for Judge.me, the widgets of other apps such as Loox and Klaviyo Reviews, and stars you design in Webflow from the rating the app saves in Shopify. A fourth way needs no review app at all: your own reviews, kept in Shopify.
> [!NOTE]
> Install the review app on Shopify first. Its widgets must be allowed on a site that is not a Shopify theme: check the app's plan and its guide for other platforms.
## Choose how to show them
Each way needs something different on the page:
| You want | Use | Works with |
|---|---|---|
| the app's own stars and review list | the Judge.me elements, `judge-me-stars` and `judge-me-reviews` (step 1), with Judge.me's script in the Head code | Judge.me |
| stars designed in Webflow, and the rating on Google | the rating metafields (step 2), with no app code on the page | any app that saves them |
| the widgets of another app | the app's code (step 3); product widgets need the product id without its prefix | Loox, Klaviyo Reviews and others |
| your own reviews, with no review app | a `metaobject-creator` form to collect them and `smootify-metaobjects` to show them (step 4); the form needs the Server plan | Shopify alone: the reviews are metaobject entries in your store |
You can mix them. For example, the app’s review list on the product page and your own stars on the product cards.
## 1. Judge.me
**In Judge.me.** Turn on the widgets for other platforms: in the Judge.me admin, **General settings › Advanced › Platform-independent review widget**. Judge.me then gives you a script. Paste it at the end of **Site settings › Custom code › Head code** in Webflow.
**In Webflow.** Two Custom elements show the widgets of one product. Both go inside a `smootify-product`, on the product page or in a product card:
- `judge-me-stars`: the star rating badge;
- `judge-me-reviews`: the reviews of the product.
```html
```
Leave them empty: Smootify replaces what is inside with the Judge.me widget of the product, then asks Judge.me’s script to draw it. Outside a `smootify-product`, they are removed. Their place on the page and their size are up to your design.
Judge.me’s other widgets, such as all the store’s reviews or a carousel, do not belong to one product. Add them as Judge.me’s guide for other platforms explains: Smootify does not touch them.
## 2. Stars from the rating metafields
Shopify has two standard product metafields for reviews: `reviews.rating`, the average rating, and `reviews.rating_count`, the number of reviews. Review apps can keep them up to date: check that option in your app’s settings. Smootify then shows them like any other metafield, with no app code on the page.
In Shopify, under **Settings › Custom data**, both definitions need **Storefront access**.
Inside a `smootify-product`:
- `metafield="rating"` with `namespace="reviews"` shows the average. On an element without children it writes the value, such as `4.5`. It always sets the CSS variable `--metafield-rating`: the rating as a share of its scale, such as `90%` for 4.5 out of 5.
- `metafield="rating_count"` with `namespace="reviews"` shows the number of reviews.
- `if-metafield="rating_count"` with `namespace="reviews"` shows an element only when the product has at least one review.
```html
★★★★★
0 reviews
```
For the stars, the element with the rating holds a fill with a row of stars inside. Give the fill a width of `var(--metafield-rating)` and hide its overflow: it covers that share of the row. `data-attr="aria-label"` gives the value to screen readers. Ratings in detail: [Metafields on the page](https://www.smootify.io/docs/build/metafields). For three products:
| Product | `--metafield-rating` | What shows |
|---|---|---|
| 4.5 out of 5, 12 reviews | 90% | the fill covers 90% of the row of stars |
| 3.8 out of 5, 5 reviews | 76% | the fill covers 76% of the row |
| no review yet | not set | the element with `if-metafield="rating_count"` hides the whole block |
The same markup works in a product card, inside the card’s `smootify-product`.
**On Google.** On a product page, Smootify adds the rating to the product’s structured data when the page shows both metafields, `rating` and `rating_count`. Google wants a marked-up rating to be visible on the page, so show both. See [SEO and structured data](https://www.smootify.io/docs/start/seo).
## 3. Loox, Klaviyo Reviews and other apps
Smootify has no element for these apps. Add their widgets as each app explains for sites outside Shopify:
- **The app’s script** goes in the site’s custom code, where the app’s guide says. Loox also asks you to add your site’s domain in its settings. Klaviyo Reviews needs Klaviyo’s own script on the site.
- **Widgets for the whole store**, such as all reviews or a review carousel, go in a Code Embed where they should show.
- **Product widgets** need the product’s id as a plain number, such as `1234567890`. The **Shopify ID** field of your Products collection holds it with a prefix, `gid://shopify/Product/1234567890`. On the product template page, insert the field in the Code Embed with **Add Field**, and add the short script of the examples below, which removes the prefix.
### Klaviyo Reviews
Turn on Klaviyo’s App embed or add Klaviyo’s script to the site (see [Shopify apps on a Webflow site](https://www.smootify.io/docs/features/theme-apps)), then add each widget in a Code Embed, inside the wrapper Klaviyo reads, a Div with `data-block-handle`. The product reviews, on the product template page, where `{Shopify ID}` is the field inserted with **Add Field**:
```html
```
The other widgets follow the same pattern, with their own handle and inner element:
- **all reviews** (any page): handle `all-reviews`, inner ``;
- **featured reviews carousel** (any page): handle `featured-reviews-carousel`, inner ``;
- **reviews list** and **reviews summary** (product page): handles `product-reviews-list` and `product-reviews-summary`, inner `` and ``, with the same script;
- **average rating** (product page): handle `average-rating`, inner ``, with the same script.
### Loox
Loox shows its widgets outside Shopify on its **Convert** plan and above. In Loox, open **Settings › General**, and under **External domains** add your site’s domain, with `www`. Then add the widgets in Code Embeds: all reviews is ``. On the product template page, the product’s reviews and its stars:
```html
```
Loox’s other widgets, such as carousels and the pop-up, are in Loox’s own guide for external sites.
For stars on product cards, the rating metafields of step 2 need no app code at all.
## 4. Your own reviews, kept in Shopify
Without a review app, reviews can live in your own store. A form on your site saves each review as a metaobject entry in Shopify, you approve it there, and the site lists the approved ones with their stars. The customers’ words stay in your store, and there is no monthly fee for it.
> [!NOTE]
> The form needs the **Server** plan and the **Metaobject creator** add-on turned on in the Smootify dashboard. See [Collect data with forms](https://www.smootify.io/docs/features/metaobject-creator).
From the definition to the page:
1. **Settings › Custom data › Metaobjects**: the definition Review, type `review`, with the fields rating (Rating, scale 1 to 5), body (Multi-line text), author (Single line text), product (Product reference) and photo (File). Active-draft status on, to approve each review; Storefronts access on, to show them.
2. **Content › Metaobjects › Review**: a review sent from the site, waiting as a draft: rating 4, "The coffee was perfect", by Anna. You make it active to publish it.
3. **On the site**, `smootify-metaobjects` with `data-type="review"` lists the active reviews. `metaobject="rating"` sets `--metafield-rating` (80% for 4 out of 5), and the stars inside fill to that width.
**The definition.** In Shopify, under **Settings › Custom data › Metaobjects**, add a definition of type `review` with a `rating` field of type **Rating** on a scale from 1 to 5, a `body`, an `author`, and if you want a `product` reference and a `photo` file. Turn on **Active-draft status**, to approve each review before it shows, and **Storefronts** access, to show them.
**The form.** A `metaobject-creator` with `data-type="review"` and `data-draft="true"` around a Webflow Form Block. The rating is a Select field or Radio buttons named `rating`, with the values 1 to 5: Smootify’s server writes the number on the field’s scale. On a product page, a Custom element with the tag `input`, `type="hidden"`, the Name `product` and its `value` bound to the `Shopify ID` field links the review to the product.
```html
Thank you. Your review shows once we have read it.
Something went wrong
```
**Approve.** Each review arrives as a draft under **Content › Metaobjects › Review**: make it active to publish it. Without `data-draft`, every review goes live the moment it is sent.
**Show them.** `smootify-metaobjects` with `data-type="review"` lists the active reviews. Inside its `smootify-metaobject` template, `metaobject="rating"` sets the CSS variable `--metafield-rating`, the rating as a share of its scale: draw the stars as in step 2, with a fill as wide as that share.
```html
★★★★★
Review
Name
No reviews yet
```
**Only the reviews of this product.** `smootify-metaobjects` with `data-type="review"` lists every active review of the store: it does not filter them by their `product` field. For a product page, keep each product’s reviews in a product metafield:
- In Shopify, under **Settings › Custom data › Products**, add a metafield definition of type **Metaobject** (list of entries) that points to the Review definition, such as `custom.reviews`, with **Storefronts** access.
- On each product, under **Metafields**, pick the reviews that belong to it.
- On the product page, set `data-type` to `metafield.custom.reviews` and bind `data-product-id` to the `Shopify ID` field. The template inside stays the same.
```html
★★★★★
Review
Name
No reviews yet
```
A product with no reviews in its metafield shows the empty state, or hide the whole section while the list is empty: `smootify-metaobjects` takes `is-empty`. More on metafield lists in [Show metaobjects](https://www.smootify.io/docs/features/metaobjects).
**Automate it with Shopify Flow.** Three workflows from the Flow library finish the job: [Review moderation notice](https://www.smootify.io/flows/review-moderation) emails you when a review is waiting, [Review request after delivery](https://www.smootify.io/flows/review-request) asks the customer for one five days after the order is fulfilled, and [Product rating from your reviews](https://www.smootify.io/flows/review-rating) adds the approved reviews to each product’s average and count every night, in the standard metafields `reviews.rating` and `reviews.rating_count`, which `metafield="rating"` with `namespace="reviews"` turns into stars on cards and product pages. All three are ready to import. The request’s buttons open the product page at `#reviews`: give the section that holds your reviews the ID `reviews`, in its settings.
## Before you publish
> [!STUCK]
> - **The Judge.me widgets are missing.** They are not inside a `smootify-product`, which removes them.
> - **The Judge.me widgets stay empty.** Judge.me's script is not in the Head code, or the widgets for other platforms are not turned on in Judge.me.
> - **The rating stays empty.** The two definitions need Storefront access, and each element needs `namespace="reviews"`: without it, Smootify reads the namespace `custom`.
> - **The stars do not fill.** The fill must be inside the element with `metafield="rating"`, with a width of `var(--metafield-rating)`.
> - **No rating on Google.** The page must show both `rating` and `rating_count`, and the product needs at least one review.
> - **Your own reviews do not show.** They are drafts waiting in **Content › Metaobjects › Review**, or the definition has no Storefronts access.
> - **A review is refused.** Its rating is outside the field's scale, such as 6 on a scale of 1 to 5: the form's Error message shows Shopify's reason.
> - **A Loox or Klaviyo product widget stays empty.** It received the Shopify ID with its `gid://shopify/Product/` prefix.
---
# Back in stock alerts
> Let customers leave their email on a sold-out variant and get an email when it is back, with Klaviyo, or with the metaobject creator and Shopify Flow for alerts kept in your store.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/apps/klaviyo-back-in-stock
URL: https://www.smootify.io/docs/features/back-in-stock
Updated: 2026-10-05
When a variant is sold out, a back-in-stock form lets the customer leave their email. Klaviyo keeps the request and emails the customer when the variant is in stock again. On the site, Smootify sends the email and the chosen variant to Klaviyo. The email itself comes from a Klaviyo flow.
> [!NOTE]
> You need a Klaviyo account connected to your Shopify store with Klaviyo's Shopify integration, so that Klaviyo knows your products and their stock.
## 1. Set up Klaviyo
1. Find your **public API key**, which Klaviyo also calls the site ID or company ID. It is in Klaviyo’s settings, under **API keys**. Smootify needs only this public key: never use a private key on the site.
2. Create a flow from Klaviyo’s **Back in Stock** template, and turn it on. Without a flow, Klaviyo saves the requests but nobody gets an email.
## 2. Build the form
Add a Custom element with the tag `klaviyo-back-in-stock` inside the `smootify-product` of the product page. Give it the custom attribute `data-company-id`, with your public API key as its value.
Inside it, Smootify needs:
- **a button that opens the form**: a Custom element with the tag `button` and the custom attribute `type="button"`. It must be the first element of this kind inside `klaviyo-back-in-stock`;
- **a panel**: a Custom element with the tag `dialog`, or a popover (see below);
- **a Form Block inside the panel**, with a Text field for the email: Webflow’s own Email field works as it is (its Name is `Email`, in any case), and so does a Text field whose Type is Email. Make it Required.
Without `data-company-id`, the button, the panel or the form, the element is removed. Outside a `smootify-product`, too.
```html
```
`condition="out-of-stock"` is optional: it shows the element only while the selected variant cannot be bought. The texts, the layout and the style of the panel are up to your design.
- **What is sent.** The email, and the variant selected when the customer sends the form. Only the `email` field reaches Klaviyo: other fields in the form are ignored.
- **After sending.** The form hides and its Success message shows. If Klaviyo refuses the request, the Error message shows Klaviyo’s reason.
## 3. Choose a dialog or a popover
The panel is a dialog, as above, or a popover in its place:
| Panel | Built with | Opens and closes |
|---|---|---|
| A dialog | a Custom element with the tag `dialog` | the button with `type="button"` opens it. Escape closes it, and so does a click outside the first element inside the dialog: keep everything in one Div block inside it |
| A popover | a Div block with the custom attribute `popover` set to `auto`, in place of the dialog | the same button with `type="button"` opens it. Escape and a click outside close it, and it can also have its own close button |
More about building a popover in [Popovers](https://www.smootify.io/docs/build/popovers).
## 4. Or keep the requests in your Shopify
Klaviyo is one way to send the alerts. To keep the list in your own store, with no other app holding your customers’ emails, use the [Metaobject creator](https://www.smootify.io/docs/features/metaobject-creator) and Shopify Flow: the form saves each request as an alert entry in Shopify, and a Shopify Flow workflow writes to the waiting customers when the moment comes. The same form, with another type, gives you more than back in stock:
| Alert | The form saves | Shopify Flow starts when | Then | Workflow |
|---|---|---|---|---|
| Back in stock | an alert of type `back_in_stock` for the variant, and tags the customer | the variant goes from zero to one or more in stock | it emails the customers tagged for that variant and removes the tag | [Back in stock](https://www.smootify.io/flows/back-in-stock) |
| Price drop | an alert of type `price_drop` for the variant, with its price of that moment | a daily check finds the variant cheaper | it emails the customer and marks the alert as sent | [Price drop alert](https://www.smootify.io/flows/price-drop) |
| Waitlist | an alert of type `waitlist` for a product published with no stock, and tags the customer | the product gets its first stock | it emails the customers tagged for that product and removes the tag | [Waitlist](https://www.smootify.io/flows/waitlist) |
**How back in stock works in Flow.** Two workflows share the job:
1. When an alert entry is created, the first one finds the customer with the alert’s email, creates them in Shopify if they are new, with a **Send Admin API request**, and adds a tag such as `_back-in-stock:`.
2. When the variant is back in stock, the second one gets the customers with that tag, up to 100 per run, sends each one the email, and removes the tag so the email goes only once.
**The email.** It goes out through an app that sends emails from Flow, such as **Workflow Email**, the one the library’s file uses, or any other transactional email app with a Flow action. On the Grow, Advanced and Plus Shopify plans, Flow’s **Send HTTP request** can also call an email service such as Resend. Shopify Messaging’s **Send marketing email** is not a fit here: it reaches only customers subscribed to email marketing, and its email cannot show which product is back.
Both workflows are in the Flow library, ready to import, with the alert definition to create in Shopify: see [Back in stock](https://www.smootify.io/flows/back-in-stock). The same alert definition serves a [waitlist](https://www.smootify.io/flows/waitlist) for a product not out yet and a [price drop](https://www.smootify.io/flows/price-drop) alert. The metaobject creator needs the Server plan. The form collects an email: say on it what you will do with the address.
## Before you publish
Test on a sold-out variant: send the form, and check that the request reaches Klaviyo.
> [!STUCK]
> - **The element disappears.** `data-company-id` is empty, there is no Custom element with the tag `button` and `type="button"`, there is no dialog or popover, there is no form, or the element is outside `smootify-product`. A Webflow Button does not count: it publishes a link.
> - **The form answers "Invalid Email".** The form has no field named `email` and no Text field whose Type is Email.
> - **The dialog closes when the customer clicks the form.** The dialog holds more than one element: wrap them all in one Div block.
> - **The requests reach Klaviyo, but nobody gets an email.** The Back in Stock flow is missing or not turned on.
> - **The button shows on products in stock.** Add `condition="out-of-stock"` to the element.
## Coming from 1.x
The markup is the same. In 2.0, the panel can also be a popover instead of a dialog.
---
# Analytics and pixels
> Let Smootify load the Meta Pixel, Google Analytics 4, Google Ads and the TikTok Pixel with the visitor's consent, with the standard e-commerce events, and feed Shopify's own reports.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/analytics
URL: https://www.smootify.io/docs/features/analytics
Updated: 2026-10-10
Smootify can load your tracking pixels itself: the Meta Pixel, Google Analytics 4, Google Ads and the TikTok Pixel. Each one loads only after the visitor gives the consent it needs. It then sends the standard e-commerce events, with the product ids of Shopify’s sales channels, so that catalog ads recognise the products. You set the ids once in the site’s custom code: there is no pixel code to paste.
> [!NOTE]
> The consent comes from the cookie banner you build with `smootify-consent`: see [Cookie consent](https://www.smootify.io/docs/features/cookie-consent). Where the law asks for consent, a page without the banner never tracks anyone.
## 1. Add your ids
In **Site settings › Custom code › Head code**, before the Smootify loader, set the ids of the services you use and leave out the others:
```html
```
If the site already sets other options, add `analytics` to the same script. See [Options](https://www.smootify.io/docs/developers/options).
Each id has its own form, needs its own consent, and its events carry the product ids of the catalog that Shopify’s sales channel syncs for that service, so that catalog ads recognise the products:
| Option | The id, and the consent it needs | Product ids in its events |
|---|---|---|
| `metaPixelId` | the Meta Pixel: 15 or 16 digits; marketing | `shopify__`, as the Facebook & Instagram catalog |
| `ga4MeasurementId` | Google Analytics 4: `G-` and letters or digits; analytics | `shopify___`, as the Google & YouTube catalog |
| `googleAdsId` | Google Ads: `AW-` and digits; marketing | `shopify___`, as the Google & YouTube catalog |
| `tiktokPixelId` | the TikTok Pixel: 20 capital letters or digits; marketing | the variant id, as the TikTok catalog |
A visitor who allows only analytics gets Google Analytics 4 and none of the advertising pixels.
## 2. Remove the other copies
Smootify must be the only one that loads these pixels. Any other copy sends its page view before the visitor has answered. When Smootify finds a copy on the page it uses it instead of loading a second one, but it cannot take back what that copy already sent.
Remove:
- the Google Analytics and Facebook Pixel ids in Webflow’s site settings, under **Apps & integrations**;
- the base code of Meta, Google or TikTok in the site’s or the pages’ custom code, and in Code Embeds;
- the same pixels in a tag manager.
## What is sent
| Event | When |
|---|---|
| page view | on every page |
| product view | on a product page |
| search | on the search results page, once per search |
| add to cart | after each addition that works, from any form or button |
| checkout start | when the visitor leaves for the checkout |
Each event has the service’s standard name, such as `ViewContent` for Meta or `view_item` for Google, and the product ids of step 1.
- **Product view.** Sent on the product pages, the pages under your products address (`productsBase`). A product shown anywhere else, such as in a quick view, sends none.
- **Search.** Sent by the search results page (`smootify-search-page`), not by the predictive search in the navbar.
- **Amounts.** The variant’s price times the quantity, before discounts.
- **After the checkout starts,** Shopify and its apps track the rest, such as the purchase, with the consent the visitor gave on your site. Set up purchase tracking on the Shopify side.
## Consent and `waitForConsent`
- **Where consent is required**, such as in the EU and the UK, no script of Meta, Google or TikTok downloads until the visitor accepts. The page is also lighter for visitors who have not answered. When they accept, the page view and, on a product page, the product view go out.
- **Where consent is not required**, the scripts start at once.
- **Google** gets Consent Mode v2 from the visitor’s choices, with advertising signals only when the sale of data is allowed too. **Meta** gets Limited Data Use for US visitors who refused the sale of their data.
`waitForConsent: false`, inside `analytics`, downloads every script at once with the consent denied. It exists for one case: Google’s estimate of the conversions of visitors who refuse. It does not give you more data. Meta and TikTok receive nothing until the visitor accepts, and Google only receives anonymous pings without cookies. Keep the default, `true`, unless you use that estimate.
## Shopify’s reports
Smootify also sends page views and additions to the cart to your store’s analytics in Shopify, once the visitor allows analytics. On the Free and Lite plans, visits and returning visitors need an analytics proxy on your Cloudflare account: see [the analytics proxy](https://www.smootify.io/docs/features/cookie-consent#the-analytics-proxy). It reads that choice from the cookie banner. A site without `smootify-consent` and without pixel ids sends nothing to Shopify. A site that uses another consent tool passes the choice on as shown below.
**The numbers are never complete.** Ad blockers, browsers that block trackers, visitors who refuse consent and bots all leave gaps or noise, in Shopify’s reports and in every pixel. Compare trends over time rather than single counts.
## Another consent tool
`smootify-consent` is the simplest way, but a site can keep another consent tool, such as Cookiebot or Finsweet’s cookie consent. Smootify still needs the visitor’s choice: pass it on from the tool’s callback, the code the tool runs when the visitor answers, with [`setConsent`](https://www.smootify.io/docs/reference/api/methods/setconsent). Smootify then saves it in Shopify, loads the pixels it allows and feeds Shopify’s reports, as with its own banner. With Cookiebot, for example, in **Site settings › Custom code › Head code**:
```html
```
Every tool has its own callback and its own category names: check its documentation, and map its categories to `analytics`, `marketing`, `preferences` and `saleOfData`. Do not use both tools: the visitor would answer twice.
## Other services
- **Klaviyo and Mixpanel.** When the site loads their own script, Smootify sends them product views and additions to the cart, after consent. The `enableAutoTrack` option, on by default, controls this: see [Options](https://www.smootify.io/docs/developers/options).
- **Klaviyo** records events only for visitors it can identify. Smootify identifies the logged-in customer to it, so the events of an anonymous visitor who is not logged in do not show in Klaviyo.
- **Anything else**, such as Pinterest or Microsoft Clarity. Smootify does not load it. A developer can start it with the visitor’s consent: see the [JavaScript API](https://www.smootify.io/docs/developers/javascript-api) and the [`consent_changed`](https://www.smootify.io/docs/reference/api/events/consent-changed) event.
## Before you publish
On your `.webflow.io` address, turn on Smootify’s debugger with Alt+Shift+D (Option+Shift+D on a Mac). Its panel points out an id that does not look like its service’s, a copy of a pixel that the page loads by itself, and ids set on a page without `smootify-consent`. See [The debugger](https://www.smootify.io/docs/start/debugger). Then accept the banner and check that the events arrive in each service’s own test view.
> [!STUCK]
> - **No data from Europe, full data from the US.** The page has no `smootify-consent`, so visitors who must consent never can. Put the banner in a Component that every page uses.
> - **Page views arrive before consent.** An old copy of the pixel is still there: a Webflow integration, custom code or a tag manager. Remove it.
> - **One pixel reports nothing.** Its id has the wrong form, such as a Google Ads id without `AW-`. The debugger checks every id.
> - **The ids are ignored.** The script with `SmootifyUserOptions` comes after the loader. Move it before.
> - **No purchase events.** Smootify's events end when the checkout starts. Purchases are tracked by Shopify, at checkout.
> - **`waitForConsent: false` brought no new data.** It only downloads the scripts sooner. Only Google's conversion estimate uses it.
## Coming from 1.x
- In 1.x the pixels were pasted in custom code or set in Webflow’s integrations, and tracked before any consent. Move their ids to `analytics` and remove the old code.
- The automatic events of 1.x (`enableAutoTrack`) no longer go to a service set in `analytics`, and they now wait for consent.
---
# Cookie consent
> Build the cookie banner and the preferences panel in Webflow with smootify-consent, over Shopify's Customer Privacy API, and let visitors reopen it from a preferences button.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/analytics/cookie-consent
URL: https://www.smootify.io/docs/features/cookie-consent
Updated: 2026-10-10
In regions with privacy laws, such as the EU and the UK, visitors must agree before a site tracks them. Every site needs the banner in Webflow, even one that does not sell in Europe: it shows only to the visitors who have to answer, and stays hidden for everyone else. Smootify does not add one: you design it in Webflow, like any other part of the site. The visitor’s choice is saved with Shopify’s Customer Privacy API, so the checkout, Shopify’s analytics and the pixels Smootify loads all follow it.
> [!NOTE]
> Before you start, turn on consent collection in Shopify: in the Shopify admin, open **Settings › Customer privacy**, then **Cookie banner**, activate it and choose the regions where visitors must answer. Shopify decides where consent is needed from these settings, and your Webflow banner shows only in those regions, and only to visitors who have not answered yet. Shopify's own banner does not show on the Webflow site: only the one you build here.
## How it fits together
What Smootify needs:
- one `smootify-consent` element on every page. A visitor can arrive on any page, so put it in a Component that every page uses, such as the footer;
- inside it, the buttons that accept and decline, and a button that opens the preferences;
- a preferences panel, where the visitor chooses category by category;
- a preferences button, so the visitor can change their choice at any time.
Visitors must always be able to choose their preferences, not only accept or decline everything, so the panel and the button are part of every banner.
It works on any domain, your `.webflow.io` address included: Smootify asks Shopify for the visitor’s region on the first visit and keeps the answer on your domain.
What is up to you: the text, the layout and where it sits (a bar at the bottom, a box in a corner).
**One Component for all of it.** The simplest setup puts everything in one `smootify-consent`, in the footer Component: the preferences button, and the banner and the preferences panel in a popover. The button stays in the footer; the popover is fixed where you want the banner, for example at the bottom of the window, not next to the button: it opens by itself at the top of the page too.
```html
…
…
```
Without a preferences button inside it, Smootify shows the element only while the visitor has to answer, or while the preferences panel is open, and hides it the rest of the time. With the button inside, the element stays in the page with only the button wherever consent applies: keep the banner’s text and buttons inside the popover or a `consent-panel`, since everything else in the element stays visible with the button. Smootify adds no other style: position the banner yourself. In the Designer it is always visible.
## 1. Build the banner
Add a Custom element with the tag `smootify-consent`. Put your text and the buttons inside it. Each button is a Webflow Button, or a Custom element with the tag `button`, with the custom attribute `data-action`:
- `data-action="accept"`: accepts every category and closes the banner.
- `data-action="decline"`: declines every category and closes the banner.
- `data-action="preferences"`: opens the preferences panel (step 2).
- `data-action="close"`: hides the banner for this page without an answer. Nothing that needs consent loads, and the banner shows again on the next page.
```html
We use cookies to measure the site and to show relevant ads.
Accept allDeclinePreferences
```
## 2. Add the preferences panel
The preferences panel lets visitors choose category by category. Every banner needs it. Inside `smootify-consent`, make two Div blocks:
- one with `consent-panel="banner"`, holding the first message and its buttons, including `data-action="preferences"`. It hides while the preferences are open;
- one with `consent-panel="preferences"`. It shows only while the preferences are open.
Webflow keeps Checkboxes inside a Form Block, so put a Form Block in the preferences panel. In each Checkbox’s settings, set its Name to one of these:
| Checkbox Name | Checked, the visitor allows | Without its Checkbox, Save… |
|---|---|---|
| `analytics` | measuring the site, such as Google Analytics and Shopify's reports | declines it |
| `marketing` | advertising, such as the Meta, Google Ads and TikTok pixels | declines it |
| `preferences` | remembering their settings on the site | declines it |
| `sale_of_data` | the sale or sharing of their data, under US privacy laws. It only matters in some regions, such as some US states: put its Checkbox inside an element with `consent-condition="sale-of-data"`, which shows only there | leaves it as it was |
Then add a button with `data-action="save"`. The Form Block only holds the Checkboxes: the form is never sent.
```html
Saved
Something went wrong
```
- **Checked means yes.** When the panel opens, each Checkbox shows the visitor’s current choice. Write every label so that checked means “allowed”: “Allow the sale of my data”, not “Do not sell my data”.
- **Save** saves the checked boxes and closes the banner. Add a Checkbox for each of `analytics`, `marketing` and `preferences`: a missing one counts as declined.
## 3. Let visitors change their mind
Visitors must be able to change their choice later, as easily as they made it. Add a Custom element with the tag `button` and the custom attribute `data-is="preferences-button"`, inside `smootify-consent` as above or anywhere else, for example in the footer. A click opens the preferences panel.
```html
```
- **It needs the banner.** Without a `smootify-consent` on the same page, Smootify removes the button.
- **It needs the button tag.** A Webflow Button publishes a link, and does nothing here.
- **It hides while the banner is on screen**, and shows again once the visitor has answered.
- **It shows only where consent applies:** where Shopify asks for consent, where the visitor has already answered, and in regions with the sale-of-data choice, such as California, where it is the “Do not sell or share” link those laws ask for. Elsewhere it is hidden, and so is a `smootify-consent` that holds it, without leaving a gap. It also stays hidden until Shopify has answered. Smootify marks the button and the element with `is-applicable` when it applies.
## Style and placement
The banner goes through these states. Style them with the classes, as combo classes:
| State | Classes | The banner and the preferences button |
|---|---|---|
| The visitor has to answer | `is-required` on `smootify-consent`, `cookie-banner-visible` on the body | the banner shows; the preferences button is hidden |
| The preferences are open | `is-open` on `smootify-consent`, `cookie-banner-visible` on the body | `consent-panel="banner"` hides, `consent-panel="preferences"` shows |
| The visitor has answered | none | the banner is hidden; the preferences button shows and opens the preferences |
| No consent needed in the region | none | the banner never shows; the preferences button shows and opens the preferences, for example to refuse the sale of data |
- **More classes.** `smootify-consent` also gets `is-sale-of-data-region` in regions with the sale-of-data choice, and `is-applicable` where consent applies. Use `cookie-banner-visible` on the body, for example, to move a chat button up while the banner shows. See [state classes](https://www.smootify.io/docs/reference/state-classes).
- **In a popover.** A popover inside `smootify-consent`, made with Webflow’s Popover element or a Div block with the `popover` attribute, opens and closes with the banner. Escape or a click outside closes it and hides the banner for this page, without an answer. See [Popovers](https://www.smootify.io/docs/build/popovers).
- **The market suggestion** waits until the banner is closed before it shows: see [Market suggestion](https://www.smootify.io/docs/features/market-suggestion).
The full list of attributes is in the Reference: [consent attributes](https://www.smootify.io/docs/reference/attributes/consent) and [`smootify-consent`](https://www.smootify.io/docs/reference/elements/smootify-consent).
## What follows the choice
- **The pixels Smootify loads**: the Meta Pixel, Google Analytics 4, Google Ads and the TikTok Pixel, each with the category it needs. See [Analytics and pixels](https://www.smootify.io/docs/features/analytics).
- **Shopify’s analytics**: page views and additions to the cart reach your store’s reports once the visitor allows analytics. Visits and returning visitors need the analytics proxy: see below.
- **Webflow Analyze** follows the visitor’s choice.
- **The checkout** receives the same choice from Shopify.
- **Your own scripts**: a developer reads the choice with the [JavaScript API](https://www.smootify.io/docs/developers/javascript-api) and the [`consent_changed`](https://www.smootify.io/docs/reference/api/events/consent-changed) event.
### The analytics proxy
Shopify no longer reads the `shopify_y` and `shopify_s` cookies that a site sets in the browser. It now gives the visitor and session tokens from its own server, in a response header, and a site on another domain gets them through a small proxy. Without it, Shopify’s reports cannot tell a new visit from a returning visitor. Smootify calls the proxy once per visit, only after the visitor allows analytics, and never on your `.webflow.io` address.
- **Server plan:** Smootify’s own proxy does it. There is nothing to set up.
- **Free and Lite plans:** deploy the free proxy on your own Cloudflare account, with one click.
3. Open [shopify-headless-proxy](https://github.com/smootify/shopify-headless-proxy) and click **Deploy to Cloudflare**. Log in to Cloudflare and confirm. You get an address such as `https://shopify-headless-proxy.your-account.workers.dev`.
4. In Webflow, go to **Site settings › Custom code** and paste this in **Head code**, before the Smootify loader, with your address followed by `/_cks`:
```html
```
The proxy needs no domain or DNS change, and one call per visit stays well within Cloudflare’s free plan. To accept calls only from your site, set its `ALLOWED_ORIGIN` variable to your domain, such as `https://www.example.com`, in the Cloudflare dashboard.
The same proxy can also open the panel of Shopify’s chat, if you add it: see [Shopify Inbox chat](https://www.smootify.io/docs/features/shopify-inbox).
## Before you publish
On your `.webflow.io` address, turn on Smootify’s debugger with Alt+Shift+D (Option+Shift+D on a Mac). It points out analytics ids set on a page without `smootify-consent`, and CSS that still uses the 1.x body class. See [The debugger](https://www.smootify.io/docs/start/debugger).
> [!STUCK]
> - **The banner always shows, even after an answer.** Smootify's stylesheet is missing, and it is the stylesheet that hides the banner. Add `data-css="true"` to the loader: see [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
> - **The banner never shows.** Consent collection is off in Shopify (**Settings › Customer privacy › Cookie banner**), so Shopify asks for consent nowhere. Or the store does not ask for it in the visitor's region, the visitor already answered, or your own styles hide the element. Smootify only hides it: it never shows what your styles hide.
> - **The preferences button disappears.** The page has no `smootify-consent`: put the banner in a Component that every page uses. Or consent does not apply in the visitor's region, and the button is hidden on purpose.
> - **The preferences button does nothing.** It is a Webflow Button, which is a link. Use a Custom element with the tag `button`.
> - **Save declines a category the visitor never saw.** A category without its Checkbox counts as declined. Add a Checkbox for each of `analytics`, `marketing` and `preferences`.
> - **The banner comes back on the next page.** The visitor closed it with `data-action="close"`, or closed its popover from outside. That hides it for one page, without an answer.
## Coming from 1.x
- 2.0 no longer loads Shopify’s cookie banner. Its appearance settings in Shopify, the `#shopify-pc__…` selectors and its CSS variables no longer apply. A site that relied on that banner has none until you add `smootify-consent`.
- The body class `is-shopify-cookie-banner-visible` is now `cookie-banner-visible`: rename it in your styles.
- `button[data-is="preferences-button"]` keeps working, and opens your new banner.
---
# Shopify apps on a Webflow site
> Which Shopify apps work on a Webflow store, and how to add the code of an app written for Shopify themes so that it runs after Smootify.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/apps/blocks
URL: https://www.smootify.io/docs/features/theme-apps
Updated: 2026-10-10
Your store keeps its Shopify apps when the storefront moves to Webflow. Apps that work in the Shopify admin or at checkout need nothing on the site. Apps that show something on the storefront need their code on the site, and many of them were written for Shopify themes. This page says which kinds of apps work and how to add their code.
## Which apps work
It depends on where the app works:
| The app | On a Webflow site | What to do |
|---|---|---|
| works in the Shopify admin or at checkout | works | nothing |
| has widgets for any website | works | paste its code as its guide for other sites says |
| was written for Shopify themes | may work | paste its code with `smootify-load` (step 2), then test |
| calls its own pages through the store (`/apps/…`) | does not work | ask the app for a version for other sites |
Review apps have their own page: [Product reviews](https://www.smootify.io/docs/features/reviews). So does Shopify’s chat: [Shopify Inbox chat](https://www.smootify.io/docs/features/shopify-inbox). EU withdrawal apps draw their button inside a theme, so on a Webflow site their form goes on a page hosted by Shopify: see [EU right of withdrawal](https://www.smootify.io/docs/features/eu-withdrawal).
## Apps that turn on as App embeds
Some apps have no code to copy: you turn them on in your Shopify theme, as an **App embed**, and Shopify adds their code to the theme’s pages. On a Webflow site, the Smootify dashboard takes that code from your store:
1. In Shopify, open **Online Store › Themes › Customize**, then **App embeds**, and turn on the app.
2. In the Smootify dashboard, open your project’s **App embeds**. Click **Read from the store**, which works when the live theme is Smootify’s [Redirect theme](https://www.smootify.io/docs/start/domains), version 1.1 or later; or save a page of your Shopify online store from the browser as an HTML file and drag it in, with any theme.
3. Paste the code the dashboard gives you in Webflow, where it says, as it is.
The [Integrations](https://www.smootify.io/integrations) page tags the apps that work this way. [Shopify Inbox](https://www.smootify.io/docs/features/shopify-inbox) has its own page.
## How theme apps work on Webflow
An app written for Shopify themes reads and changes the cart through the theme’s addresses, such as `/cart.js` and `/cart/add.js`. A Webflow site has no theme, so Smootify answers these calls on the page, with the answers Shopify would give. A product the app adds goes into the same cart as the rest of the site, and the cart on the page updates.
Most of these calls are answered, two are not:
| Theme calls | Answered by Smootify on the page? |
|---|---|
| the cart, such as `/cart.js` and `/cart/add.js` | yes |
| products | yes |
| recommendations | yes |
| search suggestions | yes |
| the change of country or language | yes |
| shipping rate estimates before the checkout | no: on a Webflow site, shipping rates exist only at checkout |
| app proxy pages, on paths such as `/apps/…`, `/a/…`, `/community/…` or `/tools/…` | no: they reach the app's own server through Shopify's online store, which a Webflow site does not have |
The full list, for developers: [Shopify AJAX API](https://www.smootify.io/docs/developers/ajax-api).
## 1. Get the app’s code
Take the code from the app’s guide for headless stores or other sites, or ask the app’s support. Code copied from a Shopify theme can hold Liquid, such as `{{ product.id }}`, which Webflow does not run: ask the app for code without it.
## 2. Paste it with `type="smootify-load"`
Smootify loads after the page’s own scripts. An app script that runs first calls `/cart.js` before Smootify can answer, and fails. To make it wait:
1. Paste the code where the app says: **Site settings › Custom code** for every page, the page settings for one page, or a Code Embed where the widget shows.
2. In every `
```
Smootify runs these scripts once it has loaded. A `smootify-load` script added after the page has loaded, for example by a tag manager, never runs: a developer can use the other ways in [Run your scripts with Smootify](https://www.smootify.io/docs/developers/scripts).
## 3. Test it
Publish to your `.webflow.io` address and try the app: add a product through it, then open the cart. Turn on Smootify’s debugger with Alt+Shift+D (Option+Shift+D on a Mac): its panel names the app proxy pages the app asked for, which the site cannot serve. See [The debugger](https://www.smootify.io/docs/start/debugger).
## Before you publish
> [!STUCK]
> - **The app's widget shows, but its cart buttons do nothing.** Its script runs before Smootify. Set `type="smootify-load"` on it.
> - **The debugger names paths such as `/apps/…`.** The app needs its app proxy, which a Webflow site cannot serve. Ask the app for its version for other sites.
> - **A module script stops working.** It lost its type. Keep it with `data-type="module"` next to `type="smootify-load"`.
> - **A shipping estimate shows an error.** Shipping rates exist only at checkout on a Webflow site.
> - **The app's code never runs.** A tag manager added it after the page loaded. `smootify-load` only works for scripts on the page when it loads.
## Coming from 1.x
Calls to app proxy pages (`/apps/…`) no longer go through Smootify. They already failed in 1.x, and the debugger now names them.
---
# Shopify Inbox chat
> Add Shopify Inbox, Shopify's free chat, to a Webflow site with the code from the Smootify dashboard, two settings in Shopify, and the proxy that opens the chat panel.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/features/shopify-inbox
Updated: 2026-10-07
Shopify Inbox is Shopify’s free chat app. Visitors write to you from the site, and you answer from Shopify Inbox in the Shopify admin or in the app on your phone. The chat button works on any site, but the chat panel opens from an address that only Shopify’s own online stores have. On a Webflow site, a proxy opens that panel. This page adds the chat to your site and sets up the proxy.
> [!NOTE]
> You need the Shopify Inbox app on your store, with its chat turned on in your Shopify theme (**Online Store › Themes › Customize › App embeds**): the code comes from a page of the store that shows it. On the Free and Lite plans you also need the free proxy on your own Cloudflare account (step 4). On the Server plan, Smootify's proxy does it.
## 1. Get the chat’s code
The code comes from your store, through the Smootify dashboard. In your project, open **App embeds**, then:
- **Read from the store.** It works when the live theme of your store is Smootify’s [Redirect theme](https://www.smootify.io/docs/start/domains), version 1.1 or later. Click it: a small window opens your store for a moment and closes by itself, and the dashboard reads the page as Shopify renders it, embeds included. Nothing goes through Smootify’s servers. If your online store has a password, type it in the window.
- **Or upload a saved page.** Open a page of your Shopify online store where the chat shows, save it from the browser as an HTML file, and drag it in. This works with any theme.
The dashboard gives you the chat’s code: its settings, its colours, the chat itself, Smootify’s `inbox.js` script and Shopify’s `chat.js` script. Copy it as it is.
If your store still uses the previous Inbox chat, whose code loads `inbox-chat-loader.js`, App embeds gives its code as before: paste it, there is nothing else to set up. The rest of this page is for the current chat.
## 2. Paste the code in Webflow
Paste the code in **Site settings › Custom code › Footer code** to show the chat on every page, or in a Code Embed on the pages where you want it.
Keep the scripts as the dashboard gives them: `inbox.js` before `chat.js`, without `async` or `defer`, so that it runs before the chat starts.
## 3. Set up Shopify
Shopify opens the chat only on the store’s own domains, and only for visitors it accepts. Both settings are required:
- **Add your Webflow domain to the store.** In the Shopify admin, go to **Settings › Domains** and add the domain of your Webflow site, such as `www.example.com`, without connecting it. The domain keeps pointing to Webflow. If your DNS provider offers to connect it for you, refuse: Cloudflare, for example, offers it on its own.
- **Let visitors chat without an account.** In the Shopify Inbox settings, turn off **Require sign-in to chat with staff**.
## 4. The proxy
- **Server plan:** Smootify’s own proxy opens the chat panel. There is nothing to set up.
- **Free and Lite plans:** use the free proxy on your own Cloudflare account, the same one that sends Shopify’s analytics (see [Cookie consent](https://www.smootify.io/docs/features/cookie-consent)). If you already deployed it, use the same one.
To set it up:
1. Open [shopify-headless-proxy](https://github.com/smootify/shopify-headless-proxy) and click **Deploy to Cloudflare**, if you have not deployed it yet. You get an address such as `https://shopify-headless-proxy.your-account.workers.dev`.
2. In the Cloudflare dashboard, open the proxy and add a variable named `SHOP` with your store’s Shopify address, such as `your-store.myshopify.com`.
3. Add a secret named `INBOX_SEAL_KEY`, with any long random string as its value. Without it the chat works, but a visitor who comes back does not find their conversation.
4. In the code you pasted in step 2, add `data-proxy` to the `inbox.js` script, with your proxy’s address followed by `/inbox`:
```html
```
## Products in the chat
The products the chat shows, featured or in its answers, open the product page on your site, and collections open your collection pages. The link uses the product’s Shopify handle. The chat uses the same page addresses as the rest of Smootify: if your product or collection pages are not at `/products/…` and `/collections/…`, set `productsBase` and `collectionsBase`, as the site already needs. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
## Adding to the cart from the chat
The visitor can add a product to the cart from the chat, change quantities, remove it and apply a discount code there. Everything goes into the site’s cart, which updates on the page without reloading. There is nothing to set up, on Smootify 2.0 and on sites still on 1.x.
## What the chat keeps
Nothing loads and nothing is saved until the visitor opens the chat. When they open it, the browser keeps their conversation’s session, sealed, so that a returning visitor finds the conversation again.
## Before you publish
Try the chat on your published domain: open it and send a message, which shows in Shopify Inbox. On your `.webflow.io` address the panel does not open, because Shopify opens the chat only on the store’s domains.
When something is wrong, the browser’s console (right click › **Inspect** › **Console**) shows a message that starts with `[Smootify] Shopify Inbox:`.
> [!STUCK]
> - **"Read from the store" asks you to update the theme.** The store's live theme is not the Redirect theme, or it is version 1.0. Install the latest one ([Domains and checkout](https://www.smootify.io/docs/start/domains)), or upload a saved page.
> - **The window shows Shopify's password page and the dashboard reads nothing.** It happens on development stores. Type the password, close the window and click **Read from the store** again.
> - **The window does not open.** The browser blocks pop-ups: allow them for the dashboard, or upload a saved page.
> - **The console says `not-a-store-domain`.** The site's domain is not on the store. Add it in **Settings › Domains**, without connecting it.
> - **The console says `server-plan-required`.** Smootify's proxy is part of the Server plan. Deploy your own proxy and set `data-proxy` (step 4).
> - **The console says `no-store`.** The proxy does not know your store. Add the `SHOP` variable to it.
> - **The console says `no-seal-key`.** The proxy has no `INBOX_SEAL_KEY` secret. The chat works, but returning visitors start a new conversation. Add the secret.
> - **The panel opens on your site's 404 page, or stays empty, and the console shows no `[Smootify] Shopify Inbox:` message.** `inbox.js` is not on the page: it is missing, its address is wrong, or something blocks it. Paste the code again as the dashboard gives it.
---
# Run your scripts with Smootify
> How your own code waits for Smootify, with window.Smootify.push, script type smootify-load or the smootify:loaded event, and the order in which they run at load.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/custom-code/js
URL: https://www.smootify.io/docs/developers/scripts
Updated: 2026-10-01
Smootify loads as an asynchronous module. A plain script on the page usually runs before it: `window.Smootify` is not ready yet and the first events have already been missed. This page shows the three ways to make your code wait for Smootify, and the order in which they run.
## Let Claude write it, in Smootify Studio
[Smootify Studio](https://www.smootify.io/desktop-app), the desktop app for Mac and Windows, gives each site its own folder of custom scripts, and writes them with you through Claude:
1. **Write with Claude.** A conversation opens in the app, in the script’s folder, with Smootify’s custom-script skill and the site’s details already loaded. Claude asks what the script should do, then writes it with Smootify’s conventions: `window.Smootify.push`, the events and their payloads, the markup of the page, and the types below.
2. **Try it on the site.** Studio’s dev server runs your latest code on the real site at every reload, with no publishing to see a change.
3. **Build.** One click type-checks the source and turns it into the single file the site loads.
4. **Put it on the site.** Claude tells you how. A small script goes inline, pasted in the site’s custom code. One too big for Webflow’s custom code limits is uploaded to your own Cloudflare R2 storage, and the site loads it from there.
You can open the folder in your own editor or terminal at any time. Claude runs on your own Claude account.
## Where to put your code
- **Every page**: Site settings, Custom code, in the Head code or the Footer code.
- **One page**: the page settings, Custom code.
- **One place in the layout**: a Code Embed element.
`window.Smootify.push` works from all of these places, and from a tag manager.
## 1. `window.Smootify.push` (recommended)
```html
```
This is the same pattern as `window.Webflow.push`. Before Smootify loads, `window.Smootify` is a plain list of functions. When Smootify starts, it runs them with the API as the argument.
- **When it runs.** As soon as Smootify knows the visitor and the market, before it loads products and the cart. The session, the market and the price format are known at that point.
- **Listeners and filters.** A listener added there gets the events from the first `product_loaded` and `cart_updated` on. A [filter](https://www.smootify.io/docs/developers/filters) added there applies from the first request to Shopify.
- **Functions pushed later.** A function pushed after Smootify has loaded, by a tag manager for example, runs right away. By then the first events may have gone out: `{ replay: true }` on `Smootify.on` also calls your handler with the last one.
- **Errors.** A function that throws is logged in the console and does not stop the others.
For data, use the async methods, such as `getCart()` and `getCustomer()`. To run code once everything has loaded, wherever the script sits:
```js
window.Smootify = window.Smootify || [];
window.Smootify.push((Smootify) => {
Smootify.on(
"loaded",
() => {
// the session, the market and the cart are known
},
{ replay: true },
);
});
```
## 2. `
```
The browser does not run a script with this type. Smootify runs it right after `smootify:loaded`, as a normal script. To give it another type, such as a module, add `data-type="module"`. Its other attributes are kept.
What these snippets can call is in [Shopify AJAX API](https://www.smootify.io/docs/developers/ajax-api).
## 3. The `smootify:loaded` event
This is the 1.x way. It works only when the listener is registered before Smootify finishes loading, for example in the Head code:
```html
```
The event fires once per page. Code added later, by a tag manager for example, never sees it. In 2.0, use `push`.
## The order at load
What happens when Smootify loads, and where each way of waiting runs:
1. Smootify reads `window.SmootifyUserOptions`. Set it before the loader script: see [Options](https://www.smootify.io/docs/developers/options).
2. It finds the visitor's market and session. The first `user_auth_change` goes out here, before your functions run.
3. The `push` functions run (way 1). The listeners and filters they add get every step below.
4. The cart loads and `cart_updated` goes out.
5. `smootify:loaded` fires and its listeners run (way 3), then the `smootify-load` scripts run (way 2).
6. Each product on the page is announced once with `product_loaded`.
Because of step 2, a `push` function that needs the session uses `getCustomer()`, or listens to `user_auth_change` with `{ replay: true }`. More in [Events](https://www.smootify.io/docs/developers/events).
## Types
`smootify.d.ts` describes `window.Smootify`, the events with their payloads and the filters. Each release publishes it at `https://cdn.smootify.io/assets/v2//smootify.d.ts`.
- In a TypeScript project, add the file to `include` in `tsconfig.json`.
- In a plain script, in an editor such as VS Code, start the file with `// @ts-check` and `/// `.
With either setup, `window.Smootify` and `document.addEventListener("smootify:…")` get their types. The methods, the events and the filters are the API. The Smootify elements on the page are not: do not script against their internals.
## Where people get stuck
> [!STUCK]
> - **`window.Smootify` is undefined, or has no methods.** The script ran before Smootify loaded. Put the code in a `push` function.
> - **A tag manager script never sees `smootify:loaded`.** The event fires once per page, before the tag manager adds the script. Use `push` with `Smootify.on("loaded", fn, { replay: true })`.
> - **The options have no effect.** `SmootifyUserOptions` is set after the loader script, or a second script replaces the first one. Set it once, before the loader.
> - **A third-party snippet gets a 404 for `/cart.js`.** It ran before Smootify, so Webflow answered. Change its type to `smootify-load`.
> - **A listener misses the first `user_auth_change`.** It goes out before the `push` functions run. Add `{ replay: true }`, or call `getCustomer()`.
## Coming from 1.x
`smootify:loaded` and `
```
More in [Run your scripts with Smootify](https://www.smootify.io/docs/developers/scripts). For designers adding an app’s snippet, see [Shopify apps on a Webflow site](https://www.smootify.io/docs/features/theme-apps).
## App proxy paths
Some Shopify apps call their own server through the store, on paths such as `/apps/…`, `/a/…`, `/community/…` or `/tools/…`. These app proxies need Shopify’s Online Store. Smootify does not answer them: the calls reach the network as they are, and a Webflow site answers 404. On a `.webflow.io` address, the [debugger](https://www.smootify.io/docs/start/debugger) names the calls.
Two exceptions keep working: Shopify Inbox’s `/apps/ba-rev` gets an empty answer, and the consent request to `/api/unstable/graphql.json` goes to the store.
An app that needs its proxy does not work on a Webflow site.
## Turn it off
When another script on the page answers these endpoints, turn Smootify’s emulation off in the [options](https://www.smootify.io/docs/developers/options):
```html
```
Set it before the Smootify loader script.
## Where people get stuck
> [!STUCK]
> - **An app gets a 404 for `/cart.js`.** Its code ran before Smootify. Change its script type to `smootify-load`.
> - **An app that wraps `fetch` never sees additions.** It wrapped `fetch` before Smootify did. Start it with `smootify-load`.
> - **An app calls `/apps/…` and fails.** App proxy paths need Shopify's Online Store and are not answered.
> - **A product list from `products.json` stops after the first page.** Only the first page is answered. Raise `limit`, up to 250.
> - **Shipping rates never load.** Shipping rates exist only at checkout: the endpoints answer `422`.
## Coming from 1.x
1.x answered `cart.js`, `cart/add.js`, `cart/update.js` and `products/{handle}.js`. 2.0 adds every other endpoint in the table, and `Shopify.actions.updateCart`. App proxy paths went through a Smootify server route in 1.x; that route is switched off for good, so these calls already failed there.
---
# Options
> Configure Smootify with window.SmootifyUserOptions, grouped by task, plus the label globals, the page overrides, the cart animations and the attributes Smootify reads on the html element.
Status: Ready (Smootify 2.0)
URL: https://www.smootify.io/docs/developers/options
Updated: 2026-10-10
Smootify reads its settings from `window.SmootifyUserOptions`, an object you set in the site’s custom code. This page groups the options by task and covers the other globals Smootify reads: labels, page overrides and animations. The full list, with every default, is in the Reference under [Options](https://www.smootify.io/docs/reference/options).
## Set the options
The builder writes the script from a few questions, with only the options that differ from the defaults:
_The options builder of this page writes the `SmootifyUserOptions` script from a few questions: open the page in a browser to use it._
By hand, put one script in Site settings, Custom code, Head code, **before** the Smootify loader:
```html
```
- Smootify reads the object once, when it starts. A change made later has no effect.
- Set only the options you change: the others keep their defaults.
- The spread (`...`) keeps options another script set before this one. Without it, a second assignment replaces the first.
The loader itself is covered in [Install Smootify 2.0](https://www.smootify.io/docs/start/install).
## Page addresses
Smootify builds links to products, collections and vendors from the slugs of your CMS collections, and needs the folder of the account pages.
- **`productsBase`** (default `"products"`), **`collectionsBase`** (`"collections"`), **`vendorsBase`** (`"vendors"`): the slugs of the Products, Collections and Vendors collections. A site whose collections are called `product`, `collection` and `vendor`, the 1.x defaults, sets them. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).
- **`accountBase`** (`"account"`): the folder of the account pages. A visitor who is not logged in is sent away from them.
- **`orderPageUrl`** (`"/account/order"`) and **`subscriptionPageUrl`** (`"/account/subscription"`): the pages that show one order and one subscription.
## Customer accounts
- **`newCustomerAccountsPublicKey`**: the client id of Shopify’s Customer Account API. Without it, login, the account elements and store credit do not work. See [Customer accounts](https://www.smootify.io/docs/build/account).
- **`newCustomerLoginRedirect`**: where Shopify sends the customer back after login. The default is the site’s address.
- **`accountPageRedirectTo`**: where a visitor who is not logged in goes from an account page: an address (default `/`), or `"passwordless-login"` to start the login.
## Markets and prices
- **`selectMarketBasedOnBrowserLanguage`** (`false`): for a visitor with no saved country, the browser’s languages choose the market.
- **`useBrowserFormatter`** (`false`): prices formatted by the browser for the market (`Intl.NumberFormat`), instead of the store’s money format.
## Speed and cache
- **`preload`** (`true`): the browser prepares product, collection and vendor pages ahead, and product data is loaded early. Always off in the Webflow preview.
- **`cacheDuration`** (`0`): minutes a product stays in the browser’s cache. While `preload` is on, `0` becomes 10.
- **`prefetch`** (`true`): once the page is idle, the products of the links near the screen, hovered or touched are fetched into the cache, so the next product page renders from it. It needs the cache: it is off when `preload` is `false` and `cacheDuration` is `0`.
- **`gsap`** (`true`): Smootify loads GSAP when the page is idle, for the cart and panel animations, unless the page already loads it. `false` never loads it: the animations then use the browser’s own animation API.
More in [Performance](https://www.smootify.io/docs/start/performance).
## Webflow interactions
When Smootify changes the page, it restarts Webflow’s interactions so that they apply to the new elements.
- **`avoidWebflowIXRestart`** (`false`): the restart skips interactions (IX2 and IX3).
- **`webflowRestartAlways`** (`false`): restarts Webflow after every product load, as 1.x did. By default 2.0 restarts only when a render changed the page.
## Integrations
- **`ajaxApi`** (`true`): Smootify answers the Shopify AJAX API (`/cart.js`, `/cart/add.js`…) on the page. See [Shopify AJAX API](https://www.smootify.io/docs/developers/ajax-api).
- **`structuredData`** (`true`): the product JSON-LD on product pages. See [SEO and structured data](https://www.smootify.io/docs/start/seo).
- **`analytics`**: an object with the ids of the tracking pixels Smootify loads with the visitor’s consent: `metaPixelId`, `ga4MeasurementId`, `googleAdsId`, `tiktokPixelId`, and `waitForConsent` (`true`). See [Analytics and pixels](https://www.smootify.io/docs/features/analytics).
- **`enableAutoTrack`** (`true`): product view, add to cart and checkout events for the GA4, Meta, Klaviyo and Mixpanel scripts the page loads itself, after consent.
## Labels
These globals replace texts Smootify writes. Set them like the options.
- **`window.SmootifyLabels`**: `{ calculatedAtCheckout: "…" }`, the text of the taxes and duties in the cart when a line has a custom price. Read once, at start.
- **`window.SmootifyStatusLabels`**: the texts of order and subscription statuses, keyed by Shopify’s value (`PAID`, `FULFILLED`…). A status without a text shows Shopify’s value.
- **`window.SmootifySortLabels`**: the texts of the sort options, keyed by `relevance`, `price_asc`, `price_desc`, `best_selling`, `created_asc`, `created_dsc`, `title_asc` and `title_dsc`. Which list shows each one: [Sort labels](https://www.smootify.io/docs/build/filters#sort-labels).
- **`window.SmootifyStoreCreditEventsLabel`**: the texts of store credit events, keyed by `ADJUSTMENT`, `ORDER_CANCELLATION`, `ORDER_PAYMENT`, `ORDER_REFUND`, `PAYMENT_FAILURE`, `PAYMENT_RETURNED`, `RECURRING_PAYMENT` (“Subscription Payment”) and `TAX_FINALIZATION`.
```html
```
The last three are read each time they are used, so they can also be set later.
## Page overrides
Set on one page, in its page settings, these change an address option for that page only:
- **`window.smootifyProductsBase`**, **`window.smootifyCollectionsBase`**, **`window.smootifyVendorsBase`**: override the three slugs above;
- **`window.smootifyAccountPage`**: overrides `accountBase`;
- **`window._languageBase`**: the address prefix of the page’s language, such as `"/fr"`, or `""` for the root, when Smootify cannot find it on its own.
## Cart and panel animations
The mini cart and `modal-backdrop` panels open and close with an animation you can change:
- **`window.cartInAnimation`** and **`window.cartOutAnimation`**, with **`window.cartInAnimationSettings`** and **`window.cartOutAnimationSettings`**;
- **`window.detailInAnimation`** and **`window.detailOutAnimation`**, with their `…Settings`, for `modal-backdrop`.
An animation is `{ prop: [from, to] }` or `{ from: {}, to: {} }`; the settings hold `duration` (seconds) and `easing` (a name or a cubic-bezier list).
```html
```
A cart inside a popover is not animated: it keeps the transitions it was designed with. See [Popovers](https://www.smootify.io/docs/build/popovers).
## The html element
Smootify also reads attributes on the page’s `html` element. Webflow and Weglot write them: there is nothing to set, but keep them when you export or rebuild the page.
- `data-wf-site`: the Webflow project, which the licence is checked against;
- `data-wf-locale` and `lang`: the page’s language, and the first market;
- `data-wf-item-slug`: the CMS item of the page, which tells Smootify the product page’s product;
- `data-wg-translated`: set by Weglot, which Smootify waits for before its first request.
The attributes Smootify writes on `html`, such as `logged-in`, are in [CSS](https://www.smootify.io/docs/developers/css).
## Where people get stuck
> [!STUCK]
> - **An option has no effect.** The script sets it after the loader, or a second script replaces the object. Set it once, before the loader, with the spread.
> - **Product links lead to a 404.** The CMS collections keep the 1.x slugs (`product`, `collection`, `vendor`). Set `productsBase`, `collectionsBase` and `vendorsBase`.
> - **Login does nothing and the account elements stay empty.** `newCustomerAccountsPublicKey` is missing.
> - **Products never come from the cache.** `preload` is `false` and `cacheDuration` is `0`, which also turns `prefetch` off.
> - **Interactions play again after a product loads.** `webflowRestartAlways` is `true`. Remove it to restart only when the page changed.
## Coming from 1.x
- **New defaults**: `productsBase`, `collectionsBase` and `vendorsBase` are plural in 2.0 (`products`, `collections`, `vendors`).
- **New options**: `ajaxApi`, `gsap`, `prefetch`, `webflowRestartAlways`, `structuredData` and `analytics`.
- **Removed**: `invalidProductRedirectUrl` is accepted and never read: a product Shopify does not return leaves its block empty, with no redirect.
- **Deprecated**: `showOnlyActiveImages` and `activeImagesMode` still work. Use `only-variant-images` and `variant-images="alt"` on `[product="gallery"]`: see [Product gallery and media](https://www.smootify.io/docs/build/product-gallery).
---
# "CSS: states, skeletons and variables"
> What Smootify's stylesheet does, the state classes and page flags you can style, the skeleton placeholders and their variables, popover backdrops, upload classes and the CSS variables Smootify writes.
Status: Ready (Smootify 2.0)
1.x page: https://docs.smootify.io/docs/custom-code/css
URL: https://www.smootify.io/docs/developers/css
Updated: 2026-10-10
Smootify’s stylesheet is small and leaves your design alone. Most of what you style comes from Smootify in two forms: classes it adds to elements as their state changes, and CSS variables it writes or reads. This page lists what you can use, for combo classes in the Designer or for your own CSS.
## The stylesheet
The loader adds the stylesheet when its script tag has `data-css="true"`: see [Install Smootify 2.0](https://www.smootify.io/docs/start/install). It holds:
- **the `condition` rules**: every `[condition="…"]`, `[customer-condition="…"]` or `[cart-condition="…"]` element is hidden with `display: none !important` while its state does not match. Without the stylesheet, these elements always show;
- **the skeleton placeholders** for elements with a `skeleton` attribute (below);
- **a few defaults**: closed popovers are hidden, popovers and the cart’s dialog lose the browser’s padding and border (your classes still set theirs), and the cart’s dialog, the market suggestion and the `store-availability` dialog get a backdrop.
Your own CSS goes in a `