Getting started

Folder structure

What lives where, and how src maps to dist.

You edit src/. Vite compiles it to dist/, which is the actual Shopify theme and the only thing ever uploaded.

They are not the same shape. Four rules decide what becomes what:

SourceOutput
  • src/liquid/**
dist/**

Same paths, copied through

  • src/components/**
  • src/framework/**
  • src/javascript/**
  • src/styles/**
dist/assets/main.js dist/assets/main.css

Compiled into one bundle

  • src/fonts/**
  • src/media/**
dist/assets/

Flattened

  • src/api/**
deploy/netlify/

Never uploaded to Shopify

Everything below follows from those four lanes.

The folders

Top level is open; the rest is one click away. Each folder carries where its contents end up.

src/liquidtheme
layout
templates
sections
js
non-js
groups
snippets
blocks
config
locales
src/componentsbundle
atoms
molecules
organisms
islands
src/frameworkbundle
src/javascriptbundle
islands.jsx
theme-editor.js
cart.js · overlay.js
use-api.js · api-cache.js
*.generated.js
src/stylesbundle
src/fonts · src/mediaassets
src/apinot uploaded
operations
services
_lib
vitetooling
scriptstooling
shopify-apptooling
deploytooling

Notes are hidden on narrow screens. Every folder is listed; only the noteworthy files are.

The two rules worth remembering

Section filenames are global. Shopify has no concept of a section subfolder, so sections/js/hero/hero.liquid and sections/non-js/hero.liquid both become sections/hero.liquid. The build warns rather than silently overwriting one with the other.

The same applies to fonts/ and media/ — Shopify's assets/ is flat.

One bundle, on purpose. Everything under components/, framework/, javascript/ and styles/ compiles into a single assets/main.js. That is what makes cross-island state work without a library: separate React roots still share one module instance, so a module-level variable is already shared between them.

See State across islands.

Where generated files come from

Three files are written by the build and should never be edited by hand. All three are gitignored.

FileWritten byHolds
src/javascript/components.generated.jsthe Vite pluginEvery section and island component
src/javascript/api.generated.jsthe Vite pluginThe api registry of operations and services
src/api/_lib/*.generated.jsnpm run api:buildCompiled operations and the service index

The first two are regenerated on every theme build, so they cannot go stale. The third is regenerated by api:build, which is also what writes deploy/.

Adding a section

Two files in one folder, named the same:

src/liquid/sections/js/my-thing/
├── my-thing.liquid ← markup, JSON payload, schema
└── my-thing.jsxdefault export, receives the JSON as props

No registry to update — the build generates it. See Adding a section.