All Tutorials

Add pickup availability to product pages

All Tutorials

Add pickup availability to product pages

Add pickup availability to product pages

The pickup availability feature lets you display whether a product is available for local pickup on a product page. Customers can use this information to learn if a product available for pickup nearby without adding it to the cart.

When the customer lands on a product page, the customer IP address is used to sort the list of locations in the back-end. Fallback to alphabetical sorting is used if the location cannot be determined based on the IP address. There is also an option to sort based on proximity to the customer based on a location input field.

This tutorial is intended as an example of implementation of the pickup feature.

For more information about the implementation of pickup availability on your Shopify shop, refer to the following API references:

To implement this feature you must perform the following steps:

  1. Create a store-availability.liquid file.
  2. Add the pickup availability container to your product page.
  3. Add the StoreAvailability function to the theme.js file.
  4. Add CSS styling to the theme.css file.

Create the store-availability.liquid file

  1. In the Sections directory, click add a new section and name it store-availability.liquid

  2. Paste the following code into the file:

    <div class="store-availability-container">
      {%- assign pick_up_availabilities = product_variant.store_availabilities | where: 'pick_up_enabled', true -%}
      {%- if pick_up_availabilities.size > 0 -%}
        <div class="store-availability-information">
          {%- assign closest_location = pick_up_availabilities.first -%}
            {%- if closest_location.available -%} {% render 'icon-in-stock' %} {%- else -%} {% render 'icon-out-of-stock' %} {%- endif -%}
            <div class="store-availability-information-container">
              {%- if closest_location.available -%}
                <p class="store-availability-information__title">
                  {{ 'store_availability.general.pick_up_available_at_html' | t: location_name: closest_location.location.name }}
                </p>
                <p class="store-availability-information__stock store-availability-small-text">
                  {{ closest_location.pick_up_time }}
                </p>
                <button class="store-availability-information__button js-modal-open-store-availability-modal store-availability-small-text" data-store-availability-modal-open aria-haspopup="dialog">
                  {%- if pick_up_availabilities.size == 1 -%}
                    {{ 'store_availability.general.view_store_info' | t }}
                  {%- else -%}
                    {{ 'store_availability.general.check_other_stores' | t }}
                  {%- endif -%}
                </button>
              {%- else -%}
                <p class="store-availability-information__title">
                  {{ 'store_availability.general.pick_up_unavailable_at_html' | t: location_name: closest_location.location.name }}
                </p>
                {%- if pick_up_availabilities.size > 1 -%}
                  <button class="store-availability-information__button js-modal-open-store-availability-modal store-availability-small-text" data-store-availability-modal-open aria-haspopup="dialog">
                    {{ 'store_availability.general.check_other_stores' | t }}
                  </button>
                {%- endif -%}
            {%- endif -%}
            </div>
    
        </div>
        <div
          class="store-availabilities-modal modal"
          id="StoreAvailabilityModal"
          role="dialog"
          aria-modal="true"
          aria-labelledby="StoreAvailabilitiesModalProductTitle"
        >
          <div class="store-availabilities-modal__header">
            <div class="store-availabilities-modal__product-information">
              <h2
                id="StoreAvailabilitiesModalProductTitle"
                class="store-availabilities-modal__product-title"
                data-store-availability-modal-product-title
              >
              </h2>
              <p
                class="store-availabilities-modal__variant-title store-availability-small-text"
                data-store-availability-modal-variant-title
              >
                {{ product_variant.title }}
              </p>
            </div>
            <button
              type="button"
              class="store-availabilities-modal__close js-modal-close-store-availability-modal text-link"
              aria-label="{{ 'general.accessibility.close_modal' | t }}"
            >
              {% render 'icon-close' %}
            </button>
          </div>
          <ul class="store-availabilities-list" role="list">
            {%- for availability in pick_up_availabilities -%}
              <li class="store-availability-list__item">
                <h3 class="store-availability-list__location">
                  {{availability.location.name}}
                </h3>
                <div class="store-availability-list__stock store-availability-small-text">
                  {%- if availability.available -%}
                    {% render 'icon-in-stock' %} {{ 'store_availability.general.pick_up_available' | t }}, {{ availability.pick_up_time | downcase }}
                  {%- else -%}
                    {% render 'icon-out-of-stock' %} {{ 'store_availability.general.pick_up_currently_unavailable' | t }}
                  {%- endif -%}
                </div>
                {%- assign address = availability.location.address -%}
                <address class="store-availability-list__address">
                  {{ address | format_address }}
                </address>
                {%- if address.phone.size > 0 -%}
                  <p class="store-availability-list__phone store-availability-small-text">
                    {{ address.phone }}<br>
                  </p>
                {%- endif -%}
              </li>
            {%- endfor -%}
          </ul>
        </div>
      {%- endif -%}
    </div>
    
    {% schema %}
    {
      "name": {},
      "settings": []
    }
    {% endschema %}
  3. Click Save.

