Command-line interface for local development of Store Pages themes. Preview templates with live reload, bundle assets with esbuild, and export production-ready ZIP files.
Requires Node.js >= 22.15.0
- Live dev server with hot reload — changes to Liquid templates and assets are reflected instantly
- Multi-locale preview with URL-based switching (e.g.
/es/store) - Asset bundling via esbuild — JS, TS, JSX, TSX, and CSS with inline sourcemaps in dev
- Custom esbuild config support for advanced bundling setups
- Translation filter (
t) backed by locale JSON files - ZIP export with JS & CSS minification for production deployment
- Configurable dev server port with
--port - Rich error pages in development showing file, line, and column information
Since this package is not published to npm, install it directly from GitHub in your package.json (use semver format for the version):
"dependencies": {
"storepages-cli": "https://github.com/localistico/storepages-cli.git#semver:2.1.0"
}Then run:
npm install
Add the following scripts to your package.json:
{
"scripts": {
"dev": "storepages dev",
"build": "storepages build"
}
}Starts a local development server with hot reloading. Compiles source files on startup and watches for changes.
npm run dev
| Option | Default | Description |
|---|---|---|
--theme-path <path> |
./theme |
Path to the theme directory |
--source-path <path> |
./src |
Path to the source directory |
--temp-path <path> |
./.temp |
Intermediate build directory for compiled assets |
--data-path <path> |
./data |
Path to local data JSON files |
--esbuild-config <filepath> |
./esbuild.config.js |
Custom esbuild config file |
--port <number> |
3000 |
Port to run the dev server on |
Example — run on a different port:
storepages dev --port 4000
Compiles and packages the theme into a ZIP file ready for upload to Store Pages.
npm run build
| Option | Default | Description |
|---|---|---|
--theme-path <path> |
./theme |
Path to the theme directory |
--source-path <path> |
./src |
Path to the source directory |
--temp-path <path> |
./.temp |
Intermediate build directory |
--build-path <path> |
./dist |
Output directory for the ZIP file |
--esbuild-config <filepath> |
./esbuild.config.js |
Custom esbuild config file |
--no-minify |
— | Skip JS & CSS minification |
Example — build without minification:
storepages build --no-minify
project/
├── theme/ # Theme files (--theme-path)
│ ├── assets/ # Static assets: images, fonts, pre-built CSS/JS
│ ├── locales/ # Translation JSON files (one per locale)
│ ├── snippets/ # Reusable Liquid snippets
│ ├── remote_snippets/ # Remote-fetched snippets (take priority over snippets/)
│ ├── templates/ # Page template files
│ ├── 404.liquid # Required — 404 error page template
│ ├── robots.txt # Required — robots.txt for search engines
│ └── theme.json # Required — theme configuration
├── src/ # Source files to bundle (--source-path)
├── data/ # Local data JSON files for dev preview (--data-path)
├── esbuild.config.js # Optional — custom esbuild configuration
└── package.json
The theme configuration file is required and must be placed at theme/theme.json.
{
"name": "My Theme",
"default_locale": "en",
"published_locales": ["en", "es"],
"templates": [
{
"key": "store",
"type": "store",
"template": "store.liquid",
"content_type": "text/html"
},
{
"key": "store-locator",
"type": "locator",
"template": "locator.liquid",
"content_type": "text/html"
}
],
"variables": {
"primary_color": "#ff5500"
}
}| Field | Type | Description |
|---|---|---|
name |
string | Theme display name — used in the ZIP filename |
default_locale |
string | Default language code (e.g. en, es) |
published_locales |
string[] | All enabled locales — each gets a URL prefix (e.g. /es/) |
templates |
array | List of page template definitions |
variables |
object | Custom key/value pairs, accessible in templates as theme_variables |
| Field | Type | Description |
|---|---|---|
key |
string | URL path for the template (e.g. store → /store) |
type |
string | Data context type — determines which data/*.json file loads |
template |
string | Liquid file path relative to the theme directory |
content_type |
string | HTTP content type (e.g. text/html) |
| Type | Data file | Description |
|---|---|---|
store |
data/store.json |
Single location page |
locator |
data/locator.json |
Multi-location finder |
landing |
data/landing.json |
Landing page |
city |
data/city.json |
City-level area page |
country |
data/country.json |
Country-level page |
administrative_area_level1 |
data/administrative_area_level1.json |
State / province page |
administrative_area_level2 |
data/administrative_area_level2.json |
County / district page |
widget_store |
data/widget_store.json |
Embeddable store widget |
widget_locator |
data/widget_locator.json |
Embeddable locator widget |
The CLI extends LiquidJS with the following custom tags.
Outputs the URL path to a file in the assets directory.
<img src="{% asset_path 'logo.png' %}" />
<!-- outputs: /assets/logo.png -->Inlines the raw content of an asset (.css, .js, or .svg) directly into the page.
<style>{% asset_content 'main.css' %}</style>
<script>{% asset_content 'main.js' %}</script>Includes a Liquid snippet from theme/snippets/ and passes named parameters to it. If a matching file exists in theme/remote_snippets/, it takes priority.
{% snippet 'header' %}
{% snippet 'card' title: location.name, url: location.url %}Inside the snippet, access parameters via snippet.param_name:
{# snippets/card.liquid #}
<div class="card">
<h2>{{ snippet.title }}</h2>
<a href="{{ snippet.url }}">View</a>
</div>Returns the URL of a template by its key as defined in theme.json, respecting the current locale prefix.
<a href="{% find_page_url 'store-locator' %}">Find a store</a>
<!-- outputs: /store-locator (or /es/store-locator in Spanish) -->Renders a <script type="application/ld+json"> block with schema.org structured data for a location. Accepts optional named parameters to override values.
{% location_json_ld location.id %}
{% location_json_ld location.id
type: 'Restaurant'
template_key: 'store' %}| Parameter | Default | Description |
|---|---|---|
type |
LocalBusiness |
schema.org type |
name |
location.name |
Override the schema name |
description |
location.summary |
Override the description |
template_key |
store |
Template key used to build the canonical URL |
Renders <link rel="alternate"> hreflang tags for all published locales. Place inside <head>.
<head>
{% content_for_head %}
</head>Output example:
<link rel="alternate" hreflang="x-default" href="/">
<link rel="alternate" hreflang="en" href="/">
<link rel="alternate" hreflang="es" href="/es/">Looks up a dot-notation key in theme/locales/{locale}.json and returns the translated string. Supports variable interpolation using {{ var }} syntax in the locale value.
{{ 'general.404.title' | t }}
{{ 'general.greeting' | t: name: location.name }}Locale file (locales/en.json):
{
"general": {
"404": { "title": "Page not found" },
"greeting": "Welcome to {{ name }}"
}
}Groups an array of objects by the value of a property. Returns an array of group objects with name, items, and size.
{% assign by_region = locations | group_by: 'region' %}
{% for group in by_region %}
<h2>{{ group.name }} ({{ group.size }})</h2>
{% for loc in group.items %}
<p>{{ loc.name }}</p>
{% endfor %}
{% endfor %}Converts a distance in meters to a human-readable string.
{{ location.distance | distance_to_human }}
<!-- 500 → "500 m" -->
<!-- 1500 → "1.5 km" -->Parses a JSON string into an object or array that can be iterated in Liquid.
{% assign items = custom_attribute_data | parse_json %}
{% for item in items %}
{{ item.name }}
{% endfor %}Parses a CSV string into an array. Pass "headers", true to use the first row as keys and return objects.
{% assign rows = csv_block | parse_csv: "headers", true %}
{% for row in rows %}
{{ row.Name }} — {{ row.City }}
{% endfor %}When running the dev server, the CLI loads data from data/ to populate templates. This lets you preview templates with realistic content without connecting to the live platform.
To use real data, download it from the platform:
- Go to storepages.localistico.com/app
- Navigate to Pages and open a page
- Click
<> View jsonto download the data - Save the file to
data/using the matching filename for the template type:
| Template type | File |
|---|---|
store |
data/store.json |
locator |
data/locator.json |
landing |
data/landing.json |
city |
data/city.json |
country |
data/country.json |
administrative_area_level1 |
data/administrative_area_level1.json |
administrative_area_level2 |
data/administrative_area_level2.json |
widget_store |
data/widget_store.json |
widget_locator |
data/widget_locator.json |
If a data file is missing, the CLI falls back to built-in placeholder data so templates always render.
Place JavaScript, TypeScript, JSX, TSX, and CSS source files in src/. Only root-level files are used as entry points — subdirectory files are bundled as imports, not compiled separately.
Compiled output goes to .temp/assets/ during development and is copied into the theme on build. Sourcemaps are enabled inline in dev mode and removed in production builds.
Create esbuild.config.js (or point to another file with --esbuild-config) to extend the default config. Your values are merged on top of the defaults.
Object export:
// esbuild.config.js
export default {
loader: { '.png': 'dataurl' },
target: 'es2020',
}Function export (receives the current command and default config):
// esbuild.config.js
export default async function (command, defaultConfig) {
if (command === 'dev') {
return { sourcemap: 'inline' }
}
return { sourcemap: false }
}- Run
npm run devto start the development server. - Visit
http://localhost:3000(or your custom--port) to see the list of available templates. - For multi-locale themes, visit
http://localhost:3000/esto preview in another locale. - Edit any template, snippet, asset, or locale file and save — the browser refreshes automatically.