Theme App Extensions ship blocks across many themes via the App Store, ideal for distributed apps with version control
Local section schemas live in the theme repo, win for one-store custom builds with no review wait and full Liquid control
Shopify Functions overlap on cart, checkout, discount, delivery logic and often beat both for backend rules
Real schema snippets included plus a 5-question decision matrix that picks the right architecture in under a minute
Honest tradeoffs: TAE has install ergonomics and reach, local sections have speed and freedom, Functions own backend logic
I shipped two Shopify projects in the same week, one as a Theme App Extension and one as plain Liquid sections, and the architecture choice mattered more than any of the code inside. The wrong pick adds 4 weeks of app review for a feature that should have taken an afternoon. The other wrong pick locks a paying customer into a one-store snowflake they can never resell.
Here is the decision matrix I now run on every brief, with real schema snippets and the parts nobody mentions out loud.
What each one actually is
A Theme App Extension (TAE) is a folder you ship inside a public or custom Shopify app. It declares blocks, sections, and app embeds that any merchant can drop into any Online Store 2.0 theme through the theme editor. The merchant installs your app, the blocks appear in the section picker, and your code lives behind a versioned app bundle that updates independently from the theme.
A local section schema is a plain .liquid file inside sections/ in the theme repo. It exposes the same {% schema %} JSON to the theme editor, but it lives and dies with that one theme. There is no app, no install flow, no marketplace. You edit, push to the theme via CLI or GitHub integration, and it is live.
Both produce the same merchant experience in the editor. A drag-and-drop block with settings, presets, and a live preview. The difference is everything around them.
Quick mental model: TAE is a published npm package, local sections are scripts in your repo. Same JavaScript inside, completely different distribution model.
When Theme App Extensions win
I reach for TAE when the answer to any of these is yes:
This block needs to ship to more than one store.
The merchant needs to update it without me touching their theme.
I want App Store distribution or a private app listing.
The feature ties into an app backend (webhooks, billing, admin UI, metafields owned by the app).
The merchant runs a third-party theme they pay for and refuses to fork.
Real example: a price-watch widget that pulls historical prices from my app database. The widget is the easy part. The app review, billing, GDPR data processing agreement, and uninstall cleanup are 80% of the work. TAE is non-negotiable here because the block has to talk to my app and stay in sync when I push updates.
The schema for a TAE block looks almost identical to a section schema, with one critical extra: target and available_if for app embeds.
{% schema %}
{
"name": "Price Watch",
"target": "section",
"settings": [
{ "type": "text", "id": "label", "label": "Label", "default": "Price history" },
{ "type": "color", "id": "accent", "label": "Accent", "default": "#e3fc02" },
{ "type": "range", "id": "days", "label": "Days back", "min": 7, "max": 365, "step": 7, "default": 90 }
]
}
{% endschema %}
The merchant gets the block in the picker the moment they install the app. Updates roll out via the next app version. No theme edit, no reupload, no support ticket asking which file to change.
What people forget about TAE: you cannot read or write theme settings, you cannot use {{ settings.thing }} from the theme, and you have a strict allowlist for which Liquid objects are available. You also cannot ship JavaScript that breaks the theme contract, because Shopify's theme inspector will fail your app review.
When local section schemas win
Local sections win when distribution is not a concern and you want maximum speed and control.
I reach for local sections when:
This is a one-store custom build for a specific client or my own shop.
I need full access to the theme's settings, snippets, and global state.
I want to ship today, no app review, no listing, no billing API.
The section is deeply branded and would never make sense on another store.
I need to compose with theme snippets that a TAE cannot reach.
Real example: the homepage hero on raxxo.shop. It pulls in metaobjects, references the theme's typography settings, animates with the theme's accent variable, and uses three custom snippets. Putting this in a TAE would mean reimplementing the snippets inside the app extension, redeclaring color tokens, and accepting that I lose access to theme.settings.heading_font. For a shop I own, that is silly.
A local section schema looks like this:
{% schema %}
{
"name": "Hero with Metaobject",
"tag": "section",
"class": "rx-hero",
"settings": [
{ "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "New drop" },
{ "type": "richtext", "id": "headline", "label": "Headline" },
{ "type": "url", "id": "cta_url", "label": "CTA URL" }
],
"blocks": [
{ "type": "feature", "name": "Feature", "settings": [
{ "type": "text", "id": "title", "label": "Title" },
{ "type": "image_picker", "id": "image", "label": "Image" }
]}
],
"presets": [{ "name": "Hero with Metaobject" }]
}
{% endschema %}
Ship it, push the theme, done. No app, no review, no version skew between merchant theme and app bundle. If you want the deeper context on local section authoring, see Shopify Section Schema Patterns Editors Actually Love.
The honest downside: this section is married to this theme. If the merchant duplicates the theme to test a holiday version, the section comes along, but if they switch to a totally new theme they lose it. There is no install flow, no portability, and no app to charge for.
The Functions overlap nobody mentions
Here is the part that wrecks half the decisions I see online. Theme App Extensions and local sections are presentation. They render Liquid into HTML in the theme editor and on the storefront. They do not run logic at checkout, they do not modify discounts, they do not change shipping rules.
Shopify Functions do.
If your merchant ask is "show a custom badge when a product is on a deep discount," that is a section problem. If the ask is "give 10% off when cart subtotal is over 200 EUR and the customer has 3 prior orders," that is a Functions problem and neither TAE nor local sections are the right tool. I covered this in more depth at Shopify Functions Replaced 8 Apps In One Saturday.
The cleanest mental split I have found:
TAE or local section: anything the merchant edits in the theme editor and the customer sees on a page.
Functions: anything that runs on cart, checkout, fulfillment, discount, payment, or delivery logic.
Admin UI extensions: anything the merchant configures inside the Shopify admin (not the theme editor).
Most real product features need two of these glued together. A custom upsell block in the cart drawer is a section that reads metafields written by a Function that ran when the cart was updated. Pretending you can solve it with one architecture leads to either over-stuffed Liquid or a Function that tries to do rendering, both of which feel wrong inside a week.
The 5-question decision matrix
When a brief lands, I run this in order and stop at the first clear answer.
Does this need to ship to more than one store? Yes -> TAE. No -> keep going.
Does this run on cart, checkout, discount, delivery, or payment? Yes -> Function (with a section for the visual surface, if any). No -> keep going.
Does the merchant own the theme and let you push to it? No (locked theme, third-party support contract) -> TAE. Yes -> keep going.
Does the section need theme global settings, snippets, or typography tokens? Yes -> local section. No -> keep going.
Are you billing for this feature through Shopify's app billing? Yes -> TAE (you need an app to use the billing API). No -> local section, ship today.
This matrix has saved me from building two things this year that should never have been TAEs and one thing that should never have been a local section. The cost of a wrong pick is real: TAE rebuilds take a week, app reviews take 5 to 14 days, and refactoring a one-store snowflake into a portable extension takes 3 to 5 days minimum.
For the bigger picture across themes, sections, and metaobjects, the Shopify Dev cluster on the Lab collects the patterns I keep coming back to.
Bottom Line
TAE and local section schemas solve different distribution problems with the same Liquid syntax. TAE is for code that ships to many stores through an app. Local sections are for code that ships to one store through the theme repo. Shopify Functions handle the backend logic both presentation layers fake at their peril. The right answer is almost never "the modern one," it is the one that matches your distribution model and your billing model.
If you are building a custom Shopify experience and want a working starting point, the Claude Blueprint is how I scaffold every new RAXXO project, including the directory layout that keeps TAE, local sections, and Functions cleanly separated from day one. Pick the architecture that matches the question, ship the first version in an afternoon, refactor only when the brief actually changes.
Top comments (0)