Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

Theme Block Extensions

Theme blocks are Liquid-based UI components that merchants can add to their storefront themes. They appear in the theme editor alongside native blocks, allowing merchants to position and configure them visually.

How Theme Blocks Work

  1. Your app installs block files (.aqua template + .schema.json schema)
  2. Blocks appear in the merchant’s theme editor under “App Blocks”
  3. Merchants drag blocks into sections and configure settings
  4. LaunchMyStore renders your block using the Liquid template engine

Block File Structure

Each theme block requires two files:
public/extensions/{domainSlug}/{appHandle}/blocks/
├── product-reviews.aqua          # Liquid template
└── product-reviews.schema.json   # Settings schema

Creating a Theme Block

1. Write the Liquid Template

Theme blocks use .aqua files with standard Liquid syntax:
{% comment %} product-reviews.aqua {% endcomment %}

<div class="app-reviews-widget" data-product-id="{{ product.id }}">
  {% if block.settings.show_title %}
    <h3 class="reviews-title">{{ block.settings.title }}</h3>
  {% endif %}
  
  <div class="reviews-summary">
    {% render 'review-stars', rating: product.metafields.reviews.average %}
    <span class="review-count">
      {{ product.metafields.reviews.count }} reviews
    </span>
  </div>
  
  <div class="reviews-list" id="reviews-{{ product.id }}">
    {% for review in product.metafields.reviews.items limit: block.settings.limit %}
      <div class="review-item">
        <div class="review-header">
          <strong>{{ review.author }}</strong>
          {% render 'review-stars', rating: review.rating %}
        </div>
        <p class="review-body">{{ review.body }}</p>
      </div>
    {% endfor %}
  </div>
  
  {% if block.settings.enable_pagination %}
    <button class="load-more" data-page="2">Load More Reviews</button>
  {% endif %}
</div>

<style>
  .app-reviews-widget {
    padding: {{ block.settings.padding }}px;
    background: {{ block.settings.background_color }};
  }
  .reviews-title {
    color: {{ block.settings.title_color }};
    font-size: {{ block.settings.title_size }}px;
  }
</style>

{% if block.settings.enable_lazy_load %}
  <script src="{{ 'reviews.js' | extension_asset_url }}" defer></script>
{% endif %}

2. Define the Schema

The schema controls what settings appear in the theme editor:
{
  "name": "Product Reviews",
  "target": "product",
  "settings": [
    {
      "type": "header",
      "content": "Content"
    },
    {
      "type": "text",
      "id": "title",
      "label": "Section Title",
      "default": "Customer Reviews"
    },
    {
      "type": "checkbox",
      "id": "show_title",
      "label": "Show Title",
      "default": true
    },
    {
      "type": "range",
      "id": "limit",
      "label": "Reviews to Show",
      "min": 3,
      "max": 20,
      "step": 1,
      "default": 5
    },
    {
      "type": "checkbox",
      "id": "enable_pagination",
      "label": "Enable Load More",
      "default": true
    },
    {
      "type": "header",
      "content": "Styling"
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "title_color",
      "label": "Title Color",
      "default": "#333333"
    },
    {
      "type": "range",
      "id": "title_size",
      "label": "Title Size",
      "min": 14,
      "max": 36,
      "step": 2,
      "default": 24,
      "unit": "px"
    },
    {
      "type": "range",
      "id": "padding",
      "label": "Padding",
      "min": 0,
      "max": 60,
      "step": 5,
      "default": 20,
      "unit": "px"
    },
    {
      "type": "header",
      "content": "Advanced"
    },
    {
      "type": "checkbox",
      "id": "enable_lazy_load",
      "label": "Lazy Load Reviews",
      "default": false,
      "info": "Load reviews via JavaScript for better performance"
    }
  ]
}

Available Targets

The target field determines which page types your block can be added to:
TargetDescriptionAvailable Objects
indexHomepageshop, collections, articles
productProduct pagesproduct, shop, collection
collectionCollection pagescollection, products, shop
cartCart pagecart, shop
articleBlog articlesarticle, blog, shop
pageCustom pagespage, shop
blogBlog listingblog, articles, shop
searchSearch resultssearch, results, shop
You can target multiple page types by specifying an array: "target": ["product", "collection"]

Setting Types

Basic Inputs

{
  "type": "text",
  "id": "heading",
  "label": "Heading Text",
  "default": "Welcome",
  "placeholder": "Enter heading..."
}
{
  "type": "textarea",
  "id": "description",
  "label": "Description",
  "default": "",
  "placeholder": "Enter description..."
}
{
  "type": "number",
  "id": "columns",
  "label": "Number of Columns",
  "default": 3
}

Selection Inputs

{
  "type": "select",
  "id": "layout",
  "label": "Layout Style",
  "options": [
    { "value": "grid", "label": "Grid" },
    { "value": "list", "label": "List" },
    { "value": "carousel", "label": "Carousel" }
  ],
  "default": "grid"
}
{
  "type": "radio",
  "id": "alignment",
  "label": "Text Alignment",
  "options": [
    { "value": "left", "label": "Left" },
    { "value": "center", "label": "Center" },
    { "value": "right", "label": "Right" }
  ],
  "default": "left"
}

Range & Toggle

