Skip to content

Build a Theme App Extension

Theme app extensions let your app ship blocks that sellers place and configure in their theme editor, without editing theme code. This guide builds one from scratch using the template's two working blocks: a product star rating and a storewide announcement bar.

Requirements

1. Scaffold an extension-only app

bash
pnpm create @youcan/app@latest

Pick a name, then select Start with an extension only. The scaffolded app has no web server, extension-only apps need no app_url and no OAuth callback. Then generate the extension:

bash
cd <your-app-name>
youcan app generate extension

Select Theme extension and name it, we'll use product-info. The generated directory contains a working example:

├── extensions/
│   └── product-info/
│       ├── assets/                      # css, js, images — served via CDN
│       │   ├── announcement-bar.css
│       │   ├── announcement-bar.js
│       │   └── star-rating.css
│       ├── blocks/                      # what your app adds to themes
│       │   ├── announcement-bar.liquid  # embed block (body target)
│       │   └── star-rating.liquid       # section block
│       ├── locales/                     # translations for your strings
│       │   ├── ar.json
│       │   ├── en.default.json
│       │   └── fr.json
│       ├── snippets/                    # reusable liquid, rendered from blocks
│       │   └── stars.liquid
│       └── youcan.extension.json        # extension name and type
├── youcan.app.json                      # your app's config as code
└── package.json

One theme extension per app: an extension represents one feature, and its blocks version together.

2. Understand app blocks

Blocks are modules sellers add, reorder, and configure in the theme editor. Each block is a liquid file with a {% schema %} describing where it renders and which settings sellers control.

The template's blocks/star-rating.liquid renders a product's star rating:

liquid
{%- assign rating = block.settings.product.metafields.reviews.rating.value | default: block.settings.preview_rating | round -%}

<div class="star-rating" style="--star-color: {{ block.settings.star_color.hex }}">
  {% render 'stars', rating: rating %}
  <span class="star-rating-label">{{ 'star_rating.label' | t }}</span>
</div>

{% schema %}
{
  "label": "Star Rating",
  "target": "section",
  "stylesheet": "star-rating.css",
  "settings": [
    { "type": "product", "id": "product", "label": "Product", "autofill": true },
    { "type": "color", "id": "star_color", "label": "Star color", "default": "#ffb400" },
    { "type": "range", "id": "preview_rating", "label": "Rating shown until your app writes one", "min": 0, "max": 5, "step": 1, "default": 4 }
  ]
}
{% endschema %}

Reading it top to bottom:

  • Settings values arrive on block.settings.<id>. The product setting resolves to the product the seller picked (autofill preselects the section's product), color settings expose .hex, and the rating falls back to preview_rating until your app writes the reviews.rating metafield.
  • {% render 'stars', rating: rating %} renders snippets/stars.liquid with arguments.
  • 0 reads locales/<locale>.json, sellers' storefronts pick the right language automatically.
  • "stylesheet": "star-rating.css" injects the asset from assets/ into <head> whenever the block is present.

3. Block targets

Every block declares a target controlling where it renders:

TargetDescription
sectionPlaced inside a theme section by the seller via the theme editor.
bodyInjected globally at the end of <body> on every page. Ideal for widgets like live chat, cookie banners, or popups.
headInjected globally inside <head> on every page. Useful for third-party scripts or meta tags.

body and head blocks are embed blocks: they render on every page once the seller enables them, no manual placement needed. The template's blocks/announcement-bar.liquid is one:

liquid
{% schema %}
{
  "label": "Announcement Bar",
  "target": "body",
  "javascript": "announcement-bar.js",
  "stylesheet": "announcement-bar.css",
  "settings": [
    { "type": "text", "id": "text", "label": "Announcement", "default": "Free shipping on all orders this week" },
    { "type": "url", "id": "link", "label": "Link" },
    { "type": "color", "id": "background", "label": "Background", "default": "#111827" },
    { "type": "color", "id": "text_color", "label": "Text color", "default": "#ffffff" },
    { "type": "checkbox", "id": "dismissible", "label": "Visitors can dismiss the bar", "default": true }
  ]
}
{% endschema %}

It demonstrates the schema javascript and stylesheet fields: both assets are injected automatically, the stylesheet into <head> and the script deferred. Inside block markup you can also load assets manually with the theme filters:

liquid
{{ 'star-rating.css' | asset_url | stylesheet_tag }}
{{ 'announcement-bar.js' | asset_url | script_tag }}

WARNING

Embed blocks require the theme layout to include {{ content_for_footer }} (for body) and {{ content_for_header }} (for head). See Layout overview.

4. Develop against your store

bash
youcan app dev

The dev command creates the app on the platform on first run, installs it when you press i, and syncs your extension files to a development session on your dev store only. Stores that installed your app keep the released version, edit freely.

Then place the blocks:

  1. In the Seller Area, go to Themes and open the theme editor via Customize.
  2. For the star rating: open a product-page section, add a block, and pick Star Rating under your app's name. Adjust the color and preview rating in the block's settings.
  3. For the announcement bar: open the App embeds panel and toggle Announcement Bar on, it renders on every page.
  4. Save. Edits to your local liquid re-sync live while dev runs.

5. Deploy your extension

Changes made with youcan app dev stay on your dev store. To ship them to every store that installs your app, deploy a version:

bash
youcan app deploy -m "initial release"

This creates an immutable version of your app configuration and extension files, and releases it. See Deploy and Release App Versions for version names, rollbacks, and CI usage.

6. Keep your extension compatible

Sellers place your blocks in their themes and configure them through settings. The platform identifies a placed block by its file name, and a stored setting value by its setting id. These identifiers are the compatibility contract of your extension:

  • Do not rename or delete a block file that a released version contains. Sellers who placed that block lose it from their theme.
  • Do not change a setting id. Stored seller values are keyed by it. A changed id orphans the old value and the setting resets to its default.
  • Adding new blocks, new settings, or changing a block's markup is always safe.

The CLI warns you at deploy time when a block present in the active version is missing or renamed locally.

7. Publish your app

To publish your extension, head to the Partner Dashboard and submit your app for review:

  1. Navigate to the Apps section and locate your app.
  2. Click Push For Review.
  3. Wait for the YouCan team to review and approve your app.
  4. Once approved, it is published on the Marketplace, available for any YouCan seller.

Important

Submission requires a released version. Run youcan app deploy first. You can keep deploying and editing your app during the review; the review evaluates your listing.

Next steps