Variants | Moloni ON API
Moloni ONGuidesAPI ReferenceExplorer
Guides

Variants

Variants represent different versions of a product, for example a T-Shirt in multiple colors and sizes. Each variant is a separate product linked to a parent, with its own price, stock and identifiers.

How variants work

Variants build on property groups:

  1. Create a property group with properties (Color, Size) and values (Red, Blue, S, M, L)
  2. Create a parent product with propertyGroupId set and a variants array
  3. Each variant specifies its propertyPairs, one value per property
  4. The API auto-generates each variant's reference from the parent reference + value codes
Parent: "TSHIRT" (propertyGroup: "T-Shirt Options")
├── Variant: "TSHIRT-RED-S"  (Color=Red, Size=Small)  → price: 15.99
├── Variant: "TSHIRT-RED-M"  (Color=Red, Size=Medium)  → price: 15.99
├── Variant: "TSHIRT-BLUE-S" (Color=Blue, Size=Small)  → price: 16.99
└── Variant: "TSHIRT-BLUE-M" (Color=Blue, Size=Medium) → price: 16.99

There is no separate "variant" mutation; variants are created and updated through productCreate and productUpdate.

Creating a product with variants

You need the UUIDs from your property group. If you haven't created one yet, see the Property Groups guide.

mutation {
  productCreate(
    companyId: 1
    data: {
      name: "Classic T-Shirt"
      reference: "TSHIRT"
      type: 1
      productCategoryId: 5
      measurementUnitId: 1
      price: 15.99
      hasStock: true
      propertyGroupId: "group-uuid"
      taxes: [
        { taxId: 1, value: 23, ordering: 1, cumulative: false }
      ]
      variants: [
        {
          name: "Red Small"
          price: 15.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "red-uuid" }
            { propertyId: "size-uuid", propertyValueId: "small-uuid" }
          ]
          warehouses: [
            { warehouseId: 1, stock: 25 }
          ]
        },
        {
          name: "Red Medium"
          price: 15.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "red-uuid" }
            { propertyId: "size-uuid", propertyValueId: "medium-uuid" }
          ]
          warehouses: [
            { warehouseId: 1, stock: 40 }
          ]
        },
        {
          name: "Blue Small"
          price: 16.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "blue-uuid" }
            { propertyId: "size-uuid", propertyValueId: "small-uuid" }
          ]
          warehouses: [
            { warehouseId: 1, stock: 15 }
          ]
        }
      ]
    }
  ) {
    errors { field msg }
    data {
      productId
      name
      reference
      variantsCount
      variants {
        productId
        parentId
        name
        reference
        price
        propertyPairs {
          property { name }
          propertyValue { value code }
          ordering
        }
        warehouses {
          warehouseId
          stock
        }
      }
    }
  }
}

Response:

{
  "data": {
    "productCreate": {
      "errors": [],
      "data": {
        "productId": 100,
        "name": "Classic T-Shirt",
        "reference": "TSHIRT",
        "variantsCount": 3,
        "variants": [
          {
            "productId": 101,
            "parentId": 100,
            "name": "Red Small",
            "reference": "TSHIRT-RED-S",
            "price": 15.99,
            "propertyPairs": [
              { "property": { "name": "Color" }, "propertyValue": { "value": "Red", "code": "RED" }, "ordering": 1 },
              { "property": { "name": "Size" }, "propertyValue": { "value": "Small", "code": "S" }, "ordering": 2 }
            ],
            "warehouses": [{ "warehouseId": 1, "stock": 25 }]
          },
          {
            "productId": 102,
            "parentId": 100,
            "name": "Red Medium",
            "reference": "TSHIRT-RED-M",
            "price": 15.99,
            "propertyPairs": [
              { "property": { "name": "Color" }, "propertyValue": { "value": "Red", "code": "RED" }, "ordering": 1 },
              { "property": { "name": "Size" }, "propertyValue": { "value": "Medium", "code": "M" }, "ordering": 2 }
            ],
            "warehouses": [{ "warehouseId": 1, "stock": 40 }]
          },
          {
            "productId": 103,
            "parentId": 100,
            "name": "Blue Small",
            "reference": "TSHIRT-BLUE-S",
            "price": 16.99,
            "propertyPairs": [
              { "property": { "name": "Color" }, "propertyValue": { "value": "Blue", "code": "BLUE" }, "ordering": 1 },
              { "property": { "name": "Size" }, "propertyValue": { "value": "Small", "code": "S" }, "ordering": 2 }
            ],
            "warehouses": [{ "warehouseId": 1, "stock": 15 }]
          }
        ]
      }
    }
  }
}

Notice that variants get their own productId and an auto-generated reference built from the parent reference + property value codes, ordered by the property's ordering.

What variants inherit from the parent

When created, variants automatically inherit these fields from the parent product:

  • type: Product or Service
  • companyId
  • measurementUnitId
  • productCategoryId
  • hasStock / minStock
  • exemptionReason
  • taxes
  • suppliers
  • customFields

What variants can override

Each variant can have its own:

FieldDescription
nameVariant display name
pricePrice (can differ from parent)
summaryDescription
notesInternal notes
visibleVisibility flag
imgVariant-specific image
warehousesStock per warehouse
priceClassesPrice class overrides
identificationsBarcodes specific to this variant

Variant input fields

The ProductVariantInsert input:

FieldTypeDescription
nameString!Variant name (required)
priceFloatVariant price
summaryStringDescription
notesStringInternal notes
visibleIntVisibility flag
imgUploadImage
minStockFloatMinimum stock threshold
warehouseIdIntDefault warehouse
propertyPairs[ProductVariantPropertyPairsAssociation!]Property value assignments
warehouses[ProductWarehouseInsertAssociation!]Stock per warehouse
priceClasses[ProductPriceClassAssociation!]Price class values
identifications[ProductIdentification!]Barcodes

Stock requirements

If the parent has hasStock: true, every variant must include at least one warehouses entry. If the parent has hasStock: false, variants cannot include warehouses.

Querying a product with its variants

query {
  product(companyId: 1, productId: 100) {
    data {
      productId
      name
      reference
      price
      variantsCount
      propertyGroup {
        propertyGroupId
        name
        properties {
          propertyId
          name
          ordering
          values {
            propertyValueId
            code
            value
          }
        }
      }
      variants {
        productId
        name
        reference
        price
        visible
        stock
        propertyPairs {
          property { name ordering }
          propertyValue { value code }
          ordering
        }
        warehouses {
          warehouseId
          stock
          minStock
        }
      }
    }
  }
}

The variants field accepts an optional visible parameter to filter by visibility:

variants(visible: 1) {
  productId
  name
  price
}

Filtering variants in product lists

When listing products with the products query, variants appear as separate products with a non-null parentId. You can use this to distinguish parent products from variants:

query {
  products(companyId: 1, options: {
    pagination: { page: 1, qty: 20 }
  }) {
    data {
      productId
      parentId
      name
      reference
      price
      variantsCount
    }
  }
}
  • parentId: null → this is a parent product (or a standalone product)
  • parentId: 100 → this is a variant of product 100

Updating variants

Use productUpdate on the parent product to manage its variants. The variants array follows the same create/update/delete convention:

  • With productId → update that existing variant
  • Without productId → create a new variant
  • Existing variants not in the array → deleted

Adding a new variant

Include all existing variants plus the new one:

mutation {
  productUpdate(
    companyId: 1
    data: {
      productId: 100
      variants: [
        {
          productId: 101
          name: "Red Small"
          price: 15.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "red-uuid" }
            { propertyId: "size-uuid", propertyValueId: "small-uuid" }
          ]
          warehouses: [{ warehouseId: 1, minStock: 5 }]
        },
        {
          productId: 102
          name: "Red Medium"
          price: 15.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "red-uuid" }
            { propertyId: "size-uuid", propertyValueId: "medium-uuid" }
          ]
          warehouses: [{ warehouseId: 1, minStock: 5 }]
        },
        {
          productId: 103
          name: "Blue Small"
          price: 16.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "blue-uuid" }
            { propertyId: "size-uuid", propertyValueId: "small-uuid" }
          ]
          warehouses: [{ warehouseId: 1, minStock: 5 }]
        },
        {
          name: "Blue Medium"
          price: 16.99
          propertyPairs: [
            { propertyId: "color-uuid", propertyValueId: "blue-uuid" }
            { propertyId: "size-uuid", propertyValueId: "medium-uuid" }
          ]
          warehouses: [{ warehouseId: 1, minStock: 3 }]
        }
      ]
    }
  ) {
    errors { field msg }
    data {
      productId
      variantsCount
      variants {
        productId
        name
        reference
        price
      }
    }
  }
}

The new "Blue Medium" variant (no productId) will be created with the auto-generated reference "TSHIRT-BLUE-M".

Updating a variant's price

To change just one variant's price, you still need to include all variants in the array:

variants: [
  { productId: 101, name: "Red Small", price: 17.99, propertyPairs: [...], warehouses: [...] },
  { productId: 102, name: "Red Medium", price: 17.99, propertyPairs: [...], warehouses: [...] },
  { productId: 103, name: "Blue Small", price: 18.99, propertyPairs: [...], warehouses: [...] },
  { productId: 104, name: "Blue Medium", price: 18.99, propertyPairs: [...], warehouses: [...] }
]

Removing a variant

Simply omit it from the variants array. The API deletes variants that are present in the database but absent from the update.

Reference generation

Variant references are auto-generated and cannot be set manually. The format is:

{parentReference}-{code1}-{code2}-...

The codes are taken from the property values in the order defined by the property's ordering field. For example:

Parent ReferenceColor CodeSize CodeVariant Reference
TSHIRTREDSTSHIRT-RED-S
TSHIRTBLUEXLTSHIRT-BLUE-XL
MUG-CERAMICWHITE-MUG-CERAMIC-WHITE

The full variant reference must fit within 50 characters (the database limit for all product references). Because of this, parent products that have variants are limited to a 30-character reference, leaving room for the separator and codes.

Keep property value codes short to avoid hitting this limit. If a generated reference exceeds 50 characters or conflicts with an existing product, the API returns a validation error.

Next steps