# Images and Assets In digital commerce, visual appeal is everything. Online shoppers rely heavily on product images when making purchase decisions and depend on high-quality visuals to evaluate product details, compare options, and build trust in a brand. Why do images matter in digital commerce? - **Increase conversions**: a well-optimized image can increase sales. - **Reduce returns**: clear product visuals help set accurate expectations, decreasing return rates. - **Strengthen brand identity**: consistent and high-quality imagery enhances brand recognition. - **Boost engagement**: interactive media keep customers engaged longer. However, as digital commerce businesses expand their catalogs and sell across multiple markets, effectively managing media assets becomes a challenge. commercetools provides two options to manage images with your product catalog: [Images](/api/projects/products.md#image) and [Assets](/api/types.md#assets). The following sections clarify the differences between Images and Assets and the scenarios where you should use each option. ## Images Images are integrated with [Product Variants](/api/projects/products.md#productvariant). - **Purpose**: represent product visuals directly on product pages and listings. - **Storage and delivery**: uploaded images are stored on a Content Delivery Network (CDN), ensuring fast loading times globally. - **Limitations**: no sharing between Product Variants. Implement this logic in your application, if required. - **External hosting**: you can link to images hosted externally by providing their URLs. You can add, update, and delete Images either via the Merchant Center on the Product Variant level or via the [HTTP API](/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproducts%2F%7Bid%7D%2Fimages%3APOST). ## Assets Assets provide a more flexible approach for handling various types of media, beyond just product images. - **Purpose**: manage multiple types of media (such as images, videos, and PDFs) associated with Products, Categories, or other resources. - **Flexibility**: - Multiple sources: an Asset can have different formats and resolutions (for example, video encodings and image sizes). - Rich metadata: you can use `name`, `description`, and `tags` for organization and filtering purposes. - Custom Fields: store additional information specific to your needs. - **Use cases**: - Product manuals (PDFs) - Localized videos - Multiple image resolutions for different contexts - 360-degree product views - Various video encodings for compatibility - **External hosting**: you can add links to images hosted externally by providing their URLs. For more information about Asset type definitions and fields, see the [Assets](/api/types.md#assets) API reference. ## Key differences at a glance | Feature | Image | Asset | | :--- | :--- | :--- | | Purpose | Product-specific visuals | General media management | | File Types | Images only | Images, videos, PDFs, and more | | Metadata | Limited (URL, label) | Rich metadata (name, description, tags, and custom fields) | | Organization | Tied to Product Variants | Flexible; can be linked to Product Variants and Categories | | Source options | Single source | Multiple sources for different formats, resolutions, and encodings | | Use cases | Product displays | Product manuals, localized content, 360° views, high-resolution downloads, and any scenario that requires diverse media with detailed metadata and flexible management | ## Choose between Images and Assets While Images are suitable for basic product visuals, Assets are generally the preferred method to manage media content. The flexibility, rich metadata, and support for multiple file types make Assets a powerful tool for creating engaging and informative product experiences. If you need to manage anything other than simple product images or if you require detailed metadata and flexible organization, Assets are the way to go. ## Working with Assets Assets are managed through [update actions](/api/projects/products.md#update-actions) on Products, Categories, and other resources. The following sections show common patterns for organizing and delivering Assets effectively. ### Add an Asset To add an Asset to a Product Variant, use the `addAsset` update action. The following example adds a PDF product manual: `POST /{projectKey}/products/` with: ```json { "version": 1, "actions": [{ "action": "addAsset", "variantId": 1, "asset": { "sources": [ { "uri": "https://example.org/content/product-manual.pdf", "contentType": "application/pdf" } ], "name": { "en" : "Product Manual" }, "description": { "en" : "The manual for my product." }, "tags": ["manual", "pdf"] } }] } ``` ```java // Create the Asset source AssetSource assetSource = AssetSource .builder() .uri("https://example.org/content/product-manual.pdf") .contentType("application/pdf") .build(); // Create the AssetDraft with the Asset source AssetDraft assetDraft = AssetDraft .builder() .sources(Arrays.asList(assetSource)) .name(LocalizedString.builder().addValue("en", "Product Manual").build()) .description( LocalizedString .builder() .addValue("en", "The manual for my product.") .build() ) .tags(Arrays.asList("manual", "pdf")) .build(); // Create the ProductUpdate with the current version of the Product and the Add // Asset action ProductUpdate productUpdate = ProductUpdate .builder() .version(1L) .plusActions(actionBuilder -> actionBuilder.addAssetBuilder().asset(assetDraft).variantId(1L) ) .build(); // Post the ProductUpdate and return the updated Product Product updatedProduct = apiRoot .products() .withId("") .post(productUpdate) .executeBlocking() .getBody(); ``` ```ts const updatedProduct = apiRoot .products() .withId({ ID: "" }) .post({ body: { version: 1, actions: [{ action: "addAsset", variantId: 1, asset: { name: { "en": "Product Manual" }, description: { "en": "The manual for my product." }, tags: ["manual", "pdf"], sources: [{ uri: "https://example.org/content/product-manual.pdf", contentType: "application/pdf" }] } }] } }) .execute(); ``` ```php withUri('https://example.org/content/product-manual.pdf') ->withContentType('application/pdf') ->build(); // Create the AssetDraft with the Asset source $assetDraft = AssetDraftBuilder::of() ->withSources(AssetSourceCollection::of()->add($assetSource)) ->withName( LocalizedStringBuilder::of()->put('en', 'Product Manual')->build(), ) ->withDescription( LocalizedStringBuilder::of() ->put('en', 'The manual for my product.') ->build(), ) ->withTags(['manual', 'pdf']) ->build(); // Create the ProductUpdate with the current version of the Product and the Add Asset action $productUpdate = ProductUpdateBuilder::of() ->withVersion(1) ->withActions( ProductUpdateActionCollection::of()->add( ProductAddAssetActionBuilder::of() ->withAsset($assetDraft) ->withVariantId(1) ->build(), ), ) ->build(); // Post the ProductUpdate and return the updated Product $updatedProduct = $apiRoot ->products() ->withId('') ->post($productUpdate) ->execute(); ``` ```cs // Create the Asset source var assetSource = new AssetSource() { Uri = "https://example.org/content/product-manual.pdf", ContentType = "application/pdf" }; // Create the AssetDraft with the Asset source var assetDraft = new AssetDraft() { Sources = new List { assetSource }, Name = new LocalizedString { { "en", "Product Manual" } }, Description = new LocalizedString { { "en", "The manual for my product." } }, Tags = new List { "manual", "pdf" } }; // Create the ProductUpdate with the current version of the Product and the Add Asset action var productUpdate = new ProductUpdate() { Version = 1, Actions = new List { new ProductAddAssetAction() { VariantId = 1, Asset=assetDraft } } }; // Post the ProductUpdate and return the updated Product var updatedProduct = projectApiRoot .Products() .WithId("") .Post(productUpdate) .ExecuteAsync(); ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/products/{product-id} -d @- << EOF { "version": 1, "actions": [{ "action": "addAsset", "variantId": 1, "asset": { "sources": [ { "uri": "https://example.org/content/product-manual.pdf", "contentType": "application/pdf" } ], "name": { "en" : "Product Manual" }, "description": { "en" : "The manual for my product." }, "tags": ["manual", "pdf"] } }] } EOF ``` The `name` and `description` fields provide human-readable metadata. The `tags` and optional `contentType` on the source let your frontend decide how to display the Asset. For example, a product details page could search for all Assets tagged as manual and use the `contentType` to render a PDF viewer or a video player. ### Use tags to organize Assets Tags are free-form strings that your frontend can use to filter and select the right Asset from the `assets` array. The following patterns illustrate common use cases. #### Tags for language-specific Assets When a video or document is localized into multiple languages, add a language tag so the frontend can select the correct version based on the user's locale: ```json { "assets": [ { "tags": ["video", "en"], ... }, { "tags": ["video", "de"], ... }, { "tags": ["video", "fr"], ... } ] } ``` #### Tags for aspect ratios When the same Asset exists in landscape and portrait versions, tags let the frontend pick the right layout for the device. For example, a desktop view shows the landscape version while a mobile device shows the portrait version: ```json { "assets": [ { "tags": ["image", "primary", "landscape"], ... }, { "tags": ["image", "primary", "portrait"], ... } ] } ``` #### Tags for 360-degree images A 360-degree view consists of multiple images shot at incremental angles. Tags identify which Assets belong to the sequence and in what order: ```json { "assets": [ { "tags": ["image"], ... }, { "tags": ["360-image", "deg=0"], ... }, { "tags": ["360-image", "deg=10"], ... }, { "tags": ["360-image", "deg=20"], ... } ] } ``` The frontend filters for all Assets with the `360-image` tag and sorts them by the degree value to assemble the interactive view. ### Use Asset Sources for multiple formats An [AssetSource](/api/types.md#assetsource) represents a single Asset in a specific format, for example, an image in a particular resolution or a video in a particular encoding. Each source can include a `key`, `dimensions`, and `contentType` so your frontend can select the optimal format. #### Asset Sources for different resolutions In this example, a product image is available as a thumbnail, a regular-resolution version, and a high-resolution zoom version: ```json { "assetId": "", "name": { "en" : "My Product Image" }, "sources": [ { "uri": "https://example.org/product-full.jpg", "dimensions": {"w": 600, "h": 400}, "key": "full" }, { "uri": "https://example.org/product-thumb.jpg", "dimensions": {"w": 90, "h": 60}, "key": "thumb" }, { "uri": "https://example.org/product-zoom.jpg", "dimensions": {"w": 3000, "h": 2000}, "key": "zoom" } ] } ``` ```java List sourceProductFull = List.of( AssetSource .builder() .uri("https://example.org/product-full.jpg") .dimensions(AssetDimensions.builder().w(600).h(400).build()) .key("full") .build(), AssetSource .builder() .uri("https://example.org/product-thumb.jpg") .dimensions(AssetDimensions.builder().w(90).h(60).build()) .key("thumb") .build(), AssetSource .builder() .uri("https://example.org/product-zoom.jpg") .dimensions(AssetDimensions.builder().w(3000).h(2000).build()) .key("zoom") .build() ); ``` ```ts let sourceProductFull = [{ uri: "https://example.org/product-full.jpg", dimensions: { w: 600, h: 400 }, key: "full" }, { uri: "https://example.org/product-thumb.jpg", dimensions: { w: 90, h: 60 }, key: "thumb" }, { uri: "https://example.org/product-zoom.jpg", dimensions: { w: 3000, h: 2000 }, key: "zoom" }]; ``` ```php add( AssetSourceBuilder::of() ->withUri('https://example.org/product-full.jpg') ->withDimensions( AssetDimensionsBuilder::of()->withW(600)->withH(400)->build(), ) ->withKey('full') ->build(), ) ->add( AssetSourceBuilder::of() ->withUri('https://example.org/product-thumb.jpg') ->withDimensions( AssetDimensionsBuilder::of()->withW(90)->withH(60)->build(), ) ->withKey('thumb') ->build(), ) ->add( AssetSourceBuilder::of() ->withUri('https://example.org/product-zoom.jpg') ->withDimensions( AssetDimensionsBuilder::of()->withW(3000)->withH(2000)->build(), ) ->withKey('zoom') ->build(), ); ``` ```cs var sourceProductFull = new List{ new AssetSource() { Uri = "https://example.org/product-full.jpg", Dimensions = new AssetDimensions { W = 600, H = 400 }, Key = "full" }, new AssetSource() { Uri = "https://example.org/product-thumb.jpg", Dimensions = new AssetDimensions { W = 90, H = 60 }, Key = "thumb" }, new AssetSource() { Uri = "https://example.org/product-zoom.jpg", Dimensions = new AssetDimensions { W = 3000, H = 2000 }, Key = "zoom" } }; ``` The frontend can use the `key` to find a specific source (for example, `"thumb"`) or compare `dimensions` to choose the best resolution for the current display. You can also use the `dimensions` field to generate the HTML `srcset` attribute for responsive images: ```html ``` #### Asset Sources for different video encodings When a product video is encoded in multiple formats (for example, MP4, WebM, and HTTP Live Streaming (HLS)), each encoding is stored as a separate source: ```json { "assetId": "", "name": { "en": "My Product Video" }, "sources": [ { "uri": "https://example.org/product-video.mp4", "key": "mp4", "contentType": "video/mp4" }, { "uri": "https://example.org/product-video.webm", "key": "webm", "contentType": "video/webm" }, { "uri": "https://example.org/product-video.m3u8", "key": "hls", "contentType": "application/x-mpegURL" }, { "uri": "https://example.org/product-video-thumb.jpg", "key": "thumb", "contentType": "image/jpeg" } ] } ``` In HTML5, the `