NORTH JS TECH
Skip to content
North Js Tech
Theme Integration

Liquid Code Examples

Below is a consolidated reference implementation of the Rich Landing Bundle theme app extension, combining the key Liquid, CSS, and JavaScript pieces described in your documentation.

bundle-embed.liquid
liquid
1{% comment %}2  bundle-embed.liquid3  App embed block loaded via body target4{% endcomment %}5 6<style>7  rich-landing-bundle {8    display: block !important;9    position: relative;10  }11  .rlb-embed-inactive-notice {12    display: none !important;13  }14</style>15 16<script>17(function() {18  window.__rlbEmbedEnabled = true;19  var cfg = window.StandaloneBundleConfig = window.StandaloneBundleConfig || {};20  if (!cfg.appProxyPath) cfg.appProxyPath = '{{ shop.url }}/apps/rlb-bundle';21  if (!cfg.shopDomain) cfg.shopDomain = '{{ shop.permanent_domain }}';22  if (!cfg.currencySymbol) cfg.currencySymbol = '{{ cart.currency.symbol }}';23  if (!cfg.currencyCode) cfg.currencyCode = '{{ cart.currency.iso_code }}';24  if (!cfg.moneyFormat) cfg.moneyFormat = {{ shop.money_format | json }};25  if (!cfg.moneyFormatWithCurrency) cfg.moneyFormatWithCurrency = {{ shop.money_with_currency_format | json }};26  if (!cfg.locale) cfg.locale = '{{ request.locale.iso_code }}';27  if (!cfg.countryCode) cfg.countryCode = '{{ localization.country.iso_code }}';28  if (!cfg.availableCountries) {29    cfg.availableCountries = [30      {% for country in localization.available_countries %}31        '{{ country.iso_code }}'{% unless forloop.last %},{% endunless %}32      {% endfor %}33    ];34  }35  if (!cfg.countryCurrencyMap) {36    cfg.countryCurrencyMap = {37      {% for country in localization.available_countries %}38        "{{ country.iso_code }}": "{{ country.currency.iso_code }}"{% unless forloop.last %},{% endunless %}39      {% endfor %}40    };41  }42})();43</script>44 45<script>46(function() {47  var s = window.Shopify;48  if (!s) return;49  var cfg = window.StandaloneBundleConfig || {};50  var map = cfg.countryCurrencyMap || {};51  var activeCurrency = (s.currency && s.currency.active)52    ? s.currency.active53    : cfg.currencyCode;54  var candidateCountry = s.country || cfg.countryCode;55  if (activeCurrency && map[candidateCountry]56      && map[candidateCountry] !== activeCurrency) {57    for (var cc in map) {58      if (map[cc] === activeCurrency) {59        candidateCountry = cc;60        break;61      }62    }63  }64  cfg.countryCode = candidateCountry;65  if (activeCurrency) cfg.currencyCode = activeCurrency;66  if (s.locale) cfg.locale = s.locale;67})();68</script>69 70<script>71(function() {72  if (window.__bundleEmbedLoaded) return;73  window.__bundleEmbedLoaded = true;74 75  var cssUrl = '{{ 'rlb-bundle.css' | asset_url }}';76  var scripts = [77    '{{ 'bundle-globals.js' | asset_url }}',78    '{{ 'rlb-bundle-style.js' | asset_url }}',79    '{{ 'rlb-bundle.js' | asset_url }}'80  ];81 82  function loadAssets() {83    if (!document.querySelector('link[href="' + cssUrl + '"]')) {84      var link = document.createElement('link');85      link.rel = 'stylesheet';86      link.href = cssUrl;87      link.crossOrigin = 'anonymous';88      document.head.appendChild(link);89    }90 91    function loadScript(idx) {92      if (idx >= scripts.length) return;93      var src = scripts[idx];94      if (document.querySelector('script[src="' + src + '"]')) {95        loadScript(idx + 1);96        return;97      }98      var el = document.createElement('script');99      el.src = src;100      el.crossOrigin = 'anonymous';101      el.onload = function() { loadScript(idx + 1); };102      document.head.appendChild(el);103    }104 105    loadScript(0);106  }107 108  loadAssets();109})();110</script>
rich-landing-bundle-block.liquid
liquid
1{% comment %}2  rich-landing-bundle-block.liquid3  Section block with optimized metafield path4{% endcomment %}5 6{% assign bundles_config = shop.metafields.bundle_app.bundles_config.value %}7{% assign bundle_data = bundles_config.bundles[block.settings.bundle_id] %}8 9{% if request.design_mode %}10  {{ 'bundle-globals.js' | asset_url | preload_tag: as: 'script', fetchpriority: 'high', crossorigin: 'anonymous' }}11  {{ 'rlb-bundle-style.js' | asset_url | preload_tag: as: 'script', fetchpriority: 'high', crossorigin: 'anonymous' }}12  {{ 'rlb-bundle.js' | asset_url | preload_tag: as: 'script', fetchpriority: 'high', crossorigin: 'anonymous' }}13  <script fetchpriority="high" defer src="{{ 'bundle-globals.js' | asset_url }}" crossorigin="anonymous"></script>14  <script fetchpriority="high" defer src="{{ 'rlb-bundle-style.js' | asset_url }}" crossorigin="anonymous"></script>15  <script fetchpriority="high" defer src="{{ 'rlb-bundle.js' | asset_url }}" crossorigin="anonymous"></script>16{% endif %}17 18{% if bundle_data and bundle_data.isActive %}19  {%- comment -%} Optimized path: render with template hint {%- endcomment -%}20  <rich-landing-bundle21    data-bundle-id="{{ block.settings.bundle_id }}"22    data-template="{{ bundle_data.template }}"23    data-init-fn="initRlbBundle"24    {% if request.design_mode %}data-design-mode{% endif %}25    {{ block.shopify_attributes }}26  >27    <!-- Skeleton HTML for loading state -->28    <div class="rlb-skeleton">29      <div class="rlb-skeleton-header"></div>30      <div class="rlb-skeleton-body"></div>31    </div>32  </rich-landing-bundle>33{% else %}34  {%- comment -%} Fallback path: render without template hint {%- endcomment -%}35  <rich-landing-bundle36    data-bundle-id="{{ block.settings.bundle_id }}"37    data-init-fn="initRlbBundle"38    {% if request.design_mode %}data-design-mode{% endif %}39    {{ block.shopify_attributes }}40  >41    <div class="rlb-skeleton">42      <div class="rlb-skeleton-header"></div>43      <div class="rlb-skeleton-body"></div>44    </div>45  </rich-landing-bundle>46{% endif %}47 48<script>49(function(){50  var c = window.StandaloneBundleConfig;51  var id = {{ block.settings.bundle_id | json }};52  if (!c || !id) return;53  var u = c.appProxyPath + '/config/' + id;54  var params = [];55  if (c.countryCode) params.push('country=' + c.countryCode);56  if (c.locale) params.push('locale=' + c.locale);57  if (params.length) u += '?' + params.join('&');58  window.__bundlePrefetch = window.__bundlePrefetch || {};59  window.__bundlePrefetch[id] = fetch(u, {60    method: 'GET',61    headers: {62      'Accept': 'application/json',63      'Content-Type': 'application/json'64    }65  }).then(function(r) { return r.json(); });66})();67</script>68 69{% schema %}70{71  "name": "Rich Landing Bundle",72  "target": "section",73  "settings": [74    {75      "type": "text",76      "id": "bundle_id",77      "label": "Bundle ID",78      "info": "Enter the bundle ID from your app dashboard. Find this in the app under Bundles > Edit Bundle."79    }80  ]81}82{% endschema %}
bundle-globals.js
javascript
1// bundle-globals.js2// Shared utilities, Preact runtime, and global helpers3 4window.RLB = window.RLB || {};5 6// Example: simple event bus used by bundle widgets7(function(ns) {8  var listeners = {};9 10  ns.on = function(event, cb) {11    (listeners[event] = listeners[event] || []).push(cb);12  };13 14  ns.off = function(event, cb) {15    if (!listeners[event]) return;16    listeners[event] = listeners[event].filter(function(fn) { return fn !== cb; });17  };18 19  ns.emit = function(event, payload) {20    (listeners[event] || []).forEach(function(fn) { fn(payload); });21  };22})(window.RLB);23 24// Money formatting helper using StandaloneBundleConfig25window.RLB.formatMoney = function(cents, opts) {26  var cfg = window.StandaloneBundleConfig || {};27  var format = (opts && opts.withCurrency)28    ? cfg.moneyFormatWithCurrency29    : cfg.moneyFormat;30  if (!format) return (cents / 100).toFixed(2);31  var value = (cents / 100).toFixed(2);32  return format.replace("{{amount}}", value).replace("{{ amount }}", value);33};
rlb-bundle-style.js
javascript
1// rlb-bundle-style.js2// Dynamic style injection based on widget configuration3 4(function() {5  var cfg = window.StandaloneBundleConfig || {};6  var theme = cfg.bundleTheme || {};7 8  var css = "" +9    ":root {" +10    (theme.primaryColor ? "--rlb-primary: " + theme.primaryColor + ";" : "") +11    (theme.secondaryColor ? "--rlb-secondary: " + theme.secondaryColor + ";" : "") +12    (theme.fontFamily ? "--rlb-font-family: " + theme.fontFamily + ";" : "") +13    "}" +14    "rich-landing-bundle { font-family: var(--rlb-font-family, inherit); }";15 16  var styleEl = document.createElement('style');17  styleEl.type = 'text/css';18  styleEl.appendChild(document.createTextNode(css));19  document.head.appendChild(styleEl);20})();
rlb-bundle.js
javascript
1// rlb-bundle.js2// Main widget logic and custom element definition3 4(function() {5  if (window.customElements && customElements.get('rich-landing-bundle')) return;6 7  function fetchConfig(bundleId) {8    var c = window.StandaloneBundleConfig || {};9    if (window.__bundlePrefetch && window.__bundlePrefetch[bundleId]) {10      return window.__bundlePrefetch[bundleId];11    }12    var u = c.appProxyPath + '/config/' + bundleId;13    var params = [];14    if (c.countryCode) params.push('country=' + c.countryCode);15    if (c.locale) params.push('locale=' + c.locale);16    if (params.length) u += '?' + params.join('&');17    return fetch(u, {18      method: 'GET',19      headers: {20        'Accept': 'application/json',21        'Content-Type': 'application/json'22      }23    }).then(function(r) { return r.json(); });24  }25 26  class RichLandingBundle extends HTMLElement {27    constructor() {28      super();29      this.bundleId = this.getAttribute('data-bundle-id');30      this.template = this.getAttribute('data-template');31      this.attachShadow({ mode: 'open' });32    }33 34    connectedCallback() {35      if (!this.bundleId) return;36      this.renderSkeleton();37      var self = this;38      fetchConfig(this.bundleId).then(function(cfg) {39        self.config = cfg;40        self.render();41      }).catch(function() {42        self.renderError();43      });44    }45 46    renderSkeleton() {47      this.shadowRoot.innerHTML = "<style>.rlb-skeleton{opacity:.4}</style><div class='rlb-skeleton'>Loading bundle...</div>";48    }49 50    renderError() {51      this.shadowRoot.innerHTML = "<div class='rlb-error'>Unable to load bundle.</div>";52    }53 54    render() {55      var cfg = this.config || {};56      var items = cfg.items || [];57      var html = "<div class='rlb-bundle'>" +58        "<h3>" + (cfg.title || "Bundle") + "</h3>" +59        "<ul>" +60        items.map(function(it) {61          return "<li>" + it.title + " - " + window.RLB.formatMoney(it.price) + "</li>";62        }).join("") +63        "</ul>" +64        "</div>";65      this.shadowRoot.innerHTML = html;66    }67  }68 69  if (window.customElements) {70    customElements.define('rich-landing-bundle', RichLandingBundle);71  }72})();

Last updated March 23, 2026

Let's talk