I trust that you found this blog post to be enjoyable. If you are interested in having my team handle your eCommerce setup and marketing for you, Contact Us Click here.

Get a Sample Button for Sample Checkout

Shopify Sample Button

Offering shopify product samples is a proven way to build trust, improve conversions, and reduce returns. In this guide, we’ll walk through how to add a “Get a Sample” button to products that triggers a separate checkout flow just for samples — all without using third-party apps.

What This Guide Covers

By the end of this tutorial, your Shopify store will support:

  • “Get a Sample” button on eligible products
  • A modal popup that displays selected sample items
  • Dedicated sample-only checkout (your main cart stays untouched)
  • Cart auto-clears when returning from Shopify sample checkout
  • Support for a sample limit (e.g. 5 items max)
  • No apps required — built with custom JS + Liquid

Requirements

  • Shopify store using an Online Store 2.0 theme (e.g., Dawn).
  • Access to theme code (via Admin > Online Store > Themes > Edit Code).

 

 

1. Create a Snippet : snippets/GetASample.liquid

 

Paste the following code into a new snippet:

 

<!-- Snippet/GetASample.liquid -->

<style>

 .btn--get-sample {

  --bg: var(--button-primary-background, #1c1c1c);

  --fg: var(--button-primary-text, #ffffff);

  display: block;

  width: 100%;

  padding: 13px;

  border: 1px solid var(--bg);

  background: transparent;

  color: var(--bg);

  font-size: 15px;

  font-weight: 500;

  text-transform: uppercase;

  letter-spacing: 0.04em;

  border-radius: 4px;

  cursor: pointer;

  text-align: center;

  text-decoration: none;

  transition: all 0.25s ease;

}


.btn--get-sample:hover,

.btn--get-sample:focus {

  background: var(--bg);

  color: var(--fg);

}


/* Modal Container */

#sample-modal {

  position: fixed;

  left: 0;

  right: 0;

  bottom: 0;

  display: none;

  z-index: 9999;

  background: #f6f6f6;

  border-top: 1px solid #ccc;

  box-shadow: 0 -4px 10px rgba(0, 0, 0, 0.1);

  transform: translateY(100%);

  opacity: 0;

  transition: transform 0.35s ease-out, opacity 0.35s ease-out;

  max-height: 90vh;

  overflow-y: auto;

}


#sample-modal.active {

  display: flex;

  flex-direction: column;

  transform: translateY(0%);

  opacity: 1;

}


.sample-modal-inner {

  width: 100%;

  padding: 20px;

  position: relative;

}


/* Close Button */

.sample-close {

  position: absolute;

  top: 10px;

  right: 15px;

  background: transparent;

  border: none;

  font-size: 24px;

  cursor: pointer;

  color: #555;

}


/* Header */

.sample-header {

  margin-bottom: 20px;

}


.sample-header h3 {

  font-size: 18px;

  margin: 0 0 5px;

}


.sample-header p {

  font-size: 14px;

  color: #333;

  margin: 0;

}


/* Sample List */

.sample-list {

  display: flex;

  gap: 20px;

  padding: 0 20px 20px;

  overflow-x: auto;

  scrollbar-width: none; /* Firefox */

  -ms-overflow-style: none; /* IE 10+ */

}


.sample-list::-webkit-scrollbar {

  display: none; /* Chrome/Safari */

}


/* Sample Cards */

.sample-card {

  flex: 0 0 auto;

  width: 120px;

  text-align: center;

  position: relative;

  background: #fff;

  border: 1px solid #ddd;

  border-radius: 4px;

  padding: 10px;

  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);

}


.sample-card img {

  width: 100%;

  height: 80px;

  object-fit: cover;

  border-radius: 4px;

}


.sample-card-title {

  margin-top: 8px;

  font-size: 13px;

  color: #333;

  line-height: 1.4;

}


.sample-card-remove {

  position: absolute;

  top: 6px;

  right: 6px;

  border: none;

  background: transparent;

  font-size: 16px;

  cursor: pointer;

  color: #999;

}


.sample-card-remove:hover {

  color: #000;

}


/* Footer */

