Ideal House
Skip to content

Shopify developer integration#

Add the ideal.house button to a Shopify theme and map it to the selected Variant SKU.

<!-- DASHBOARD_READINESS -->

Before loading the SDK: Open Dashboard → Integration, add your storefront origin to Allowed Origins (CORS), and click Save. Include the published hostname and any preview or staging origins you use; separate multiple origins with commas. See the configuration steps.

New merchants can apply for a reviewed 15-day free trial. Once approved, sign in to Dashboard, upload and publish products, then return here to install the SDK. When the trial ends, the public Marketplace page remains available and the storefront SDK pauses until the shop subscribes.

1. Confirm prerequisites#

  • You can edit the live theme code or a safe duplicate of the theme.
  • At least one test product is imported and active in Catalog.
  • The selected Variant SKU is the same persisted productCode used by Catalog for this Shop.

2. Create the button snippet#

snippets/idealhouse-view-button.liquid#

liquid
{%- comment -%}
  Renders an ideal.house authored visualizer button.

  Accepts:
  - product_code: {String} Variant SKU used by the ideal.house Catalog mapping.
  - placement: {String} "card", "pdp-desktop", or "pdp-mobile".
{%- endcomment -%}

{%- liquid
  assign button_code = product_code | strip
  assign button_placement = placement | default: 'card'
-%}

<button
  type="button"
  class="idealhouse-view-button idealhouse-view-button--{{ button_placement | escape }}"
  data-idealhouse-authored-button
  data-active="false"
  {% if button_code != blank %}
    data-product-code="{{ button_code | escape }}"
  {% else %}
    disabled
  {% endif %}
  style="visibility: hidden;"
>
  <span>View in my room</span>
</button>

Keep the button hidden until the SDK confirms product eligibility.

3. Render the button in theme surfaces#

Collection and search cards#

liquid
{%- render 'idealhouse-view-button',
  product_code: card_product.selected_or_first_available_variant.sku,
  placement: 'card'
-%}

Common files include snippets/card-product.liquid, snippets/product-card.liquid, or snippets/product-grid-item.liquid. Adapt card_product to the product variable used by the theme.

Product detail page#

liquid
{%- render 'idealhouse-view-button',
  product_code: product.selected_or_first_available_variant.sku,
  placement: 'pdp-desktop'
-%}

{%- render 'idealhouse-view-button',
  product_code: product.selected_or_first_available_variant.sku,
  placement: 'pdp-mobile'
-%}

Common files include sections/main-product.liquid or snippets/product.liquid. Place separate desktop and mobile renders only when the theme has separate layouts.

4. Load the SDK and sync variants#

assets/idealhouse-room-visualizer.js#

js
(function () {
  'use strict';

  var buttonSelector = '[data-idealhouse-authored-button]';
  var sdkRequested = false;
  var buttonObserver = null;

  function resetButton(button) {
    button.dataset.active = 'false';
    button.style.visibility = 'hidden';
    delete button.dataset.rvProductCode;
    delete button.dataset.rvProductType;
    delete button.dataset.rvProductId;
    delete button.dataset.rvProductIds;
  }

  function syncButton(button, productCode) {
    if (!button) return false;
    var code = typeof productCode === 'string' ? productCode.trim() : '';
    resetButton(button);
    delete button.dataset.productCode;

    if (!code) {
      button.disabled = true;
      return false;
    }

    button.disabled = false;
    button.dataset.productCode = code;
    return true;
  }

  function syncButtons(container, productCode) {
    (container || document).querySelectorAll(buttonSelector).forEach(function (button) {
      syncButton(button, productCode);
    });
  }

  function loadSdk() {
    if (sdkRequested || !document.querySelector(buttonSelector)) return false;
    sdkRequested = true;
    if (buttonObserver) buttonObserver.disconnect();

    var script = document.createElement('script');
    script.src = 'https://sdk.ideal.house/sdk.js';
    script.async = true;
    script.dataset.shopId = '<SHOP_ID>';
    script.dataset.publishableKey = '<PUBLISHABLE_KEY>';
    script.dataset.authoredButtonSelector = buttonSelector;
    document.head.appendChild(script);
    return true;
  }

  function startSdkLoader() {
    if (loadSdk() || typeof MutationObserver === 'undefined') return;
    buttonObserver = new MutationObserver(loadSdk);
    buttonObserver.observe(document.documentElement, { childList: true, subtree: true });
  }

  // Dispatch this event from your theme's variant-change callback.
  document.addEventListener('idealhouse:variant-change', function (event) {
    var detail = event.detail || {};
    syncButtons(detail.container || document, event.detail && event.detail.sku);
  });

  window.IdealhouseShopify = {
    syncButton: syncButton,
    syncButtons: syncButtons
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', startSdkLoader);
  } else {
    startSdkLoader();
  }
}());

Shopify does not define one universal theme variant event. Connect the small adapter below to the event or callback exposed by the active theme.

Theme event adapter example#

Replace the example event with the active theme API. Some themes publish through window.subscribe; others dispatch a DOM event or update product-info.

js
// Example only: replace "variant:change" with your theme's event.
document.addEventListener('variant:change', function (event) {
  var variant = event.detail && event.detail.variant;
  var productContainer = event.target.closest('product-info') || document;

  document.dispatchEvent(new CustomEvent('idealhouse:variant-change', {
    detail: {
      container: productContainer,
      sku: variant && variant.sku
    }
  }));
});

5. Add theme styles#

assets/idealhouse-room-visualizer.css#

css
.idealhouse-view-button {
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  padding: 0.75rem 1rem;
  border: 1px solid currentColor;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
}

.idealhouse-view-button[data-active="true"] {
  display: inline-flex;
  visibility: visible;
}

.idealhouse-view-button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

The theme may override these visual styles.

6. Include assets in theme.liquid#

Include the visualizer stylesheet and script in layout/theme.liquid to apply styles and activate the SDK loader.

Include stylesheet (inside <head>)#

liquid
{{ 'idealhouse-room-visualizer.css' | asset_url | stylesheet_tag }}

Place inside the <head> tag of layout/theme.liquid so styles are loaded cleanly.

Include script (before </body>)#

liquid
<script src="{{ 'idealhouse-room-visualizer.js' | asset_url }}" defer="defer"></script>

Use defer and place right before the closing </body> tag in layout/theme.liquid to avoid blocking page rendering.

The 'View in my room' button remains hidden until this loader script is included in theme.liquid.

7. Verify on the live storefront#

  1. The Network panel shows sdk.js loaded with HTTP 200.
  2. The authored button data-product-code exactly matches the Catalog productCode.
  3. Eligible products reveal a button; ineligible products keep it hidden.
  4. Switching Variant updates data-product-code and revalidates the button.
  5. Collection/search, PDP desktop, and PDP mobile placements work where present.
  6. Clicking the button opens the ideal.house Viewer with the selected product.