Shopify Inventory Sync

Last Authored: Monday, July 24th, 2023

By: KJ Chabra

In this tutorial, we will be exploring how to sync data from Shopify into AWS Dynamo Database and querying the synced data from the Shopify Online Store to display the product/variant inventory count.

For this tutorial, we will be syncing Products and Inventory data only.


Shopify inventory tutorial

Why?

Shopify's Inventory data for boutique locations is not available to use through native functionality such as Liquid or Storefront API. Hence, if your online store manages inventory via Shopify, you can make the inventory data of your physical locations available via REST API. This comes extremely handy for online shoppers, especially when you have BOPIS (Buy Online Pay In Store) enabled.

Why use Project Compound?
  • Project Compound delivers a well tested solution that is self-hosted on your own AWS (all the data stays in your own domain)

  • Additionally it reduces time, effort and nuisances of working with API's that are rate limited

  • You don't need to know how to code or architect software or cloud resources (the guided UI will handle that for you)

Requirements
  1. AWS Account

  2. Shopify Admin Token with read access to Products, Locations and Inventory scopes

  3. Project Compound Account

Who is this for?
  • Ideally any Shopify store (Basic, Shopify for Small Business, Advanced, Starter or Plus plans) that manage inventory of their products via Shopify and have "Fulfill online orders from this location" selected for their boutique locations.

  • If you use Shopify POS for your boutique locations then this tutorial will probably apply to you.

Get in touch via Twitter or Email if you have any questions regarding this tutorial.

End Result
Shopify inventory tutorial
Workflow Diagram

Project Compound - Shopify workflow

Good to Know

The sync of products and inventory from Shopify is made possible via the frequency parameter in the interaction phase. The data can't be updated in real time as there are limitations on how many times you can fetch data.

Read more about Shopify API Rate limitation


Steps
  1. Start by entering data into the Settings stage. Once all the inputs have been entered, the Next button should be clickable which will put you into the Interaction stage.

  2. In the Interaction stage, select Shopify > Shopify Products and fill out the required inputs.
    To retrieve admin token, follow the steps listed on the Shopify Interaction page.
    For Shopify API version, input the value of 2023-01. You can find a list of the applicable version from Shopify's Release Schedule page.

    Shopify Products Integration

  3. Once you have filled out all the fields, the Next button should be enabled. Clicking that button will put you into the Extraction stage.

  4. In the Extraction stage, select Dynamo DB option from the AWS services menu and enter a Table Name.
    Extraction Stage - Shopify Products

  5. Once you have entered the Table Name input, clicking the Next button will put you into a Summary page.

  6. Summary page will list out the interaction and extraction details. Once you are ready, go ahead and click the Deploy button.

  7. Repeat Steps 1-6 and select Shopify > Shopify Inventory in the interaction stage. Ensure that the region in Step 1 selected is the same for this application.

  8. Once both the apps are deployed, feel free to trigger the sync of the data manually.
    Shopify Products Trigger

  9. Finally, we need to deploy another app that would allows us to read data from the database through REST API format.

  10. Create a new application and in the interaction stage select AWS > AWS REST API

  11. Give the interaction a name and add 2 routes. For the first route enter /variants/{variantId} and for the second route enter /inventory/{inventoryItemId}.

    These routes are essentially endpoints that will deliver data once we call them from the Shopify Online store.
    AWS API routes

  12. Once all the data is entered, click the Next button to advance to the Extraction stage.

  13. In the Extraction stage click the AWS > AWS Dynamo DB option and this should display the mappings of the routes declared in the Interaction stage.
    Since databases were already created in the Shopify Products and Shopify Inventory apps, simply select the Table already exists option and enter the table name.
    For the variants route enter the table name entered in Shopify Products application and for the inventory route enter the table name entered in Shopify Inventory application.
    Once the table name has been entered simply click the CHECK button, this will run a verification and ensure that the table indeed exists. If the check passes, the UI will list out the values that could be entered in the routes Table field lookup field.
    REST API route mappingREST API route mapping

  14. Next button will only become enabled once all the CHECK buttons have been clicked.

  15. Clicking the button will place you into the Summary page and if everything looks good, feel free to click the Deploy button.

Shopify
  1. In your Shopify store, ensure your locations have the fulfillment option checked
    Shopify Location fulfillment option

  2. In your Online Theme, click Edit Code
    Edit Code selection

  3. Under Snippets folder click Add a new snippet and name the file as inventory-count.liquid.

  4. Under Assets folder click Add a new asset and select Create a blank file. Choose the js option and name the file as inventory-fetch.js.

  5. Open the inventory-count.liquid file and paste the HTML code listed after the steps. Similarly, open the inventory-fetch.js file and paste the the Javascript code listed after the steps.
    In the Javascript file ensure you replace the apiUrl value with the API value you deployed. You can retrieve the value of the API by opening the Triggers and Testers and copying the text starting with https://.
    Shopify REST API details

  6. Lastly, paste the following line of code in buy-buttons.liquid (at the bottom of the file) to enable the display of the count.
    {% render 'inventory-count' %}


inventory-count.liquid

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 {% comment %} # inventory-count.liquid file Render a table template to pull inventory from DB {% endcomment %} {%- assign pick_up_availabilities = product.selected_or_first_available_variant.store_availabilities | where: 'pick_up_enabled', true -%} {% assign locations = product.selected_or_first_available_variant.store_availabilities | map: 'location' %} {% if locations.size > 0 %} <pickup-inventory-table {% if product.selected_or_first_available_variant.available and pick_up_availabilities.size > 0 %} available {% endif %} data-variant-id="{{ product.selected_or_first_available_variant.id }}" > <table> <thead> <th>Location Name</th> <th>Count</th> <th>Last Updated</th> </thead> {% for location in locations %} <tr> <td>{{ location.name }}</td> <td data-location-id-count="{{ location.id }}"></td> <td data-location-id-updated="{{ location.id }}"></td> </tr> {% endfor %} </table> </pickup-inventory-table> {% endif %} <script src="{{ 'inventory-fetch.js' | asset_url }}" defer="defer"></script>


inventory-fetch.js

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 /* inventory-fetch.js */ // Replace with your Deployed ap API url const apiUrl = "https://your-api.us-east-1.amazonaws.com/prod/v1/"; if (!customElements.get("pickup-inventory-table")) { customElements.define( "pickup-inventory-table", class PickupInventoryTable extends HTMLElement { constructor() { super(); if (!this.hasAttribute("available")) return; this.fetchInventoryCount(this.dataset.variantId); } async fetchInventoryCount(variantId) { const variantUrlFetch = `${apiUrl}variants/${variantId}`; const variantFetch = await fetch(variantUrlFetch, { method: "GET" }); const variantResp = await variantFetch.json(); const variantInventoryItemId = variantResp.Items && variantResp.Items[0] && variantResp.Items[0].inventoryItemId ? variantResp.Items[0].inventoryItemId.S : null; const inventoryUrlFetch = variantInventoryItemId ? `${apiUrl}inventory/${variantInventoryItemId}` : null; const inventoryFetch = inventoryUrlFetch && (await fetch(inventoryUrlFetch, { method: "GET" })); const inventoryResp = inventoryFetch && (await inventoryFetch.json()); if (inventoryResp) { inventoryResp.Items.forEach((item) => { const updatedDate = new Date(item.modifiedAt.S); this.renderDetails(item.locationId.S, "count", item.available.N); this.renderDetails( item.locationId.S, "updated", updatedDate.toLocaleString() ); }); } } renderDetails(locationId, key, value) { const elem = this.querySelector( `[data-location-id-${key}="${locationId}"]` ); if (elem) { elem.innerHTML = `${value}`; } } } ); }