{
  "type": "range",
  "id": "opacity",
  "label": "Opacity",
  "min": 0,
  "max": 100,
  "step": 5,
  "default": 100,
  "unit": "%"
}
{
  "type": "checkbox",
  "id": "show_badge",
  "label": "Show Badge",
  "default": true,
  "info": "Display a badge on featured items"
}

Visual Pickers

{
  "type": "color",
  "id": "accent_color",
  "label": "Accent Color",
  "default": "#007bff"
}
{
  "type": "color_background",
  "id": "background",
  "label": "Background",
  "default": "linear-gradient(180deg, #ffffff 0%, #f5f5f5 100%)"
}
{
  "type": "image_picker",
  "id": "banner_image",
  "label": "Banner Image"
}

Resource Pickers

{
  "type": "product",
  "id": "featured_product",
  "label": "Featured Product"
}
{
  "type": "collection",
  "id": "source_collection",
  "label": "Source Collection"
}
{
  "type": "url",
  "id": "link",
  "label": "Link URL"
}

Installing Theme Blocks

Install blocks when your app is installed on a store:
const response = await fetch('https://store.launchmystore.io/api/apps/install-extensions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${accessToken}`
  },
  body: JSON.stringify({
    domainSlug: 'merchant-store',
    appHandle: 'my-reviews-app',
    blocks: [
      {
        handle: 'product-reviews',
        template: `{% comment %}Your Liquid template{% endcomment %}...`,
        schema: {
          name: 'Product Reviews',
          target: 'product',
          settings: [/* ... */]
        }
      }
    ],
    snippets: [
      {
        handle: 'review-stars',
        template: `{% for i in (1..5) %}...{% endfor %}`
      }
    ],
    assets: [
      {
        filename: 'reviews.js',
        content: '// Your JavaScript...'
      },
      {
        filename: 'reviews.css',
        content: '/* Your styles */'
      }
    ]
  })
});

Extension Assets

Reference your extension’s assets using the extension_asset_url filter:
<link rel="stylesheet" href="{{ 'reviews.css' | extension_asset_url }}">
<script src="{{ 'reviews.js' | extension_asset_url }}"></script>
<img src="{{ 'logo.png' | extension_asset_url }}" alt="App Logo">

Snippets

Create reusable components as snippets:
{% comment %} snippets/review-stars.aqua {% endcomment %}

<div class="star-rating" aria-label="{{ rating }} out of 5 stars">
  {% assign full_stars = rating | floor %}
  {% assign half_star = rating | modulo: 1 | round %}
  {% assign empty_stars = 5 | minus: full_stars | minus: half_star %}
  
  {% for i in (1..full_stars) %}
    <span class="star star--full"></span>
  {% endfor %}
  
  {% if half_star == 1 %}
    <span class="star star--half"></span>
  {% endif %}
  
  {% for i in (1..empty_stars) %}
    <span class="star star--empty"></span>
  {% endfor %}
</div>
Then render the snippet from your block:
{% render 'review-stars', rating: 4.5 %}

Accessing Data

Block Settings

Access settings via block.settings:
{{ block.settings.title }}
{{ block.settings.show_title }}
{{ block.settings.background_color }}

Global Objects

Standard Shopify global objects are available:
{{ shop.name }}
{{ product.title }}
{{ customer.email }}
{{ cart.item_count }}

Metafields

Access your app’s metafields:
{{ product.metafields.my_app.rating }}
{{ shop.metafields.my_app.settings | json }}

Best Practices

Avoid complex logic in Liquid. Pre-compute values in your backend and store them in metafields.
Use proper heading levels, ARIA labels, and semantic elements for accessibility.
Prefix class names with your app handle to avoid style conflicts with the theme.
Use defer or dynamic imports for JavaScript to avoid blocking page render.
Respect the merchant’s theme colors and fonts where possible. Use CSS custom properties.
Test your blocks with multiple themes to ensure compatibility with different layouts.
A complete example showing a featured products carousel:
<div class="app-featured-products" data-app="{{ block.settings.app_id }}">
  <div class="featured-header">
    {% if block.settings.show_title %}
      <h2 class="featured-title">{{ block.settings.title }}</h2>
    {% endif %}
    {% if block.settings.show_subtitle %}
      <p class="featured-subtitle">{{ block.settings.subtitle }}</p>
    {% endif %}
  </div>
  
  <div class="featured-grid" style="--columns: {{ block.settings.columns }}">
    {% assign collection = collections[block.settings.collection] %}
    {% for product in collection.products limit: block.settings.limit %}
      <div class="featured-product">
        <a href="{{ product.url }}">
          <img 
            src="{{ product.featured_image | image_url: width: 400 }}" 
            alt="{{ product.title }}"
            loading="lazy"
          >
          <h3>{{ product.title }}</h3>
          <p class="price">{{ product.price | money }}</p>
        </a>
        <button class="add-to-cart" data-variant="{{ product.first_available_variant.id }}">
          Add to Cart
        </button>
      </div>
    {% endfor %}
  </div>
</div>

<style>
  .app-featured-products {
    padding: {{ block.settings.padding }}px 0;
  }
  .featured-grid {
    display: grid;
    grid-template-columns: repeat(var(--columns), 1fr);
    gap: 20px;
  }
  @media (max-width: 768px) {
    .featured-grid {
      grid-template-columns: repeat(2, 1fr);
    }
  }
</style>