.sample-footer {

  display: flex;

  justify-content: space-between;

  align-items: center;

  padding: 15px 20px;

  border-top: 1px solid #ccc;

  background: #f6f6f6;

}


.sample-clear {

  background: transparent;

  border: none;

  font-size: 14px;

  text-decoration: underline;

  cursor: pointer;

  color: #333;

}


.sample-checkout-btn {

  background: #000;

  color: #fff;

  padding: 12px 24px;

  border: none;

  border-radius: 4px;

  text-decoration: none;

  font-weight: 600;

  font-size: 14px;

  transition: background 0.3s ease;

}


.sample-checkout-btn:hover {

  background: #333;

}



  .custom-loader-overlay {

    position: fixed;

    inset: 0;

    background: rgba(255, 255, 255, 0.85);

    z-index: 9999;

    display: flex;

    align-items: center;

    justify-content: center;

  }

  .custom-loader-spinner {

    border: 4px solid #ccc;

    border-top: 4px solid #000;

    border-radius: 50%;

    width: 36px;

    height: 36px;

    animation: spin 0.8s linear infinite;

  }

  @keyframes spin {

    to { transform: rotate(360deg); }

  }

</style>


{%- assign sample_product = product.metafields.custom.product.value -%}

{%- assign sample_image_url = sample_product.images.first | json -%}

<a class="btn--get-sample"

   id="GetSample-{{ sample_product.id }}"

   href="#"

   data-sample-variant="{{ sample_product.first_available_variant.id }}"

   data-original-product="{{ product.id }}"

   data-product-title="{{ sample_product.title | escape }}"

   data-sample-image-url={{ sample_image_url }}>

  Get&nbsp;a&nbsp;Sample

</a>

 

Style and modal markup are also included in the same snippet or embedded via separate assets.

 

2. Add Modal Markup to footer.liquid

Place this at the bottom of your theme:

 

<div id="sample-modal" class="sample-modal" style="display: none;">

  <div class="sample-modal-inner">

    <button class="sample-close" onclick="closeSampleModal()">×</button>

    <div class="sample-header">

      <h3>SAMPLES</h3>

      <p><strong>Feel the Quality Before You Commit</strong><br>

      See, touch, and feel the perfect tiles for your project.</p>

    </div>

    <div id="sample-list" class="sample-list"></div>

    <div class="sample-footer">

  <button class="sample-clear">Clear All</button>

  <a href="#" class="sample-checkout-btn">Checkout</a>

</div>

  </div>

</div>

 

 

3. Create JavaScript File : assets/sample_product_btn.js

Go to the Assets folder, choose 'Create a blank file', then enter the file name and extension.

 

Upload a new JS asset with the following script, or paste your custom version if already configured:

 


