Web/Guru

Shopify theme app extensions and app blocks: a practical guide for app developers (2026)

Script tags stop running on Shopify storefronts on March 1, 2027. How to move to theme app extensions: app blocks vs embeds, deep links, limits, code.

Published
Updated
October 5, 2026
Read time
11 min read
Written by
Webguru
Shopify theme app extensions and app blocks: a practical guide for app developers (2026)
/On this page01 / 14

If your app puts anything on a Shopify storefront, such as a review widget, a size chart, a chat bubble or a tracking script, a theme app extension is now the supported way to ship it. Shopify requires one for any App Store app that integrates with themes, and for the Built for Shopify badge. Since October 1, 2026, apps can't create or update script tags, and on March 1, 2027 Shopify stops injecting script tags into storefronts at all. If your app still relies on them, you have until March to move that code into a theme app extension or a web pixel.

This guide is for app developers and the agencies that commission them. It covers how app blocks and app embed blocks differ, when a plain theme section is the better answer, a minimal app block you can deploy today, and how to get merchants to turn it on. It also covers the limits that fail a deploy and what happens when a merchant uninstalls your app.

What a theme app extension is

A theme app extension is a bundle of Liquid blocks, snippets, assets and translations that Shopify serves inside a merchant's theme without your app ever editing theme code. You create it with Shopify CLI, and it ships as part of your app version. The generated folder looks like this:

Folder or fileWhat goes in it
blocks/One Liquid file per app block or app embed block. The file name becomes the block's handle.
snippets/Reusable Liquid snippets that your blocks render.
assets/CSS, JavaScript and images, served from Shopify's CDN.
locales/Schema locale files (theme editor labels) and storefront locale files (customer-facing text). Merchants can't edit your storefront strings.
shopify.extension.tomlThe extension name and type = "theme".

One catch for agencies: admin-created custom apps can't use app extensions at all, so they can't ship theme app extensions or web pixels. If a client's integration runs on a custom app that was created in the Shopify admin, it has to be rebuilt as a CLI or Dev Dashboard app first. Our custom app vs public app guide explains the app types.

App blocks vs app embed blocks

Both live in blocks/. The target value in the {% schema %} tag decides which one you're building.

App blockApp embed block
target valuesectionhead, body or compliance_head
Where it rendersInside a section, or as its own section in an Apps wrapperInjected before </head> or </body>
Theme supportOnline Store 2.0 themes only (JSON templates, sections that accept @app blocks)Vintage and Online Store 2.0 themes
How merchants turn it onAdd block or Add section, then pick it under Apps in the theme editorToggle it on under App embeds in the theme editor
State after installNot added to the themeDeactivated
Dynamic sourcesYes, with autofill resource settingsNo, global Liquid scope only
Typical useReviews, ratings, size charts, product badges inlineChat bubbles, pop-ups, SEO meta tags, analytics, cookie banners

Use compliance_head only when you really need it, for example for a cookie consent banner. Shopify includes those embeds first.

For merchants, adding an app block means Online Store > Edit theme, then Add block (inside a section) or Add section (full width), and picking your block under Apps. Activating an embed means clicking the App embeds icon in the theme editor sidebar and switching on the toggle. Merchants with several markets can even set embeds per market.

App block, app embed or custom theme section?

Not every storefront feature needs an app. This is how we decide:

SituationBuildWhy
Your app shows content the merchant should position on a pageApp blockThe merchant places, reorders and styles it in the theme editor.
Your app needs to load JavaScript on every page, or show a floating elementApp embed blockIt works on vintage themes too, and can be limited to the templates that need it.
Your app only collects analytics or conversion eventsWeb pixelShopify's replacement for tracking script tags. It needs no theme editor step.
One store, one theme, content with no app backendCustom theme sectionNo app to install or maintain. Your team owns the code in the theme.
The feature must appear on checkout or the Order status pageCheckout or customer account UI extensionTheme app extensions can't render on checkout pages.
The merchant's published theme is vintage and your feature is inlineApp embed block that places content with JavaScriptShopify's own fallback for themes without app block support.

The custom section route is honest advice for single-store work. A section in the merchant's theme is simpler than an app. The trade-off is that editing theme code takes the theme off its automatic upgrade path, and the code stays behind if anyone removes the feature later. For anything installed on more than one store, use an extension.

Theme app extensions vs script tags: the 2026–2027 deadlines

Shopify's changelog of August 24, 2026 set the storefront timeline:

  • October 1, 2026: scriptTagCreate and scriptTagUpdate return an error, and POST/PUT to the REST ScriptTag resource fail, on every API version. Pinning an older version doesn't help. Existing script tags keep running, and you can still read and delete them.
  • March 1, 2027: Shopify stops injecting script tags into storefront pages.
  • Order status page: script tags there already stopped on August 26, 2026. Our Thank you page tracking guide covers what replaced them.

We track these dates, and the other Shopify platform deadlines, on our Shopify deprecation tracker.

From October 1, merchants who install an app that uses script tags and ships neither an app embed block nor a web pixel see a dismissible warning banner that part of the app will stop working.

