Skip to content

Add Frame to Vite.

Frame expects a Shopify theme at the Vite root and source files in src. JavaScript works in place of TypeScript.

Requires Node.js 22.12 or newer and Vite 7 or 8.

Install Frame
pnpm add -D vite-plugin-shopify-frame vite
vite.config.ts
import {defineConfig} from 'vite';
import frame from 'vite-plugin-shopify-frame';

export default defineConfig({
  plugins: [frame()],
});

Add these scripts to your package.json:

package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}
layout/theme.liquid
{% render 'frame-assets' %}
Generated output ignores
.frame/
assets/frame-*
snippets/frame-assets.liquid

Zero configuration entry

Frame creates a bundle named theme from src/main.ts or src/main.js, plus src/style.css.

Two tools, one workflow.

Run Vite and Shopify CLI independently. Vite serves frontend assets while Shopify CLI synchronizes the theme.

Terminal one
npm run dev
Terminal two
shopify theme dev \
  --notify .frame/shopify-ready

The notification lets Frame request a full page reload after Shopify CLI finishes an update. It is optional. Vite HMR and Shopify CLI live reload continue to work without it.

Build, then deploy normally.

Frame publishes generated assets into the theme and replaces the development loader with Shopify CDN asset tags.

Build and push
npm run build
shopify theme push

Run the build before shopify theme push or shopify theme package. Shopify CLI remains responsible for deployment.

Load code where it belongs.

Start with the default bundle. Add named bundles when a substantial feature should only load on selected Liquid surfaces.

vite.config.ts
frame({
  bundles: {
    theme: {script: 'main.ts', style: 'style.css'},
    product: {script: 'product.ts', style: 'product.css'},
  },
});
layout/theme.liquid
{% render 'frame-assets', entry: 'theme' %}

{% if request.page_type == 'product' %}
  {% render 'frame-assets', entry: 'product' %}
{% endif %}
  • Use dynamic imports first

    They keep one entry architecture while Vite loads optional features on demand.

  • Use bundles for clear boundaries

    Product configurators, account areas, and store locators are good candidates.

Small API, explicit control.

Every Frame option is optional.

vite.config.ts
frame({
  // Theme directory relative to the Vite root.
  theme: '.',

  // Source directory relative to the Vite root.
  source: 'src',

  // Namespace for generated assets and Liquid.
  namespace: 'frame',

  // Set to false to disable coordinated reloads.
  refresh: {
    signal: '.frame/shopify-ready',
    delay: 100,
  },
});
theme
string

Shopify theme directory. Defaults to the Vite root.

source
string

Source directory. Defaults to src.

bundles
Record<string, FrameBundle>

Named script and stylesheet entry definitions. Each bundle needs a script, a style, or both.

bundles[name].script
string

Optional script entry, relative to source. When provided, script entries must be distinct across named bundles.

bundles[name].style
string

Optional stylesheet entry, relative to source.

namespace
string

Generated asset and snippet namespace. Defaults to frame.

refresh
boolean | FrameRefreshOptions

Notification file and debounce settings, or false. Enabled by default.

refresh.signal
string

Shopify CLI --notify file relative to the Vite root. Defaults to .frame/shopify-ready.

refresh.delay
number

Debounce delay in milliseconds. Defaults to 100; must be an integer from 0 to 10000.

Bring the Vite ecosystem.

Frame does not wrap frontend frameworks. Add their official Vite plugins as usual.

  • Tailwind CSS

    Official Vite plugin

  • Alpine.js

    Standard entry import

  • React

    Fast Refresh ready

  • Vue

    Single file components

  • Sass

    Native Vite support

  • PostCSS

    Native Vite config

React Fast Refresh requires @vitejs/plugin-react/preamble because Frame does not use an HTML entry. Framework code mounted inside sections should handle Shopify Theme Editor section load and unload events.

Built for a shared assets directory.

Shopify mixes generated assets and hand authored files in one directory. Frame cleans up without treating that directory like a disposable build folder.

  • Ownership ledger

    Only files recorded as Frame output can be removed as stale.

  • Isolated staging

    Every build finishes away from the theme before publication.

  • Protected writes

    Unknown theme files are never silently overwritten.

  • Recovery journal

    Interrupted publication is restored before the next build continues.

See it in a real theme.

Every example is independently runnable, checked in CI, and tested against a Shopify development store.

  • Vanilla

    TypeScript, CSS, dynamic imports, fonts, and images

  • Alpine and Tailwind

    Alpine.js with Tailwind CSS through Vite

  • React

    React components mounted inside Liquid sections

  • Vue

    Vue single file components inside Liquid sections