(function () {

  /* ---------- CONSTANTS ---------- */

  const LS_SAMPLES   = 'sample_products';     // stores current sample list

  const MAX_SAMPLES  = 5;

  const CHECKOUT_KEY = 'in_sample_checkout';  // flag to detect return from checkout


  /* ---------- LOCAL STORAGE HELPERS ---------- */

  const getSamples = () => JSON.parse(localStorage.getItem(LS_SAMPLES) || '[]');

  const saveSamples = (arr) => localStorage.setItem(LS_SAMPLES, JSON.stringify(arr));


  /* ---------- UTILITY ---------- */

  const normalizeURL = src =>

    !src ? '' : src.startsWith('http') ? src : src.startsWith('//') ? 'https:' + src : src;


  /* ---------- CART COUNT BUBBLE ---------- */

  const updateCartCountBubble = () => {

    fetch('/cart.js')

      .then(r => r.json())

      .then(cart => {

        const bubble = document.querySelector('.cart-count-bubble');

        if (!bubble) return;

        const total = cart.items.reduce((sum, i) => sum + i.quantity, 0);

        bubble.innerHTML = `

          <span aria-hidden="true">${total}</span>

          <span class="visually-hidden">${total} ${total === 1 ? 'item' : 'items'}</span>`;

      })

      .catch(console.error);

  };


  /* ---------- MODAL OPEN/CLOSE ---------- */

  const openModal = () => {

    const m = document.getElementById('sample-modal');

    if (m) { m.style.display = 'flex'; requestAnimationFrame(() => m.classList.add('active')); }

  };

  const closeModal = () => {

    const m = document.getElementById('sample-modal');

    if (m) { m.classList.remove('active'); setTimeout(() => m.style.display = 'none', 350); }

  };

  window.closeSampleModal = closeModal;


  /* ---------- RENDER SAMPLE LIST ---------- */

  const renderSampleList = () => {

    const list = document.getElementById('sample-list');

    const go   = document.querySelector('.sample-checkout-btn');

    if (!list || !go) return;


    const samples = getSamples();

    list.innerHTML = '';

    if (!samples.length) { go.href = '#'; go.classList.add('disabled'); return; }

    go.classList.remove('disabled');


    samples.forEach(s =>

      list.insertAdjacentHTML('beforeend', `

        <div class="sample-card">

          <button class="sample-card-remove" data-v="${s.variantId}" aria-label="Remove ${s.title}">×</button>

          <img loading="lazy" src="${s.image}" alt="${s.title}">

          <div class="sample-card-title">${s.title}</div>

        </div>`));


    go.href = `/cart/${samples.map(s => `${s.variantId}:1`).join(',')}`;

  };


  /* ---------- “GET SAMPLE” BUTTON CLICK ---------- */

  document.addEventListener('click', e => {

    const btn = e.target.closest('.btn--get-sample');

    if (!btn) return;


    e.preventDefault();


    const variantId = btn.dataset.sampleVariant?.trim();

    if (!variantId) {                  

      console.warn(' No sample variant on button – click ignored', btn);

      return;

    }


    let fullProd = Number((btn.dataset.originalProduct || '').split('/Product/').pop() || 0);


    /* -- Remove any full‑size product in cart -- */

    if (fullProd) {

      fetch('/cart.js')

        .then(r => r.json())

        .then(cart => Promise.all(

          cart.items

            .filter(i => i.product_id === fullProd)

            .map(i => fetch('/cart/change.js', {

              method : 'POST',

              headers: { 'Content-Type': 'application/json' },

              body   : JSON.stringify({ id: i.key, quantity: 0 })

            }))

        ).then(updateCartCountBubble));

    }


    /* -- Manage samples list -- */

    let samples = getSamples();

    if (samples.some(s => s.variantId === variantId)) { openModal(); renderSampleList(); return; }

    if (samples.length >= MAX_SAMPLES)  { alert(`Max ${MAX_SAMPLES} samples.`); openModal(); renderSampleList(); return; }


    samples.push({

      variantId,

      title : btn.dataset.productTitle,

      image : normalizeURL(btn.dataset.sampleImageUrl)

    });

    saveSamples(samples);

    openModal(); renderSampleList();

  });


  /* ---------- REMOVE/CLEAR IN MODAL ---------- */

  document.addEventListener('click', e => {

    const rm = e.target.closest('.sample-card-remove');

    if (rm) { saveSamples(getSamples().filter(s => s.variantId !== rm.dataset.v)); renderSampleList(); return; }

    if (e.target.closest('.sample-clear')) { localStorage.removeItem(LS_SAMPLES); renderSampleList(); }

  });


  /* ---------- CHECKOUT WITH SAMPLES ---------- */

  document.addEventListener('click', async e => {

    const go = e.target.closest('.sample-checkout-btn');

    if (!go) return;


    e.preventDefault();

    if (go.dataset.locked) return;

    go.dataset.locked = 'true'; go.classList.add('loading');


    const samples = getSamples();

    if (!samples.length) { alert('No samples selected.'); go.dataset.locked = ''; go.classList.remove('loading'); return; }


    localStorage.setItem(CHECKOUT_KEY, 'true');

    await fetch('/cart/clear.js', { method: 'POST' });


    const ids = samples.map(s => `${s.variantId}:1`).join(',');

    window.location.href = `/cart/${ids}?checkout`;

  });


  /* ---------- BACK‑BUTTON HANDLING ---------- */

  window.addEventListener('pageshow', ev => {

    const cameBack = ev.persisted || window.performance?.navigation?.type === 2;

    if (!cameBack) return;


    const fromCheckout = localStorage.getItem(CHECKOUT_KEY) === 'true';

    if (fromCheckout) {

      console.log('↩️ Returned from sample checkout – clearing cart again');

      localStorage.removeItem(CHECKOUT_KEY);

      fetch('/cart/clear.js', { method: 'POST' })

        .finally(() => setTimeout(updateCartCountBubble, 300));

    } else {

      setTimeout(updateCartCountBubble, 100);

    }

  });


  /* ---------- INITIAL BUBBLE REFRESH ---------- */

  document.addEventListener('DOMContentLoaded', updateCartCountBubble);

})();

 