Add the pickup availability container to the product page

  1. In the Sections directory, go to the product.liquid file.

  2. Find where you would like the pickup availability options to appear and paste the following code:

    <div
      class="product-single__store-availability-container"
      data-store-availability-container
      data-product-title="{{ product.title | escape }}"
      data-has-only-default-variant="{{ product.has_only_default_variant }}"
      data-base-url="{{ shop.url }}{{ routes.root_url }}"
    >
    </div>
  3. Click Save.

Add the StoreAvailability function

  1. In the Assets directory, go to the Theme.js file.

  2. At the bottom of the file, paste the following code:

    theme.StoreAvailability = (function() {
      var selectors = {
        storeAvailabilityModalOpen: '[data-store-availability-modal-open]',
        storeAvailabilityModalProductTitle: '[data-store-availability-modal-product-title]',
    storeAvailabilityModalVariantTitle: '[data-store-availability-modal-variant-title]'
      };
    
      var classes = {
        hidden: 'hide'
      };
    
      function StoreAvailability(container) {
        this.container = container;
        this.productTitle = this.container.dataset.productTitle;
        this.hasOnlyDefaultVariant =
        this.container.dataset.hasOnlyDefaultVariant === 'true';
      }
    
      StoreAvailability.prototype = Object.assign({},   StoreAvailability.prototype, {
        updateContent: function(variantId) {
          var variantSectionUrl =
          this.container.dataset.baseUrl +
          '/variants/' +
          variantId +
          '/?section_id=store-availability';
          var self = this;
    
          var storeAvailabilityModalOpen = self.container.querySelector(
            selectors.storeAvailabilityModalOpen
          );
    
          this.container.style.opacity = 0.5;
          if (storeAvailabilityModalOpen) {
            storeAvailabilityModalOpen.disabled = true;
            storeAvailabilityModalOpen.setAttribute('aria-busy', true);
          }
    
          fetch(variantSectionUrl)
          .then(function(response) {
            return response.text();
          })
          .then(function(storeAvailabilityHTML) {
            if (storeAvailabilityHTML.trim() === '') {
              return;
            }
          self.container.innerHTML = storeAvailabilityHTML;
          self.container.innerHTML = self.container.firstElementChild.innerHTML;
          self.container.style.opacity = 1;
    
          // Need to query this again because we updated the DOM
          storeAvailabilityModalOpen = self.container.querySelector(
            selectors.storeAvailabilityModalOpen
          );
    
          if (!storeAvailabilityModalOpen) {
            return;
          }
    
          storeAvailabilityModalOpen.addEventListener(
            'click',
            self._onClickModalOpen.bind(self)
          );
    
          self.modal = self._initModal();
          self._updateProductTitle();
          if (self.hasOnlyDefaultVariant) {
            self._hideVariantTitle();
          }
        });
      },
    
      clearContent: function() {
        this.container.innerHTML = '';
      },
    
      _onClickModalOpen: function() {
        this.container.dispatchEvent(
          new CustomEvent('storeAvailabilityModalOpened', {
            bubbles: true,
            cancelable: true
          })
        );
      },
    
      _initModal: function() {
        return new window.Modals(
          'StoreAvailabilityModal',
          'store-availability-modal',
          {
            close: '.js-modal-close-store-availability-modal',
            closeModalOnClick: true,
            openClass: 'store-availabilities-modal--active'
          }
        );
      },
    
      _updateProductTitle: function() {
        var storeAvailabilityModalProductTitle = this.container.querySelector(
          selectors.storeAvailabilityModalProductTitle
        );
        storeAvailabilityModalProductTitle.textContent = this.productTitle;
      },
    
      _hideVariantTitle: function() {
        var storeAvailabilityModalVariantTitle = this.container.querySelector(
          selectors.storeAvailabilityModalVariantTitle
        );
        storeAvailabilityModalVariantTitle.classList.add(classes.hidden);
      }
    });
    
    return StoreAvailability;
    })();
  3. Click Save.

Add CSS for the product page container

  1. In the Assets directory, go to the Theme.css file.

  2. At the bottom of the file, paste the following code:

      .store-availability-small-text {
        font-size: calc(((var(--font-size-base) - 2) / (var(--font-size-base))) * 1em);
      }
    
      .store-availability-information {
        display: flex;
        margin-left: 5px;
      }
    
      .store-availability-information-container {
        margin-left: 5px;
      }
    
      .store-availability-information__title {
        margin-bottom: 0;
      }
    
      .store-availability-information__stock {
        margin-bottom: 0;
      }
    
      .store-availability-information__button {
        text-decoration: underline;
        cursor: pointer;
        border: none;
        padding: 0;
        background: transparent;
        margin-top: 8px;
      }
    
      .store-availability-container .icon {
        width: 12px;
        height: 12px;
        margin: 4px 4px 0 0;
      }
    
      .store-availability-container .icon-in-stock {
        fill: #00730B;
      }
    
      .store-availability-container .icon-out-of-stock {
        fill: #DD2200;
      }
    
      .store-availability-container .icon-close {
        width: 18px;
        height: 18px;
        margin: 0;
      }
    
      .store-availability-container .store-availability-list__stock .icon {
        margin: 0 2px 3px 0;
      }
    
      .store-availability-container .store-availability-list__confirm-address .icon-unverified-address {
        margin: 0 0 3px 0;
      }
      .store-availability-container .store-availability-list__confirm-address .icon-unverified-address .icon-unverified-address__exclamation {
        fill: var(--color-body-text);
      }
      .store-availability-container .store-availability-list__confirm-address .icon-unverified-address .icon-unverified-address__circle {
        stroke: var(--color-body-text);
        fill: transparent;
      }
    
      .store-availabilities-modal {
        z-index: 3;
        width: 375px;
        left: auto;
        border: 1px solid var(--color-border);
        box-sizing: border-box;
        box-shadow: -4px 4px 6px rgba(58, 58, 58, 0.04);
      }
    
      .store-availabilities-modal--active {
        display: flex;
        flex-direction: column;
        transform: translateY(0);
        opacity: 1;
      }
      @media only screen and (max-width: 749px) {
        .store-availabilities-modal--active {
          width: 100%;
          height: 100%;
          overflow: hidden;
          z-index: 9999;
          }
        }
    
        .store-availabilities-modal__header {
          display: flex;
          justify-content: space-between;
          margin: 16px 0 16px 0;
          padding: 0 24px 0 24px;
        }
    
        .store-availabilities-modal__product-title {
          font-size: calc(((var(--font-size-base) + 2) / (var(--font-size-base))) * 1em);
          text-transform: none;
          margin-bottom: 0;
          letter-spacing: 0;
        }
    
        .store-availabilities-list {
          overflow-y: auto;
          padding: 0 20px 0 20px;
        }
    
        .store-availabilities-modal__close {
          margin: -14px -14px 0 0;
          padding: 14px;
          align-self: start;
        }
    
        .store-availability-list__item {
          padding-bottom: 16px;
        }
        .store-availability-list__item::before {
          content: "";
          display: block;
          margin: 0 0 16px 0;
          border-bottom: 1px solid var(--color-border);
        }
    
        .store-availability-list__address {
          font-size: calc(((var(--font-size-base) - 2) / (var(--font-size-base))) * 1em);
          font-style: normal;
          margin-bottom: 0;
        }
    
        .store-availability-list__location {
          font-size: var(--font-size-base);
          margin-bottom: 8px;
        }
    
        .store-availability-list__stock {
          margin-top: 8px;
          margin-bottom: 16px;
        }
    
        .store-availability-list__phone {
          margin-bottom: 0;
        }
  1. Click Save.