Case Study

InMode eStore

Case Study - Card - InMode eStore Image -- Tablet
Case Study - Card - InMode eStore Logo
Case Study - Card - InMode eStore Image -- Mobile
Case Study - Card - InMode eStore Logo
Case Study - Card - InMode eStore Image -- Tablet
Case Study - Card - InMode eStore Logo
Case Study - Card - InMode eStore Image -- Mobile
Case Study - Card - InMode eStore Logo
Orange Background Rectangle

InMode is the leading global provider of medical aesthetic devices harnessing novel radiofrequency, light and laser-based technologies. They operate globally providing their technologies and products to clinics.

Clinics using InMode technologies and workstations can purchase consumables and marketing materials for their machines via InMode’s permissioned wholesale store. Additionally, clinics can download resources such as photos and videos relating to the technologies and workstations they have via a section of the wholesale store called the Resource Center.

We facilitated the transition from their existing setup on Wordpress to Shopify for both a more scalable admin experience and also a more enjoyable experience for clinics.

  1. Web Store

    • Permissioned web store for approved customers.

  2. Resource Center

    • Permissioned website for viewing and downloading files for approved customers.

  3. Shopify App

    • For specific administration tasks.

Each of these three pieces had their own specific requirements which we will explore in further detail.

By default, Shopify fulfills the standard needs of any web store. However, there are still a number of requirements which it doesn’t fulfill, some of which are surprisingly common. We spent quite some time researching and vetting the abundance of apps which are available in the Shopify app store to see how they could be combined to suit InMode’s requirements. However, due to the scale of InMode’s requirements we ultimately decided to create custom solutions which would work seamlessly with one another.

We also explored Shopify’s wholesale sales channel which is available to Shopify Plus merchants, however we became quickly aware that it was extremely basic and would not fulfill the extensive requirements of InMode, even with our own custom solutions built on top of it.


Instead, we opted to use Shopify’s regular online store sales channel with a custom theme built from scratch to facilitate all of InMode’s requirements such as their in depth permissions system.

From a high level perspective, what was mainly needed on the web store was:

  1. Access only to approved users.

    • New users can request access via a form that would collect their contact information and clinic details.

      • Users already in the InMode ecosystem have unique user IDs which follow a specific pattern based on the user’s country and user type.

  2. Limited product access.

    • User’s are only able to view and purchase products for technologies and workstations they have specific access to.

  3. Purchase limits.

    • Some specific products have single order limits.

    • Products exist as 2 distinct types (consumables and marketing) which have monthly and quarterly purchase limits for specific customer types and roles.

  4. Order approvals.

    • Specific customer types can order products at no cost however each of their orders must be manually reviewed before being dispatched to the fulfillment center.

Limiting access to the store for only approved users was surprisingly easy to implement. A quick overview of how themes are coded in Shopify:

Themes are written in Liquid which is an open source templating language created by Shopify and written in Ruby. All themes follow the same structure:

The layout is the wrapper for the rest of the code. It includes all of the <head> tags and the main <body> tag. It is the same for every page unless otherwise specified (you can have multiple layout files). Within it each template is rendered.

Templates are JSON files for each specific page (e.g. product, collection, blog, etc) which contain references to the sections which are rendered on that page.They also contain the data which will be rendered within the sections.

Sections are where the actual HTML markup of the page lives. Sections include a schema where variable data can be defined. This data is editable by the Shopify store owner via the theme editor. Sections are reusable across all pages and can have unique data on each page.

Snippets are small reusable snippets of code that can be referenced anywhere, including in other snippets.

So, to restrict access to the store to only users that are currently logged in we simply wrote the following in the main layout file:

InMode eStore - Case Study - Code 1

What this bit of code does is check if the user is currently logged in and if they are not we render a script that returns them to the login page instead of the actual contents of the page. The first line checks if the current page is one of the few pages that unauthenticated users are allowed to view such as the login, registration, policies, or gift card pages.

This restriction works very well for InMode because users can only register for accounts via the customer approval process which means that only approved users will be able to create a Shopify customer account. Additionally, Liquid is rendered server side meaning that no store data is ever sent to the user’s browser if they are not logged in. Our production code has additional server side checks for further page access however this example covers the main functionality of it.