4. Load Script in theme.liquid

Include the JS file before the closing </body> tag:

5. Update main-product.liquid Section

Ensure the <product-info> element has the proper attributes to support sample tracking:

 

<product-info

  id="MainProduct-{{ section.id }}"

  class="section-{{ section.id }}-padding gradient color-{{ section.settings.color_scheme }}"

  data-section="{{ section.id }}"

  data-product-id="{{ product.id }}"

  data-update-url="true"

  data-url="{{ product.url }}"

  {% if section.settings.image_zoom == 'hover' %}

    data-zoom-on-hover

  {% endif %}

  {% assign sample_item = cart.items | where: "product_id", product.id | where: "properties._sample", "true" | first %}

  {% if sample_item %}

    data-sample-variant="{{ sample_item.variant_id }}"

  {% endif %}

>

 

sample-button-render

 

6. Add Sample Button Render Code in buy-buttons.liquid

Find the line with:

{{ form | payment_button }}

 

And replace it with:

{% if product.metafields.custom.product != blank %}

        {% render 'GetASample' %}

  {% endif %}

 

payment-button

 

The “Get a Sample” button only appears on products where a linked sample product is added via the metafield custom.product. This ensures the button is conditionally visible only on eligible products.

 

metafield-custom-product

 

When the “Get a Sample” button is clicked, a modal opens at the bottom of the screen showing the selected sample products. Customers can review, remove, or proceed to checkout directly from this modal

 

 

When the Checkout button is clicked in the sample modal, only the selected sample products are added to the cart and the user is redirected directly to the Shopify checkout page. This ensures a smooth, sample-only checkout flow. The main cart remains untouched.

 

sample-variants

 

Summary

We now have a complete sample product selection flow:

  • Only sample variants can be selected
  • Full-size version is removed automatically
  • Max 5 samples per session
  • Cart is cleared on Shopify sample checkout
  • Cart is not restored after Shopify sample checkout
  • Script and modal integrated cleanly with your theme

 

Conclusion

In this implementation, we added a “Get a Sample” button that lets customers add up to 5 sample products. When the Checkout button is clicked, the cart is fully cleared and only the selected sample variants are sent to checkout. The original cart is not restored after returning, keeping the sample flow completely separate. We also configured <product-info> to support sample tracking, and included the required script and modal setup to provide a smooth, isolated Shopify sample checkout experience.

Frequently Asked Questions

Q1. How does the “Get a Sample” button work ?

A. When clicked, the button adds a sample product (linked via metafield) to a dedicated sample selection modal. Customers can add up to 5 samples, review them, and proceed to a separate checkout — without affecting their main cart.

Q2. Can customers order both full-size and sample products together ?

A. No. The shopify sample checkout flow is completely isolated. If a full-size version of the product is in the cart, it is automatically removed when a sample is selected. Similarly, returning from the shopify sample checkout does not restore the main cart.

Q3. How do I link a sample product to a full-size product ?

A. Use a Shopify metafield (custom.product) on the main product to reference the sample product. The “Get a Sample” button will only appear if a valid linked sample product exists.

Q4. What happens if the user hits the browser's back button after checkout ?

A. The cart is cleared again to prevent accidental merging of the shopify sample checkout with the main cart. This ensures a clean, isolated experience every time.

Q5. Can I limit the number of samples a user selects ?

A. Yes. The system enforces a maximum of 5 samples per session. If the limit is reached, an alert is shown and the user cannot add more until they remove an existing one.

Back to blog

Are you interested in boosting your sales orders?

"PTI WebTech has a proven strategy to boost your sales. Contact us to learn more."

Conact Us

Newsletter

Subscribe our Newsletter for our blog posts and our new updates. Let's stay updated!

Our Latest Post