Skip to main content
Most theme bugs come from something outside the file you’re editing: a script Fluid injects, a value Fluid resolves before Liquid sees it, or a template Fluid picks for the visitor. This page covers those pieces and the mistakes they cause.

Leave the FairShare SDK script alone

The FairShare SDK powers the cart, rep attribution, and the data-fluid-* attributes on your markup. It isn’t a theme file. Fluid loads it from a storefront Global Embed managed in the admin, as a script with the ID fluid-cdn-script. The floating cart and lead-capture widgets come from Global Embeds too. Fluid adds every active storefront Global Embed to each storefront page, in the head or body according to the embed’s placement. So:
  • Don’t add the SDK script to your theme, and don’t remove it.
  • If the cart or attribution stops working across the whole storefront, check that the SDK’s Global Embed still exists and is active before debugging the theme. A draft embed doesn’t render.
  • Style and toggle the floating widgets from their Global Embed settings in the admin, not from theme markup.

Know what puts markup in the <head>

{{ content_for_header }} outputs more than your theme’s own tags. In order, it adds:
  1. Every active storefront Global Embed placed in the head, including the FairShare SDK script.
  2. Structured data, the canonical and hreflang links, and the title and Open Graph meta tags Fluid builds for the page.
  3. The CSRF meta tag, Fluid’s own scripts, the --company-color CSS variable, and your theme’s stylesheets.
  4. The head markup stored with the page’s template, when it has any.
When the rendered <head> has a tag you didn’t write, such as a second <title> or a canonical link to another site, check these sources in order:
  • Your theme files. Search the layout, the page’s template, and its sections.
  • Global Embeds. List them with GET /api/global_embeds, or open Settings > Global Embeds in the admin, rather than assuming one is responsible.
  • The template’s stored head markup. Some older templates carry head markup that is saved with the template rather than in a theme file. fluid theme pull doesn’t download it, so searching your theme won’t find it.

Don’t paste a whole HTML document into a template

Your layout already outputs <!DOCTYPE html>, <html>, <head>, and <body>, and each template renders inside {{ content_for_layout }}. A template that contains a complete page, often the result of copying another site’s source, nests a second document inside the first. Its <title>, canonical link, and scripts land in the storefront, where they duplicate or override Fluid’s and can send search engines to the other site. When you rebuild a page from another site, move its content into sections and its assets into the DAM. Drop its <!DOCTYPE>, <html>, <head>, and <body> tags, and anything that was inside <head>.

Do not call createCart() to read data

createCart() always creates a new, empty cart and replaces the shopper’s stored cart token. The shopper loses the cart they had. Don’t call it to read a value such as the country. Read the country with getCountryCode(), the language with getLanguage(), and the current cart with getLocalCart(). See Carts that fill in the background. fluid theme lint warns about each call, with the rule cart/create-cart-for-data.

Custom add buttons: open the drawer at the click

Declarative buttons such as data-fluid-add-to-cart open the cart drawer for you. A button that calls addCartItems() itself does not. Call FairShareSDK.cart.control.open() at the click, before you await the add. Don’t open the drawer after the add resolves: on a new cart the add waits for the fill, which takes up to about 2 seconds. A theme with its own cart sidebar has more to handle. See Build a custom cart drawer. fluid theme lint warns, with the rule cart/custom-add-without-drawer-open, when a file calls addCartItems(), addEnrollmentPack() or setEnrollment() but never opens the drawer. It checks each file on its own, so open the drawer in the same file as the add.

Checkout isn’t part of your theme

Your theme renders the storefront and cart only. Payment, shipping, and tax happen in Fluid Checkout, a separate app at checkout.fluid.app. Nothing in your Liquid, CSS, or JavaScript can change the checkout page.

Render standalone blocks with content_for

A block in blocks/<name>/index.liquid owns its markup. Render a section’s standalone blocks with {% content_for 'blocks' %}, and a single fixed block with {% content_for 'block', type: 'heading', id: 'intro' %}. See Blocks and components. {% render block %} renders only app blocks from Droplet theme extensions. For a theme block it outputs nothing. Don’t loop over section.blocks to render blocks that content_for 'blocks' already renders: they appear twice.

Output block.fluid_attributes on every block