Note: while it is technically possible for unauthenticated users to forcibly create customer accounts via modifying the page markup to add the standard Shopify customer registration form, further access to the store is limited server side via customer metafields which can only be modified via the Shopify API. Access to the two main parts of the store (the store and the Resource Center) requires a specific customer metafield, otherwise none of the page content is rendered, meaning that no authentication protected store information is available to the user. Additionally, we have an automation in place which checks newly created customer accounts for the required metafield and immediately suspends the account if it does not.

User account applications are managed by the custom app we created for InMode which will be detailed later on.

Layout > Template > Section > Snippet

InMode eStore Case Study Image - 1

Limited product access and by extension limited resources access (which we will detail later on), was the most complex part of the build. Every user has specific attributes which determine the specific products and collections they have access to.

The main attributes are region, technology, type, and workstation. region is the customer’s region (e.g. USA, Canada, etc), technology is the technologies that the customer has, type is the customer type (e.g. customer, sales representative, third party vendor, etc), and workstation is the workstations that the customer has. We apply these tags using the format ATTRIBUTE:VALUE, for example TYPE:CUSTOMER or TECHNOLOGY:AVIVA. A customer account can have any number of these tags. There are a few more attributes based on the customer type which we will not go into full detail about for the sake of simplicity.

To use these tags to restrict customer access we created a content model in Contentful called “Permission Set” (see the section below titled “Resource Center” for further details about how and why we are using Contentful). A permission set is simply a set of rules that a customer must pass in order to view a product it is attached to. Permission sets are attached to products in 2 fields, primary and secondary. Products must have at least 1 primary permission set while secondary permission sets are optional. There is no limit to the number of permission sets that can be attached to either the primary or secondary fields. The logic used when evaluating permission sets is (ANY PRIMARY) AND (ANY SECONDARY) or in other terms (PRIMARY_1 OR PRIMARY_2 OR ...) AND (SECONDARY_1 OR SECONDARY_2 OR ...).

Permission sets themselves contain a list of checkboxes for each customer attribute with the values that are required to pass the permission set. A customer must have at least one specified value of each attribute in order to pass the permission set. For example, a permission set might specify REGION: USA, CANADA and TECHNOLOGY: AVIVA, BODYTITE. In this case a customer must have either tag REGION:USA or REGION:CANADA as well as either tag TECHNOLOGY:AVIVA or TECHNOLOGY:BODYTITE. This combination of logic both within the permission set and on the resource allows for a wide variety of reusable access requirements that is flexible and easy to update.

On the storefront we are applying this permissions logic via a simple liquid snippet that checks the collections and products on the page against the user’s tags and conditionally shows the items.

The permissions sets are attached to collections and products via metafields which are managed by the custom app (more details on this later on).

Single order purchase limits were solved via a simple utility script in the theme code that checks a global metafield for a product’s ID. If the product ID is found in the metafield then the attached purchase limit rule is applied. This utility is used whenever a product is added to the cart to first change the quantity based on the rule before submitting the “add to cart” API request.

In addition to this, InMode also has monthly and quarterly purchase limits for specific product types which apply to their sales representatives. However, unlike the single order purchase limits, these limits are soft, meaning that a sales representative can still submit an order that exceeds the limits. If an order does exceed the limits then it will be held for manual review.

To achieve this we are utilizing Shopify’s draft order API. When an order is submitted, instead of going through the regular checkout process, the storefront sends an API call to our backend where the order details are saved to a database and a draft order is created. An email notification is then sent to the applicable InMode staff member with a unique authenticated link to approve or deny the order. If the order is approved then the draft order is created into a regular order. Since sales representatives don’t have to pay for the items they purchase, the order is automatically completed without further action. We keep track of the sales representative’s monthly and quarterly purchases via a metafield on their customer object.

As aforementioned, sales representative orders are routed through our backend API instead of the regular checkout process. There are certain conditions where these orders are automatically approved, however if these conditions are not met then an InMode staff member must manually approve the order.

To achieve this we save the order details to a database and send an email to the applicable staff member with a unique link containing an authentication token to view the order details. The staff members that are responsible for particular orders are managed via our custom app. More details on this later. The unique link takes the staff member to a simple page served by our backend which displays all the order information along with two buttons for approving and denying the order. The action taken is then passed back to our backend where the order is updated and an email is sent to the customer informing them of the outcome.

This process of passwordless authentication for order approvals helped InMode with their daily operations as they have less users to manage and allows them to quickly switch the staff members who receive the order approval emails.