The safe migration order, from Shopify's storefront script tag guide:

  1. Build a theme app extension with an app embed block (or a web pixel for pure tracking).
  2. Send the merchant into the theme editor with a deep link that activates the embed.
  3. Confirm the embed is active on the published theme.
  4. Only then delete the script tag with scriptTagDelete. Running both loads your script twice, which can double-count events or render your UI twice.

Build a minimal app block, step by step

This example adds a small "delivery note" to product pages. It uses the page's product automatically and gives the merchant two settings.

Step 1. From your app's root folder, generate the extension and choose Theme app extension:

bash
shopify app generate extension
# choose "Theme app extension", then name it, for example "storefront-blocks"

Step 2. Replace the sample block with extensions/storefront-blocks/blocks/delivery-note.liquid:

liquid
{%- assign p = block.settings.product -%}
<div class="delivery-note" style="--delivery-note-accent: {{ block.settings.accent }}">
  {%- if p.available -%}
    <p>{{ block.settings.message | escape }}</p>
  {%- else -%}
    <p>This product is currently out of stock.</p>
  {%- endif -%}
</div>
{% schema %}
{
  "name": "Delivery note",
  "target": "section",
  "stylesheet": "delivery-note.css",
  "enabled_on": { "templates": ["product"] },
  "settings": [
    { "type": "product", "id": "product", "label": "Product", "autofill": true },
    { "type": "text", "id": "message", "label": "Message", "default": "Order by 2 pm for next-day dispatch" },
    { "type": "color", "id": "accent", "label": "Accent color", "default": "#1a7f37" }
  ]
}
{% endschema %}

Step 3. Add extensions/storefront-blocks/assets/delivery-note.css. Because the schema references it, Shopify adds it to the page once, however many copies of the block the merchant adds.

Step 4. Preview it on your dev store. The CLI walks you through adding the block in the theme editor. The hot-reloading preview works in Google Chrome only, and without --theme the CLI uploads Dawn to the dev store:

bash
# --theme takes a theme ID or name
shopify app dev --theme "Horizon"

Step 5. Check the limits, then create and release a version:

bash
shopify app build
shopify app deploy --version "1.4.0" --message "Add delivery note block"
# create the version without releasing it:
shopify app deploy --no-release

A few details in that schema matter. "autofill": true on the product setting makes the block follow the section's product, so it works on every product page. enabled_on keeps the block off templates where it makes no sense. Keep the name under 25 characters, and don't put your app's name in it, because the theme editor already shows it underneath.

Get merchants to turn it on

Nothing appears after install. App blocks aren't added, and app embeds are off. Onboarding is part of the build, and the App Store requires detailed setup instructions for both. Two tools make it painless.

Deep links. Send the merchant straight into the theme editor with the block ready to preview and save. api_key is your app's client ID, and handle is the block's file name without .liquid:

text
https://your-store.myshopify.com/admin/themes/current/editor?template=product&addAppBlockId={api_key}/delivery-note&target=mainSection
https://your-store.myshopify.com/admin/themes/current/editor?context=apps&template=product&activateAppId={api_key}/{embed-handle}

App block links can also target newAppsSection, a section group (sectionGroup:header, footer or aside) or a specific section ID. Each link adds one block. Before you link to a section or template, check it exists in the theme with the read_themes scope. Otherwise the merchant sees an error.

Detection. To show "Step 2 of 3: done" in your app, call app.extensions() from your embedded app. It needs no extra scopes and reports on the published theme only:

js
const extensions = await shopify.app.extensions();
const theme = extensions.filter((ext) => ext.type === "theme_app_extension");
const isActive = (handle) =>
  theme.some((ext) =>
    ext.activations.some((block) => block.handle === handle && block.status === "active"),
  );
// "available" = shipped but not enabled; "unavailable" = hidden by available_if
console.log("Delivery note placed:", isActive("delivery-note"));

From your backend, read the published theme's files with read_themes. App blocks appear in template JSON files with a type like shopify://apps/<app>/blocks/<block>/<id>. App embeds appear in config/settings_data.json once first enabled, and stay there with "disabled": true if switched off, so check that flag.

Also remember that embeds are enabled per theme. When a merchant publishes a new theme, your app isn't active on it until they re-enable it, so re-check activation.

Limits that fail a deploy

Shopify validates the extension on deploy. If you go over an enforced limit, the extension fails validation and doesn't update. shopify app build runs Theme Check so you catch this locally.

ContentLimitType
All files in the extension10 MBEnforced
Number of blocks30Enforced
Locale files100Enforced
Size of each locale file15 KBEnforced
Liquid across all files100 KBEnforced
CSS referenced by the schema (compressed)100 KBSuggested
JavaScript referenced by the schema (compressed)10 KBSuggested

JSON in your extension can't contain comments or trailing commas, even though theme files allow them. Your blocks also can't read content_for_header, content_for_layout or content_for_index, and they see only the parent section's id.

Performance

