Bringing your product data into Crystallize shouldn't involve thousands of individual API calls. Whether you are migrating from a legacy system, syncing a CSV from a supplier, or pushing daily updates from an ERP, Mass Operations is the high-performance engine designed for the job.
By using a declarative JSON structure, you can define your entire catalog hierarchy, handle multi-variant products, and manage publishing states in a single batch. This approach significantly reduces network overhead and ensures your data is processed efficiently in the background.
We have collected a selection of typical product import operations to get you started.
The repository can be found on the Crystallize GitHub: https://github.com/CrystallizeAPI/examples/tree/main/mass-operations
In this example, we perform three distinct actions within one operation:
Note on References: We use _ref to create a temporary handle for an item. This allows subsequent operations in the same file (like the product creation or publishing) to reference the ID of an item that hasn't been created yet.
{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}Managing media in bulk shouldn't be a manual task. With Mass Operations, you can register and import external images (via URL) and link them to your variants in a single job. The images are then automatically made available in responsive sizes and modern formats on the Crystallize CDN.
{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "img1",
"intent": "image/register",
"key": "{{ upload \"https://picsum.photos/seed/product1/800/600\" }}"
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"images": [{ "key": "{{ img1.id }}" }],
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "img1",
"intent": "image/register",
"key": "{{ upload \"https://picsum.photos/seed/product1/800/600\" }}"
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"images": [{ "key": "{{ img1.id }}" }],
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}In Crystallize, categories are typically represented by Folders. To build a navigation tree or a category hierarchy, you use the tree property to define parent-child relationships.
When importing a full tree, you use _ref to create a "handle" for the parent folder. You then reference that handle in the child's parentId using Handlebars syntax: {{ your_reference.id }}.
This example demonstrates a complete catalog setup in one file:
{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" }
},
/* Sub-categories nested under the Root */
{
"_ref": "cat1",
"intent": "folder/upsert",
"resourceIdentifier": "category-1",
"language": "en",
"shapeIdentifier": "folder",
"name": "Category 1",
"tree": { "parentId": "{{ rootCategory.id }}" }
},
/* ... repeat for other categories ... */
/* Registering external images */
{
"_ref": "img1",
"intent": "image/register",
"key": "{{ upload \"https://picsum.photos/seed/product1/800/600\" }}"
},
/* Creating a product inside a sub-category */
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ cat1.id }}" },
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"images": [{ "key": "{{ img1.id }}" }],
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
/* Explicitly publishing the hierarchy */
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
}
/* ... continue publishing all items ... */
]
}{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" }
},
/* Sub-categories nested under the Root */
{
"_ref": "cat1",
"intent": "folder/upsert",
"resourceIdentifier": "category-1",
"language": "en",
"shapeIdentifier": "folder",
"name": "Category 1",
"tree": { "parentId": "{{ rootCategory.id }}" }
},
/* ... repeat for other categories ... */
/* Registering external images */
{
"_ref": "img1",
"intent": "image/register",
"key": "{{ upload \"https://picsum.photos/seed/product1/800/600\" }}"
},
/* Creating a product inside a sub-category */
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ cat1.id }}" },
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"images": [{ "key": "{{ img1.id }}" }],
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
/* Explicitly publishing the hierarchy */
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
}
/* ... continue publishing all items ... */
]
}The complete operations file can be downloaded from GitHub.
Product storytelling requires more than just a name and a price. In Crystallize, you use Shapes to define custom structures (like technical specs, marketing copy, or SEO metadata). When importing, you populate these structures via the components array.
To successfully import rich content, the componentId in your JSON must match the Identifier of the component defined in your Shape. In this example, we are populating a singleLine component with the ID title.
{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [
{
"componentId": "title",
"singleLine": {
"text": "Longer title for Product 1"
}
}
],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}{
"version": "0.0.1",
"operations": [
{
"_ref": "rootCategory",
"intent": "folder/upsert",
"resourceIdentifier": "product-category",
"language": "en",
"shapeIdentifier": "folder",
"name": "Demo",
"tree": { "parentId": "{{ defaults.rootItemId }}" },
"components": []
},
{
"_ref": "p1",
"intent": "product/upsert",
"resourceIdentifier": "p-1",
"language": "en",
"shapeIdentifier": "product",
"name": "Product 1",
"tree": { "parentId": "{{ rootCategory.id }}" },
"components": [
{
"componentId": "title",
"singleLine": {
"text": "Longer title for Product 1"
}
}
],
"vatTypeId": "{{ defaults.vatTypeIds.[0] }}",
"variants": [
{
"name": "Product 1 Variant",
"sku": "sku-001",
"isDefault": true,
"priceVariants": [{ "identifier": "default", "price": 9.99 }]
}
]
},
{
"intent": "item/publish",
"itemId": "{{ rootCategory.id }}",
"language": "en"
},
{
"intent": "item/publish",
"itemId": "{{ p1.id }}",
"language": "en"
}
]
}When managing inventory at scale, you don't want to update products one by one. The Mass Operations API allows you to target specific variants by SKU and modify their stock across different SKUs and stock locations simultaneously.
Choosing the right operation is key to maintaining data integrity between your physical warehouse and your digital catalog:
Operation | Action | Best Use Case |
overwrite | Replaces the current stock value with the provided quantity. | Periodic full syncs from an ERP or daily inventory counts. |
increase | Adds the quantity to the existing stock. | Recording new stock arrivals or returns. |
decrease | Subtracts the quantity from the existing stock. | Processing sales that occurred on an external platform (offline or via POS). |
In this scenario, we are performing an overwrite to ensure Crystallize exactly matches the numbers from our warehouse for the "Default" location. When working with multiple warehouse locations you can define this as additional Stock Locations in Crystallize and refer to the stockLocationIdentifier in the operation.
{
"version": "0.0.1",
"operations": [
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 100,
"sku": "sku-001",
"stockLocationIdentifier": "default"
},
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 50,
"sku": "sku-002",
"stockLocationIdentifier": "default"
},
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 10,
"sku": "sku-003",
"stockLocationIdentifier": "default"
}
]
}{
"version": "0.0.1",
"operations": [
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 100,
"sku": "sku-001",
"stockLocationIdentifier": "default"
},
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 50,
"sku": "sku-002",
"stockLocationIdentifier": "default"
},
{
"intent": "product/variant/stock/modify",
"operation": "overwrite",
"quantity": 10,
"sku": "sku-003",
"stockLocationIdentifier": "default"
}
]
}To ensure your stock update runs smoothly, keep these details in mind: