Product structure and how to recognize variants

In order to understand how variants work, it's important to understand the way they are represented in the API and the Lightspeed Retail (X-Series) database. The core of the concept is that there is always a variant parent and at least one variant child. There is a limit of 200 variants per family.

Parent - child relationship

Products have a few of attributes which define the if and how they are involved in a variant product.


The first and the most important thing in here is the handle. The handle is the main thing that the variant parent and all of its children have in common. The handle is also what allows us to create new variants via the API but more on that later here:

variant_parent_id and has_variants

Once a variant product is created, variant_parent_id and has_variants allow the API consumer to recognize what part a particular product is playing in the parent-child relationship.

NOTE: It's important to remember that those are read-only attributes and they cannot be used to modify to remove a product from a variant product or reassign it to another product.

There are 3 possible combinations in which those attributes can exist:

  • Standard, simple product - no variants involved
  • The main product - variant parent
  • The variant product - variant child
variant_parent_idid of the parent product

Variant options - variant's unique characteristics

Apart from the "structural" attributes discussed above, there are also some which define unique properties of each variant (parent or child) - so-called variant options. In total, Lightspeed Retail (X-Series) allows the user to define 3 different options. The are usually used to describe properties like color, size, fabric, etc.
A simple example of a product with the Color option defined as black will look like this in the payload:

    "variant_option_one_name": "Color",
    "variant_option_one_value": "black",
    "variant_option_two_name": "",
    "variant_option_two_value": "",
    "variant_option_three_name": "",
    "variant_option_three_value": "",

NOTE: It will sometimes happen that a product not involved in a variant product will have those properties populated in the payload. That means that this product used to be a variant (most likely parent) but the user turned it into a simple product. The variant options data should not be used to determine if a product IS involved in variants.

Variant property inheritance

When interrogating a product's properties, it's important to know that variant children don't hold all the data related to them. Some of that data is only available on the parent.
The following attributes of a product are always inherited from the parent:

  • Name
  • Handle
  • Description
  • Tags (categories)
  • Product type
  • Supplier
  • Brand
  • Sales account code
  • Purchase account code
  • Images

All the following attributes are associated directly with the variant product object:

  • SKU
  • Pricing
  • Loyalty
  • Supplier code
  • Inventory

How to get all the variants

Based on the assumption that all the variant products (parent and the children) have the same handle it is possible to get all of them from the API by using the handle query parameter like:


The important thing to remember here is that the API doesn't verify in any way if all the children are actually associated with the returned parent(s) by the variant_parent_id. While this shouldn't normally happen, it is possible to get to that state by requesting product undeletion from our Support or a broken CSV import. Because of that, the application making this request should verify the correctness of the relationship between all returned products.

How to create variants

Due to its size, this section was extracted into a separate article here: Creating variant products via the API


Variant updates and base_name

When updating a product which is a part of a variant product, there's one important thing to watch out for. The name of the product delivered in the payload will be concatenated from the "root" defined as name by the user and all the variant option values, separated by the / character, e.g. "iPad case / black". If a name like that gets posted back to the API it will be stored as the name attribute and the next time it is retrieved from the API all the variant options will be duplicated like "iPad case / black / black".
There are 2 ways to avoid that behavior:

  1. Avoid posting the name back to the API if you don't really want to change it,
  2. Read the root of the name from the base_name attribute and post that as name.