As you might expect, Shopify’s out of the box search functionality did not have the customization necessary for facilitating the permissioned product and resource search required by InMode. This is why we opted to use Algolia instead.

Algolia is a service that provides search functionality via their own indices and search engine. This can be customized to allow a range of features such as typo tolerance and complex filters. We utilized this to restrict search results via customer permissions.

When a customer types into the search bar we pass this query to Algolia along with a filter rule for all of the permission sets that the customer passes. The filter behaves exactly like the permissions logic mentioned above, which is (ANY PRIMARY) AND (ANY SECONDARY). In the Algolia filter this looks like (p1:id1 OR p1:id2 OR ... OR p1:any) AND (p2:id1 OR p2: id2 OR ... OR p2:any). Here p1 and p2 represent primary and secondary permission sets. They were named p1 and p2 to save on characters in the query. Additionally, the permission set IDs used in Algolia are the first 5 characters instead of the full length of 22 characters to also save on characters in the query. We opted to use 5 characters because this results in ~916 million different possible truncated IDs since IDs can contain uppercase characters (26), lowercase characters (26), and numbers (10). This is 62 to the power of 5. That means the odds of ID collision are roughly 0.00054% for 100 permission sets. This can be calculated using the Birthday Paradox formula 1 - ((d-1)/d)**(n*(n-1)/2) where d is the number of possibilities and n is the number of IDs. Currently InMode only uses roughly 50 permission sets so the collision odds are even lower.

Algolia allows up to 1,000 filters in a request which means that InMode can have 500 permission sets before this limit is reached since we use each permission set twice in the query.

We do not pass this query through our backend to save on unnecessary bandwidth and strain. Instead the search is passed directly to Algolia via the web store which means that a malicious user could technically modify the search request to return results that they do not have permissions to access. However, Algolia only stores the name and link of a given product or resource which is not deemed as sensitive information since the pages the links point to are restricted server side.

InMode eStore Case Study Image - 2

The Resource Center is a section of the web store where customers can download resources (such as images and videos) that are relevant to the technologies and workstations that they have. It is organized similarly to the web store via collections (however with a few modifications). The number of resources in the Resource Center is far greater than the number of products in Shopify (~1,000 compared to ~100). This meant that we were unable to host the resources on Shopify via custom pages without running into significant complexity issues.

Instead we opted to use Contentful, a CMS (content management system), which we have extensive experience with via our other clients. Contentful allows us to declare content models with specific fields and validations which can then be managed by InMode staff. Additionally, Contentful hosts all the files for all the resources.

While drafting the initial plan for the new tech stack we had to make a decision on how to best manage the data that is shared between the store and the Resource Center. This data being the definitions of all the technologies, workstations, and permission sets. With Shopify, InMode has multiple expansion stores for each region they operate in so we decided to manage all of the global data in Contentful since the Resource Center is shared between all of the stores. This means that any changes are immediately available to all stores without needing to maintain multiple sources of truth.

Additionally, Contentful has localization support out of the box which means that InMode does not need to maintain multiple copies of the same resources for stores in different regions.

Initially we experimented with the idea of using either vanilla JavaScript or React on the page to dynamically fetch and display the data from Contentful. However we decided instead to use the JavaScript framework Gatsby JS to build static pages of all of the resources since Contentful’s delivery API limits were too low to handle the expected web traffic from InMode’s customers. The Gatsby site is hosted on Netlify and displayed on the web store via an iFrame. The iFrame and JavaScript of the web store communicate with one another to only display the resources that the user has access to. We also pass localizations from the web store to the iFrame so that all translations are maintained in the Shopify theme’s locales JSON file.

You may be curious about the security of the permissions with a static website. Unfortunately, without server side rendering a static site is unable to fully restrict access to resources that a user does not have permission to view. That being said, the resources in the Resource Center are not secretive since they are all intended to be used by clinics to show to customers (for example marketing materials or case studies). Therefore we are able to take this compromise of filtering on the front end since the purpose of the permissions are primarily to help InMode customers find the resources that are applicable to them and hide the ones that are not.

Regardless, we still have taken a few steps to make accessing resources that a user does not have access to a little harder. First of all, before any of the page is rendered the top level window’s domain is checked and if it is not a valid InMode URL then the website either closes itself, returns to the previous page, or redirects to a blank page. Secondly, the markup of the page is also not rendered until after a valid connection with the InMode web store is made. The actual details of the resources are abstracted in the compiled JavaScript of the page. And with Gatsby’s code splitting and multiple JavaScript bundle files it’s not trivial to extract these details. And finally, the details themselves are scrambled at build time and require a key that is passed from the web store at run time to unscramble. This makes them more difficult to search for and extract from the raw JavaScript chunk files. While none of this makes it technically impossible for someone to extract the details of the resources from the Resource Center, it makes it infeasible considering that the majority of the resources publicly exist on social media already.

