If you build WordPress block themes, you already understand most of Shopify theme development. You just know it by different names. A template is still a template. A block is still a block. Global styles still exist, they simply live in a different file. What changes is the language (Liquid instead of PHP), where the data comes from (Shopify’s objects instead of WordPress queries), and what you are not allowed to do at all.

This guide translates block theme concepts into Shopify’s, one by one, for WordPress theme developers picking up their first Shopify project. To keep it honest, we built a small real Shopify theme while writing it, using the Shopify CLI, and we show that code and what the CLI’s checker said about it. It is not a full store theme. It is enough to make every comparison below concrete.

The one-table translation

In a WordPress block themeIn a Shopify themeHow close
theme.json settings and stylesconfig/settings_schema.json (the options) and config/settings_data.json (the saved values)Close in purpose, different in shape
Style variations (styles/*.json)Theme style presets saved in settings_data.jsonClose
Templates (templates/*.html)Templates: templates/*.json (a list of sections) or templates/*.liquid (code)Very close
Template parts (header, footer)The layout file (layout/theme.liquid) plus section groups for header and footerClose
Blocks with block.json attributesTheme blocks in blocks/*.liquid with a {% schema %} of settingsClose in spirit
Inner blocksNested theme blocks, or body content passed into a blockClose
Block patternsPresets on sections and blocksSimilar job
PHP templates and template tagsLiquid tags, filters and objectsDifferent language, same role
Template partials (get_template_part)Snippets with {% render %}Very close
Translations (.pot, .po)Locale JSON files with the t filterClose
The Site EditorThe theme editorClose
functions.php, hooks, custom queriesNo equivalent in a themeNone

Keep the last row in mind. It is the biggest mental shift, and we will come back to it.

Shopify has two ways to build pages now

This is the first thing a WordPress developer should know in late 2026, because tutorials disagree depending on when they were written.

The established way: JSON templates and sections. A JSON template is a list of sections in order, and each section is a Liquid file with its own settings and blocks. Merchants add, remove and reorder sections in the theme editor. Shopify’s own Dawn theme works this way, and so do most themes in the Theme Store. Shopify’s JSON template docs state the limits: up to 25 sections per template, each with up to 50 blocks, and up to 1,000 JSON templates per theme.

The newer way: Liquid templates that call blocks directly. Shopify’s Skeleton theme, the starting point the Shopify CLI clones by default, uses no sections folder and no JSON templates at all. Each template is a Liquid file that composes the page from theme blocks with the {% block %} tag, which the Liquid reference describes as rendering a reusable theme block directly from a template, the same way {% render %} renders a snippet.

Both are current. If you join a project based on Dawn or a Theme Store theme, you will work with sections. If you start fresh from the CLI today, you start with the block-composed model. For a block theme developer, the newer model will feel especially familiar: a template that reads as a tree of blocks is exactly what a block theme’s templates/index.html looks like.

What we built

We started from Skeleton with one command:

shopify theme init brndle-demo

Then we added one small feature, the kind of thing you build in your first hour on any theme: a “feature row”, a heading with a grid of feature cards, plus an alternate page template that uses it. In WordPress terms, it is a group block with a heading and a columns layout of cards, saved as a pattern. Here is the Shopify version.

The card: one theme block

A theme block is a Liquid file in blocks/. The markup comes first, the settings schema last. block.settings is the equivalent of a block’s attributes, and the schema is roughly your block.json attributes plus the inspector controls that edit them:

{% doc %}
  A single feature: a short title and one or two sentences of text.
  Merchants edit both in the theme editor.
{% enddoc %}

<div class="feature-card" {{ block.shopify_attributes }}>
  <h3 class="feature-card__title">{{ block.settings.title }}</h3>
  <p class="feature-card__text">{{ block.settings.text }}</p>
</div>

{% schema %}
{
  "name": "t:general.feature_card",
  "settings": [
    { "type": "text", "id": "title", "label": "t:labels.title", "default": "Feature" },
    { "type": "textarea", "id": "text", "label": "t:labels.text" }
  ]
}
{% endschema %}

{{ block.shopify_attributes }} is what lets the theme editor find and select the block on the page, much like the wrapper attributes WordPress adds to a block in the editor. Leave it out and merchants cannot click the block to edit it.

The row: a block that wraps other blocks

The row takes the cards as body content and prints them with {{ content }}. This is the closest thing to inner blocks in the new model: the template decides what goes inside, and the block decides how it is wrapped.

{% doc %}
  A heading followed by a responsive grid of feature cards.

  @param {string} content - The feature cards to place in the grid.
{% enddoc %}

<div class="feature-row" style="--feature-columns: {{ block.settings.columns }};" {{ block.shopify_attributes }}>
  {%- if block.settings.heading != blank -%}
    <h2 class="feature-row__heading">{{ block.settings.heading }}</h2>
  {%- endif -%}
  <div class="feature-row__grid">
    {{ content }}
  </div>
</div>

{% schema %}
{
  "name": "t:general.feature_row",
  "settings": [
    { "type": "text", "id": "heading", "label": "t:labels.heading" },
    { "type": "range", "id": "columns", "label": "t:labels.columns", "min": 2, "max": 4, "step": 1, "default": 3 }
  ]
}
{% endschema %}

The columns range setting becomes a CSS custom property, and the stylesheet does the layout. That habit carries straight over from block themes: settings produce variables, CSS consumes them.

The template: composing the page

An alternate template is just another file named after the resource type, here templates/page.features.liquid. A merchant can assign it to any page in the admin, the same way you pick a custom template for a page in WordPress:

{% block 'container' %}
  <h1>{{ page.title }}</h1>
  {{ page.content }}
{% endblock %}

{% block 'container' %}
  {% block 'feature-row', heading: 'Why shop with us', columns: 3 %}
    {% block 'feature-card', title: 'Fast shipping', text: 'Orders leave our studio within 24 hours.' %}{% endblock %}
    {% block 'feature-card', title: 'Easy returns', text: 'Thirty days to change your mind.' %}{% endblock %}
    {% block 'feature-card', title: 'Made to last', text: 'Every piece is checked by hand before it ships.' %}{% endblock %}
  {% endblock %}
{% endblock %}

Notice the parameters. Because heading and columns match settings in the row’s schema, passing them from the template also sets block.settings.heading and block.settings.columns, according to Shopify’s docs. The template supplies sensible defaults; the merchant can still change them in the theme editor.

The styles

Skeleton keeps all CSS in assets/ rather than inside Liquid files, so the grid went into assets/base.css, with a mobile-first single column:

.feature-row__grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 750px) {
  .feature-row__grid {
    grid-template-columns: repeat(var(--feature-columns, 3), minmax(0, 1fr));
  }
}

What the checker said

The Shopify CLI includes Theme Check, a linter for themes, which is roughly what PHPCS with the WordPress standards is to a WordPress theme. We ran it twice with Shopify CLI 3.93.2:

shopify theme check
  • On the untouched Skeleton theme: 3 warnings, all “Unknown object ‘content’ used”, in Skeleton’s own container, hello-world and liquid-tips blocks.
  • After adding our two blocks and the template: 29 files inspected, still the same 3 warnings, none in our files.

The difference is one line. Our row block documents content with @param inside {% doc %}, as Shopify’s block tag documentation recommends (“always document content in {% doc %}“). Skeleton’s blocks do not, and the checker treats the undocumented content as an unknown object. It is a small thing, but it is exactly the kind of detail you only learn by building, and it is a good habit: document every parameter a block accepts, the way you would document a function’s arguments.

What we did not do: preview it on a live store. That needs a development store from a Shopify Partner account and shopify theme dev, which serves your local files against real store data with hot reload. If you are evaluating Shopify for a client, that is your next step after this post.

The same feature in the sections model

If your project uses JSON templates and sections, the feature row would become a section file in sections/ with its own schema, and the template would be a JSON file listing which sections appear and in what order. The shape of a JSON template is small:

{
  "sections": {
    "main": { "type": "main-page", "settings": {} },
    "features": {
      "type": "feature-row",
      "settings": { "heading": "Why shop with us", "columns": 3 }
    }
  },
  "order": ["main", "features"]
}

The type is the section’s file name, settings holds the saved values, and order decides the sequence. Merchants change this file indirectly, every time they add, remove or reorder sections in the theme editor. It is the closest thing Shopify has to a block theme template saved from the Site Editor: data that describes a layout, rather than code that builds it.

The trade-off between the two models mirrors one you already know. A JSON template gives merchants the most freedom, like an unlocked block template. A Liquid template that composes blocks keeps the structure in code, with merchants editing the settings of each block, closer to a locked template with editable content.

Liquid for PHP developers

Liquid is a template language, not a programming language. That is deliberate. You cannot open a database connection, call an API or write a class. You can loop, branch, assign variables and transform values with filters.

PHP in a WordPress themeLiquid in a Shopify theme
<?php echo esc_html( $title ); ?>{{ title | escape }} (Liquid does not escape output for you; add the escape filter to text that could contain HTML)
if ( $x ) { } else { }{% if x %} {% else %} {% endif %}
foreach ( $posts as $post ){% for product in collection.products %}
get_template_part( 'card' ){% render 'card' %}
__( 'Add to cart', 'theme' ){{ 'products.add_to_cart' | t }}
get_theme_file_uri( 'a.css' ){{ 'a.css' | asset_url }}
WP_QueryNo equivalent: you use objects Shopify already provides, such as product, collection and cart

The Liquid reference lists every tag, filter and object. Spend an hour with the objects page before your first project; most of the “how do I query…” questions answer themselves once you see what each template already has in scope.

Global settings: theme.json versus settings_schema.json

In a block theme, theme.json declares both the options (palette, font sizes) and their default values, and the Site Editor saves changes to the database. Shopify splits this in two. config/settings_schema.json declares the theme settings merchants see (colour pickers, font pickers, checkboxes), and config/settings_data.json stores the values, including saved style presets. Shopify’s settings documentation covers both.

In Liquid, global settings are available as settings.your_id. Skeleton turns them into CSS custom properties in a css-variables snippet rendered from the layout, for example --color-background: {{ settings.background_color }};, which is the same pattern as WordPress generating --wp--preset--* variables from theme.json. If you are used to thinking in design tokens, keep doing exactly that; our post on design tokens that outlive your CSS framework applies to Shopify themes just as much.

Headers and footers: layouts and section groups

A WordPress block theme has template parts for the header and footer, included by each template. Shopify has one layout file, layout/theme.liquid, which wraps every page and prints the template’s output with {{ content_for_layout }}. Think of it as a single parent template that every page inherits.

In section-based themes, the header and footer areas are section groups, so merchants can add an announcement bar or reorder footer sections in the theme editor. In Skeleton, the layout renders header and footer theme blocks directly.

Patterns versus presets

Block patterns are pre-arranged groups of blocks editors insert and then edit. Shopify’s closest equivalent is presets: a section or block can define presets in its schema, and those presets are what appear in the theme editor’s “Add section” or “Add block” pickers. Shopify’s docs note that a section or block needs presets to be addable to JSON templates through the theme editor; without them it can only be placed in code.

The practical takeaway for a block theme developer: if you want merchants to add something themselves, give it a preset, exactly as you would register a pattern so editors can find it in the inserter.

What has no equivalent

This is where WordPress developers get frustrated, so it is worth being blunt.

  • No functions.php. A Shopify theme cannot run server code. There are no hooks, no filters, no custom endpoints.
  • No custom queries. You cannot fetch arbitrary data. You use the objects Shopify gives each template, plus metafields and metaobjects for custom data the merchant defines.
  • No plugins inside the theme. Extra functionality comes from Shopify apps, which can inject app blocks into themes that support them.
  • No direct file writes or database access. Everything persistent goes through Shopify.

The trade-off is the point of the platform: you give up flexibility at the server level, and in return you never manage hosting, security patches or performance at that level. Whether that trade is right for a client is a business question; our sister site’s WooCommerce vs Shopify comparison covers it from the store owner’s side.

Translations: locales instead of .po files

Every user-facing string in Skeleton goes through the t filter with a key, such as {{ 'hello_world.title' | t }}, and the strings live in JSON files in locales/. Strings that only appear in the theme editor (block names, setting labels) live in a separate schema locale file. When we added our blocks, we added their names and labels to locales/en.default.schema.json, the equivalent of wrapping strings in __() and updating your .pot.

The workflow, side by side

TaskWordPress block themeShopify theme
Start a themeCreate Block Theme plugin, or a startershopify theme init
Local developmentwp-env, Local, Dockershopify theme dev against a development store
LintingPHPCS with WordPress standards, theme check pluginsshopify theme check
DeployZip, Git deploy, or a host’s toolsshopify theme push, or GitHub integration
Visual editingSite EditorTheme editor

Common mistakes WordPress developers make

  • Looking for a place to put PHP. There is none. If a feature needs server logic, it is an app, not a theme feature.
  • Forgetting {{ block.shopify_attributes }} on a block’s wrapper, so merchants cannot select it in the theme editor.
  • Hard-coding URLs like /cart instead of using Liquid’s routes object, which breaks on stores with other languages or markets.
  • Skipping the t filter for “just this one label”. Shopify’s own starter treats every user-facing string as translatable.
  • Not documenting block parameters with {% doc %}, which, as we saw, is enough to trip Theme Check.
  • Treating the theme editor as an afterthought. Merchants live there, as editors live in the Site Editor. Test every setting in it.

A first-week plan for a WordPress developer

  1. Day 1: run shopify theme init, read the Skeleton files top to bottom, and read Shopify’s theme architecture overview.
  2. Day 2: build one theme block with two settings, document its parameters with {% doc %}, and get shopify theme check clean for your files.
  3. Day 3: set up a development store and run shopify theme dev. Edit your block in the theme editor and watch the settings round-trip.
  4. Day 4: clone Dawn and read one section and its JSON template, so you can work on section-based themes too.
  5. Day 5: rebuild one of your own block patterns as a Shopify block or section with a preset. Compare what moved across and what did not.

Questions WordPress developers ask

Is Shopify theme development harder than WordPress theme development?

Different rather than harder. The theme layer is smaller and stricter: no server code, no custom queries. For a developer who already thinks in blocks, templates and design tokens, the concepts transfer quickly. Liquid is the main new thing to learn.

Should I learn sections or the newer block-composed templates?

Both, eventually. Most existing themes, including Dawn and many Theme Store themes, use JSON templates and sections. New projects started from the CLI’s default starter use Liquid templates that call theme blocks directly.

What is the Shopify equivalent of a block pattern?

A preset on a section or block. It is what merchants see when they add something in the theme editor.

Can a Shopify theme have custom post types?

Not in the WordPress sense. Custom structured content uses metaobjects and metafields, which merchants define in the admin and themes read through Liquid.

Can I reuse my CSS and design tokens from a block theme?

Mostly, yes. CSS is CSS, and custom properties work the same way. Map your theme.json presets to Shopify theme settings, output them as variables in a snippet the layout renders, and most of your component styles carry over with only class name changes.

Do I need a paid Shopify account to develop themes?

Shopify offers development stores through its Partner program for building and testing themes. You can scaffold and lint a theme locally, as we did, without one, but you need a store to preview it.

Where to start

Install the Shopify CLI, run shopify theme init, and build one block. You will recognise more than you expect: a template that composes blocks, settings that become CSS variables, a global settings file that behaves like theme.json. The parts you will miss, mostly functions.php and custom queries, are the parts Shopify deliberately took away. If you are weighing Shopify against WordPress for a project rather than learning it for a client, our honest look at the best WordPress alternatives is a good companion read.