The Page Editor selects a block by the attributes that {{ block.fluid_attributes }} emits. Put it on each block’s outer element, whether the block is inline in a section or standalone. Sections don’t need an equivalent: Fluid wraps each section with its own attributes.

Detect the editor with request.design_mode

To render something differently in the Page Editor, branch on request.design_mode: it’s true in the editor and false on the live site. Never infer the editor from block.fluid_attributes being blank or from editor attributes in the page. Those can change on the live site and expose editor-only behavior to shoppers. See Change behavior in the editor.

Know which settings Fluid resolves

Fluid turns the value of these setting types into a Liquid object before your template reads it: product, variant, collection, category, enrollment_pack, media, link_list, and product_list. Every other type reaches Liquid exactly as saved. A post or collection_list setting, for example, gives you the stored identifier, not a record. See Resource selectors. Composite layout settings such as padding, border, and corner_radius also return structured values. Inspect the value before you interpolate it into CSS.

Give CSS values a fallback

An empty setting interpolated into CSS produces invalid CSS, and the browser drops the whole declaration without an error. For example, var(--color-{{ section.settings.accent }}) becomes var(--color-) when the setting is blank. Give the value a default:

Expect presets to fill empty sections

When a section instance has no blocks, Fluid fills in the blocks from the section’s first preset each time the page renders. A section you emptied on purpose shows its preset blocks again. To render nothing, add an empty state in the section’s Liquid rather than relying on an empty block list.

Keep translated settings editable

The storefront resolves a t tag anywhere in a setting, so a mistake here only shows up in the Page Editor. Write every translation key as group.key, with two parts, and make a translated setting’s whole value one tag with no filter arguments or whitespace control, such as "heading": "{{ 'page.about_hero_heading' | t }}". Give the key a string value, not a plural object, in locales/en.json, which is where the editor reads it. A deeper key, a key missing from en.json, or a plural value leaves the setting’s field empty when you select its section or block. A tag inside other text or HTML shows the raw tag. See Translate section and block settings.

Read the language from localization

Use localization.language and localization.country for the visitor’s language and country. See Localization. The request object carries only path, host, page_type, query_parameters, and full_url, so a value such as request.locale renders as an empty string:
Change the visitor’s country or language with the storefront SDK, as shown in Navbar locale selector. Don’t write Fluid’s locale cookies yourself.

Country-specific navbars, footers, and layouts

You can assign navbar, footer, and layout templates to countries. For each visitor, Fluid serves the newest published template of that type assigned to their country. When none matches, it serves a template that isn’t assigned to any country, preferring the default. Page templates aren’t chosen by country. To give one market a different page, use a separate template and link to it, rather than branching on the country inside one template.

A missing template shows your home page

If a URL such as /home/posts/<slug> shows your home page, your theme is missing the template for that page type. In Liquid, request.page_type reads home_page and request.path reads /home, even though the address bar shows the post’s URL. Only request.full_url still holds the post’s URL. fluid theme lint names each missing type in a warning. Add the missing template, such as post/default/index.liquid for blog posts. Don’t redirect from the layout. See Add a template for every page type your store uses. After you add it, a page that was already cached can keep showing the old version for a while.

A clean lint isn’t a working page

fluid theme lint validates schemas and section references, the same checks fluid theme push runs. It also warns when your theme has no template for a page type the storefront routes to, and about cart code that misuses the SDK. It doesn’t render your templates, so a theme that lints clean can still show a blank section, an empty page, or your home page in place of the page you built. The storefront doesn’t show Liquid errors. A template with a Liquid syntax error renders nothing, and a tag or filter that fails renders nothing where it sits. Before you push, or after a push to your live theme:
  1. Open the exact route. Load the URL your change affects, such as /home/products/hydrating-serum, not just the home page. A page that shows your home page means the template for its page type is missing. See A missing template shows your home page.
  2. Watch the dev server’s output. When fluid theme dev syncs a file, it warns about unbalanced Liquid delimiters and schema problems. The storefront accepts those files and can keep serving the previous content for that section without an error.
  3. Find the Liquid error. Open the template in the Page Editor. Its preview shows Liquid errors in the page, where the storefront drops them.
  4. Check the browser console. Look for script errors and failed requests, such as an asset URL that returns not found.
See CLI.