A significant issue we faced with Contentful’s asset hosting was that when users clicked on the “download” button for files such as images and PDFs, their browser would open a new tab and show the image or PDF instead of downloading it. This is because modern browsers block direct downloads from third party domains even and require them to have the response header Content-Disposition: attachment in order to allow them. Unfortunately Contentful does not have an option to enable this. So the solution that we came up with is to proxy all download requests through a simple server that adds this header to the response. The code for this proxy is very simple

InMode eStore - Case Study - Code 2

What this bit of code does is check if the user is currently logged in and if they are not we render a script that returns them to the login page instead of the actual contents of the page. The first line checks if the current page is one of the few pages that unauthenticated users are allowed to view such as the login, registration, policies, or gift card pages.

This restriction works very well for InMode because users can only register for accounts via the customer approval process which means that only approved users will be able to create a Shopify customer account. Additionally, Liquid is rendered server side meaning that no store data is ever sent to the user’s browser if they are not logged in. Our production code has additional server side checks for further page access however this example covers the main functionality of it.

Note: while it is technically possible for unauthenticated users to forcibly create customer accounts via modifying the page markup to add the standard Shopify customer registration form, further access to the store is limited server side via customer metafields which can only be modified via the Shopify API. Access to the two main parts of the store (the store and the Resource Center) requires a specific customer metafield, otherwise none of the page content is rendered, meaning that no authentication protected store information is available to the user. Additionally, we have an automation in place which checks newly created customer accounts for the required metafield and immediately suspends the account if it does not.

User account applications are managed by the custom app we created for InMode which will be detailed later on.

Those familiar with iFrames might be curious how we keep a seamless integration and keep up the illusion that the pages within the iFrame are natively part of the web store. To achieve this we do a couple of things when the iFrame load.

Firstly, the iFrame is temporarily covered by a loading spinner overlay. When the iFrame has loaded it sends a message to the web store requesting initial data. When the web store gets this message it replies with a JSON object containing the current customer details (for filtering the resource based on their permissions), the URL of the main CSS stylesheet, and the key for unscrambling the resources data.

The CSS URL is then added to the <head> of the iFrame so that the page inherits all of the latest styles of the web store. This means we do not need to update the styles on the Resource Center when they are updated on the web store. Then the data is unscrambled and filtered, the page is rendered, and a message is sent back to the web store that the Resource Center has initialized. The web store listens for this message and removes the loading spinner overlay. This takes roughly 0.5 to 2 seconds to complete. After the initial render Gatsby JS behaves like any other single page application, meaning that the initialization process only needs to run once per session.

The iFrame also sends a message to the web store whenever the route is changed so that the web store can update the window’s URL. This allows the user to keep a direct link to the current page even though the content is being served via an iFrame. The way we made this work is by search parameters on the web store. Before the iFrame is initialized the JavaScript on the page checks for any search parameters and determines the correct URL to initialize the iFrame with.

You might be wondering about what happens when a user then opens a link within the iFrame to a new tab. Without any specific handling of this the new tab would just auto close or redirect to a blank page as previously mentioned. To prevent this, every link in the iFrame has the search parameter shop=us.inmoderesources.com where the shop parameter is the current web store URL. The iFrame then redirects to the Resource Center page of the applicable web store with the specific search parameters for the requested resource.

All of the custom functionality we set up for InMode needed to be managed somewhere. To keep the staff member experience as seamless as possible, we built InMode a custom Shopify app that is accessible in their Shopify admin dashboard. This app is where staff members complete admin tasks such as:

  1. Managing customer account requests

  2. Resending custom transactional customer emails

  3. Managing discount code SKUs

  4. Managing who receives emails about specific events

  5. Exporting orders in custom CSV formats

  6. Managing gift card reasons and SKUs (custom metadata)

  7. Viewing existing gift cards and their custom metadata

  8. Issuing gift cards with custom metadata

  9. Managing pending orders

  10. Managing product and collection permissions

  11. Managing sales representative order limits

  12. Managing single order limits

  13. Managing staff access within the InMode custom app