Everything in assets/ is served from Shopify's CDN. Files you reference with the javascript and stylesheet schema attributes are included once per page, even if a merchant adds several of your blocks. Shopify suggests keeping that JavaScript under 10 KB compressed. If you want the Built for Shopify badge, your app must not lower the storefront Lighthouse performance score by more than ten points.

In practice, use enabled_on to keep embeds to the templates that need them. Defer anything that isn't needed for first paint. Test in the theme editor too, because Shopify expects your app to work there.

What happens on uninstall

This is the strongest argument for extensions when you pitch them to merchants. When a merchant uninstalls your app, Shopify removes its app blocks and app embeds from their themes automatically and completely. Nobody has to hunt through theme code.

Compare that with the old pattern of writing snippets into the theme through the Asset API. Shopify's own docs describe that pattern leaving "ghost code" behind, which can cause quality and performance problems and takes the theme off its upgrade path. Built for Shopify also says apps shouldn't add, remove or edit a merchant's theme files, apart from a few exceptions such as page builders and backup tools. If you inherit a store full of leftover snippets from old apps, cleaning that out is a separate job.

Versioning and releases

Your theme app extension is versioned together with your app configuration and every other extension as one app version:

  • shopify app deploy creates a version and releases it. Releasing replaces the active version for every store with your app installed, and the rollout can take several minutes.
  • You can revert to a previous version at any time, from the CLI or the Dev Dashboard.
  • A CLI-created version includes only the extensions that exist in your local checkout. Deploying from a branch that's missing an extension leaves that extension out of the version.
  • In CI, pass --allow-updates (or --no-release) so the deploy doesn't wait for a confirmation prompt.

Common mistakes

  • Deleting script tags before the embed is live. The feature goes dark until the merchant activates the embed.
  • Assuming app blocks work on every theme. Vintage themes need an app embed fallback. Public listings should say whether an Online Store 2.0 theme is required.
  • Cluttering the App embeds panel. Every app's embeds share one panel. Keep settings to visual choices and move the rest into your app.
  • Linking to a section that doesn't exist. Check the template, section group or section ID with read_themes before you build the deep link.
  • Trying to render on checkout. Theme app extensions can't render on checkout pages or the Order status page. Our checkout customization guide covers the right extension types.
  • Reading theme files over REST. New public apps must use the GraphQL Admin API, which can read theme files too. See our REST to GraphQL migration guide.

Launch checklist

  • The block target is correct: section for inline content, head/body for embeds.
  • enabled_on or disabled_on limits templates (only one of the two).
  • Resource settings use autofill where the block follows a product or collection.
  • shopify app build passes, and the Liquid total is under 100 KB.
  • Schema JavaScript is under the suggested 10 KB compressed.
  • Deep links for each block and embed have been tested on Dawn and Horizon.
  • The onboarding screen reports activation from app.extensions().
  • The backend re-checks activation after a theme change.
  • Script tags are deleted per store only after the embed is confirmed active.
  • Analytics-only scripts have moved to a web pixel.
  • The block works on a vintage theme, or the listing says Online Store 2.0 is required.
  • --no-release staging and a rollback plan are documented for the team.

Want help with your theme app extension?

We build and migrate Shopify apps, including theme app extensions, script tag replacements and web pixels, and we test them on real themes before release. If your app still loads code through script tags ahead of the March 1, 2027 cut-off, see our Shopify development services and tell us what your storefront code does today.

Sources

/Tags#Shopify#Apps#Themes

Written by

Webguru

Shopify Development Team

Webguru.pk is a Shopify development team in Lahore, Pakistan, building stores, custom apps and integrations for brands worldwide.

/Share

Need a hand with this?

We design, build and fix this kind of work for Shopify, SaaS and AI products.

/faqs

Theme App Extensions: Frequently Asked Questions

An app block has a schema target of section and renders inline, where the merchant places it in the theme editor. An app embed block has a target of head, body or compliance_head, is injected before the closing head or body tag, and suits floating elements, meta tags and scripts.

App embed blocks work on both vintage and Online Store 2.0 themes. App blocks need an Online Store 2.0 theme with JSON templates and sections that accept blocks of type @app, so apps that must support vintage themes need an app embed fallback.

Since October 1, 2026, apps can't create or update script tags on any API version, although existing ones keep running. On March 1, 2027, Shopify stops injecting script tags into storefronts. Script tags on the Order status page already stopped on August 26, 2026.

Shopify automatically and entirely removes the app's blocks and app embeds from the store's themes. Apps that wrote code into theme files through the Asset API can leave that code behind, which is why Built for Shopify requires theme app extensions.

Call app.extensions() from your embedded app in the Shopify admin. It needs no extra access scopes and reports each block's status on the published theme. From your backend, read the published theme's config/settings_data.json with the read_themes scope and check that your embed isn't marked disabled.

No. Admin-created custom apps can't use app extensions, including theme app extensions and web pixels. The app has to be rebuilt as an app created with Shopify CLI or the Dev Dashboard to use them.

Still loading storefront code through script tags?

We build the theme app extensions, app embeds and web pixels that replace them, and test them on real themes before release.

or write to+44 7882 755642