The majority of the app data is stored on the Shopify store itself via metafields since a large number of the settings are required by the web store meaning that our theme can access it directly. However private data such as the customer account applications, pending orders, gift card metadata, staff data, etc, is stored in a database. We chose Firebase as the database for its ease of use and simplicity. The bandwidth the database experiences is relatively low so we decided to use the same Firebase instance for each store instead of setting up an entirely separate instance for each. This makes launching new expansion stores quick and easy.

The app itself is a simple React app hosted on Netlify with a handful of serverless functions which act as our backend. Shopify’s documentation suggests using Express JS in a Node JS server to serve a React app and also contain all the backend code. However, since our custom app doesn’t experience the same bandwidth as public apps do, we decided to use Netlify’s serverless functions as our backend instead. This means we do not need to maintain an active server.

All database and Shopify API interaction is handled via the serverless functions so none of the API keys are accessible to the front end for security reasons. Every request to the functions is also validated via Shopify’s session token to verify that the request came from the custom app.

A major limitation of Shopify’s app environment is that there is no distinction between the users that are currently accessing the app. For a client such as InMode with many staff members who each have different responsibilities, this was an issue. InMode requires strict control of what each staff member has access to so that mistakes are minimized.

To solve this problem, we implemented a rudimentary authentication system. We could have implemented a fully fledged authentication solution however this would have added friction to the staff member experience since they would have to log in a second time within the app. Instead, InMode was happy with a simple PIN code system, similar to how point of sale systems in restaurants or cafes work.

So when a staff member accesses the app for the first time they are prompted to enter their email address and PIN code. Once authenticated their session is saved so they do not need to reauthenticate again. Using their email address the app can then filter the options that the staff member can see based on the rules set by the InMode admins within the app.

With authenticated staff member access to the custom app we are also able to keep a log of all actions taken by every staff member which makes identifying staff member mistakes quick and easy.

Staff members can approve or deny pending customer applications within the custom app. During the approval process staff members are required to select the necessary permissions the customer account should have. Upon approval the account is created and an email sent to the customer notifying them.

If a customer account is instead denied then the staff member is required to enter a reason for the denial. The staff member can also decide to block future applications from the same email address. The customer is also notified via email of this decision.

Approved and denied customer applications are then viewable in the custom app with all the permissions that the staff member attached to the account.

To attach permission sets from Contentful to collections and products in Shopify we are using metafields. All available permission sets are stored on each Shopify store via a metafield which is used to display the checklist to the staff member currently updating the permissions.

When a collection or product’s permissions are updated, the IDs of the selected permission sets are saved on a JSON metafield on the collection or product which is then accessible to the web store’s front end theme code.

Most objects in Shopify can have metafields, which are custom values that can be attached to them for any purpose. However gift cards are one of the few objects that do not have metafields, much to the disappointment of InMode as they keep track of a number of metadata for every gift card that is issued.

So we had to retrofit this functionality. Instead of issuing a gift card via the Shopify admin dashboard, a staff member issues it via our custom app where they are required to enter a couple of key pieces of information which is then saved to our database.

Staff members can also view gift cards via our custom app which fetches the metadata from our database.

InMode’s order handling system is a custom solution that listens for CSV files uploaded to a server. We do this via a Shopify webhook for new orders which sends the order information to our backend where it is transformed into the necessary CSV format and uploaded to the server via SFTP.

One piece of custom data that is required in the CSV files is SKUs for discount codes. These SKUs are defined by a staff member via the custom app and saved on a metafield. This metafield is then retrieved by our order handler and matched to the discount code used on the order (if any). A similar action is taken for gift cards.

Additionally, we also automatically tag every order with their shipping method to allow for filtering on Shopify’s admin dashboard since this is not a feature they offer natively.

The backend of the custom app is also the receiver of a couple of webhooks from both Shopify and Contentful when products and resources are created, updated, or deleted. The handler of these webhooks syncs this data with Algolia.

Another piece of data syncing that is done automatically is copying all the available permission sets from Contentful to a Shopify metafield for use within the custom app and web store front end.

Using a handful of technologies together we have been able to add significant depth to Shopify’s core features where InMode required them most while simultaneously keeping software costs minimal and scalability easy. Our approach of breaking down their requirements into three distinct parts allows us to easily delegate requested features to their respective application.

Are you ready to scale your eComm brand?

Get in touch with us!