> ## Documentation Index
> Fetch the complete documentation index at: https://cloudinary.com/documentation/llms.txt
> Use this file to discover all available pages before exploring further.

# CDN delivery options


Cloudinary incorporates robust Content Delivery Network (CDN) features to efficiently distribute media assets across the web. With strategically located edge servers worldwide, including leading CDNs such as Akamai, Fastly, and Cloudflare, Cloudinary reduces latency and improves overall performance of images, videos, and other media. 

This section delves into various special CDN delivery options provided by Cloudinary that enable you to further customize content delivery strategies based on your technical requirements and preferences.

## Asset versions

By default, Cloudinary assigns a randomly generated unique public ID to each uploaded media asset. Alternatively, you can either define your own custom public ID or one based on the original file name of the uploaded image. If you upload an image with a public ID that already exists, the file will be overwritten. Keep in mind though that the CDN may already contain a previously cached copy of the older image.

To force the CDN to display the latest uploaded image, you can add a version component to Cloudinary's URLs. The version value is returned by Cloudinary as part of the response of the [image upload](upload_images), and represents the timestamp of the upload. Adding the version component to URLs can be done by setting the `version` parameter (`v` in URLs), for example:

![sample image with version](https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg "thumb: w_400, version:1312461204")

```nodejs
cloudinary.image("sample.jpg")
```

```react
new CloudinaryImage("sample.jpg").setVersion(1312461204);
```

```vue
new CloudinaryImage("sample.jpg").setVersion(1312461204);
```

```angular
new CloudinaryImage("sample.jpg").setVersion(1312461204);
```

```js
new CloudinaryImage("sample.jpg").setVersion(1312461204);
```

```python
CloudinaryImage("sample.jpg").image()
```

```php
(new ImageTag('sample.jpg'))
	->version(1312461204);
```

```java
cloudinary.url().transformation(new Transformation().imageTag("sample.jpg");
```

```ruby
cl_image_tag("sample.jpg")
```

```csharp
cloudinary.Api.UrlImgUp.BuildImageTag("sample.jpg")
```

```dart
cloudinary.image('sample.jpg').transformation(Transformation()
	.version(1312461204));
```

```swift
imageView.cldSetImage(cloudinary.createUrl().generate("sample.jpg")!, cloudinary: cloudinary)
```

```android
MediaManager.get().url().transformation(new Transformation().generate("sample.jpg");
```

```flutter
cloudinary.image('sample.jpg').transformation(Transformation()
	.version(1312461204));
```

```kotlin
cloudinary.image {
	publicId("sample.jpg")
	 version(1312461204) 
}.generate()
```

```jquery
$.cloudinary.image("sample.jpg")
```

```react_native
new CloudinaryImage("sample.jpg").setVersion(1312461204);
```

If you use a version number in the URL that's higher than the asset's upload time, Cloudinary sets the cache duration to only five minutes, which isn't ideal for performance. For best caching results, always use the correct version number provided by Cloudinary.

### SDK placeholder for subfolders

When a public ID consists of a path with slashes (a folder path in fixed-folder mode) and you don't explicitly include a version component, some Cloudinary SDKs automatically insert a placeholder version segment of `/v1/` in the delivery URL. This ensures Cloudinary parses the URL unambiguously (distinguishing the transformation component from the public ID path).

If you include the actual version value (v + the UNIX timestamp returned by the upload response), the SDK uses that value instead. The `/v1/` placeholder isn't tied to the asset's upload time.

**No explicit version supplied (asset in a subfolder):**

`https://res.cloudinary.com/<cloud_name>/image/upload/v1/products/shoes/boot.jpg`

**Explicit version supplied (from upload response):**

`https://res.cloudinary.com/<cloud_name>/image/upload/v1640995200/products/shoes/boot.jpg`

### Cache invalidation alternative

As an alternative to using versions, you can set the `invalidate` parameter to `true` while uploading a new image in order to invalidate the previous image throughout the CDN. It usually takes between a few seconds and a few minutes for the invalidation to fully propagate through the CDN, while the `version` component takes effect immediately. 

> **NOTES**:
>
> * There are also a number of other important considerations when using the invalidate functionality. For details on invalidating media assets, see [Invalidating cached media assets on the CDN](invalidate_cached_media_assets_on_the_cdn). 

> * None of the segments of the `folder` value can start with 'v' followed by numeric characters when using the legacy fixed folder mode, or at the start of a component in a `public_id_prefix` for dynamic folder mode. This is because 'v' is reserved for the version segment of the URL.

## Private CDNs and custom delivery hostnames (CNAMEs)

The default Cloudinary shared CDN distribution is `https://res.cloudinary.com`. Thus, the default delivery URL for Cloudinary product environments uses the format:

`https://res.cloudinary.com/<cloud_name>/...`

Cloudinary also supports **private CDN distribution** configurations. In this case, your asset delivery URL will follow the format:

`https://<cloud_name>-res.cloudinary.com/...`

For example:

`https://demo-res.cloudinary.com/image/upload/sample.jpg` 

Additionally, Cloudinary supports defining a **custom delivery hostname** (also known as a **CNAME**) for relevant customers. In this case, your asset delivery URL will follow the format:

`https://<your custom delivery hostname>/...`

For example:

`https://www.example.com/image/upload/sample.jpg`

> **NOTE**: Private CDN distributions and CNAMEs are available only for Cloudinary's [Advanced plan](https://cloudinary.com/pricing) and higher, and require a small setup on Cloudinary's side. Custom delivery hostnames (CNAMEs) also entail an additional cost. [Contact us](https://support.cloudinary.com/hc/en-us/requests/new) for more details.

### SDK configuration for private CDNs and custom delivery hostnames (CNAMEs)
By default, when you use Cloudinary SDKs to generate delivery or transformation URLs, they generate them in the default format.

If you are using a private CDN or custom delivery hostname, make sure to define the `private_cdn` or `secure_distribution`/`cname` configuration parameter, so that the SDK will generate your URLs correctly.

For details, see [Configuration parameters](cloudinary_sdks#configuration_parameters) as well as the Installation and Configuration section of the relevant [SDK guide](cloudinary_sdks).

### Custom favicons

If your product environment is configured with a private CDN or custom delivery hostname (CNAME), you can also set up a custom **favicon**. This favicon will be displayed whenever anyone views the assets you deliver in a separate tab. For example, note the custom  logo in the browser tab of the following image: 
![Sample favicon](https://res.cloudinary.com/demo/image/upload/bo_1px_solid_gray/f_auto,q_auto/docs/jackie-favicon.png "thumb: w_285,dpr_2, width:285, with_code:false, with_url:false")

To set your custom favicon, upload a public **.ico** file called **favicon.ico** to the **root** folder of your product environment.

If you don't set a custom favicon, the default Cloudinary logo favicon is used.

## Multi-CDN solutions

Different CDN solutions may provide better support for a particular CDN-based functionality that is important for your website or application, such as quick [invalidations](invalidate_cached_media_assets_on_the_cdn), [video streaming capabilities](video_manipulation_and_delivery#streaming_videos_using_urls), and more. 

Alternatively, you may find that one CDN service best serves certain geographical markets, while another is significantly better for other locations. The same may be true for the same geography, but at different times of the day, or for a variety of other variables. Therefore, you could potentially optimize your page load time for each user by using different CDN services for different user requests.  

If your account is on an [Enterprise plan](https://cloudinary.com/pricing#pricing-enterprise), you can optionally take advantage of one of the following solutions to ensure that your resources are delivered via the CDN that bests fits the needs of your application and your users:

* **Dynamic multi-CDN switching**: Uses real-time data to automatically select the best performing or most appropriate of the supported CDN services for every user request. This automated CDN switching service routes users based on geography, HTTP round-trip-time (RTT), and a variety of other variables, and directs each user to a specific web server based on intersections between server load and geography, while avoiding CDNs that are experiencing temporary outages or lowered availability. It gathers this data on an ongoing basis so that it is ready to make the best real-time routing decision each time a request comes in.

	* Take a look at the [Multi-CDN Demo page](https://demo.cloudinary.com/multi-cdn) to see how long the same resource takes to deliver to your browser or to a variety of geographies via different CDN services, and how quickly you can deliver that same resource via the multi-CDN switching feature.

	* This service includes real-time monitoring that can provide insights about how improvements in your page load time influence user behavior KPIs such as visit length and conversions.

* **Smart CDN selection**: Cloudinary experts help you determine which of the supported CDN services is the best fit for your required features and target audience. In some cases, it may even be determined that certain types of resources should be delivered through one CDN service, and the remainder through another. Additionally, if it's determined in the future that another CDN service could better serve your content, Cloudinary transparently implements the new configuration and mapping behind the scenes. 

In both of the above options, Cloudinary handles the required CDN configurations and the mappings between CDN domains and the public Cloudinary domain, so that the resource URLs in your site or app never need to change.

These multi-CDN features are available only to Cloudinary customers with [Enterprise plan](https://cloudinary.com/pricing#pricing-enterprise) accounts, and the dynamic multi-CDN switching feature affects pricing. [Contact our Enterprise support and sales team](https://cloudinary.com/contact?plan=enterprise) or your CSM for additional details.

## Fast API

> **INFO**: This feature is only available for Enterprise Customers at an additional cost. Please [contact support](https://support.cloudinary.com/hc/en-us/requests/new) to enable this feature.

In general, repository operations like upload, data enhancement, search, administration, and delivery of assets operate from regional data centers in the US, EU, and AP. By default, only Cloudinary delivery operates to local CDNs, and for companies with a global user base spread across different regions, other repository operations that operate through far away data centers using default network protocols can be slow and unacceptable. 

To address this issue, the Fast API feature provides network traffic acceleration to and from their data centers. Fast API utilizes the capabilities of a Content Delivery Network (CDN) and customers are able to connect to the closest Point of Presence (PoP) within the CDN. Enhanced internal connectivity between the CDN and Cloudinary backend provides an extra performance enhancement.

By default, all our APIs receive improved performance, and this would be noticeable in the case of the Upload API, particularly when making large uploads in chunks. For other APIs, the benefits may be less noticeable, unless a specific endpoint is called frequently with high concurrency. 

### Using Fast API

To enable the feature you need to set the `upload_prefix` [configuration parameter](cloudinary_sdks#configuration_parameters) when configuring your Cloudinary SDK to:

```
https://<cloud-name>.api-fast.cloudinary.com
```

For customers using the EU or AP data centers, use the specific hostname, respectively:

```
https://<cloud-name>.api-fast-eu.cloudinary.com
// OR
https://<cloud-name>.api-fast-ap.cloudinary.com
```

## SEO-friendly media asset URLs

Image and video URLs are specified in the source code of HTML pages and are leveraged by search engines to understand the media asset's content. Concise and descriptive image and video file names are better for search engines to extract information about a media file, therefore supporting your site's SEO ranking. 

SEO starts with the ability to define custom [public IDs while uploading media assets](upload_parameters#public_id) which can be as descriptive as necessary. Cloudinary can help make your image and video URLs more SEO friendly in a few other ways: 

* [Root path URLs](#root_path_urls)
* [Dynamic SEO suffixes](#dynamic_seo_suffixes)

> **TIP**: If you're on the [Advanced plan](https://cloudinary.com/pricing) or higher, you can also make your URLs more SEO friendly by using a [custom delivery hostname (CNAME)](#private_cdns_and_custom_delivery_hostnames_cnames) for your URLs instead of the shared `res.cloudinary.com`. The dynamic SEO suffix and CNAME features can also be used together, for example:

`https://<mydomain.com>/images/t8sn7wg4jd74j/basktetball-game.jpg`

For more information on creating SEO friendly URLs, see the article on [How to dynamically create SEO-friendly URLs for your site's images](/blog/how_to_dynamically_create_seo_friendly_urls_for_your_site_s_images).

### Root path URLs

The Root Path URL feature allows you to create shorter image URLs for delivering uploaded images. The default Cloudinary resource delivery URL includes the `resource type` and `type` parameters, for example, `/image/upload`. With Cloudinary's Root Path URL feature, the resource type and type parameters are set to the default values 'image' and 'upload' respectively, which means that any URL without the resource type and type parameters will automatically default to using those values. 

For example, the default delivery URL for the `sample` image in JPEG format is normally:

`https://res.cloudinary.com/demo/image/upload/sample.jpg`

The delivery URL using the Root Path URL feature for image uploads is:

`https://res.cloudinary.com/demo/sample.jpg`

Both the URLs above deliver the same uploaded image.

### Dynamic SEO suffixes

The dynamic SEO suffixes feature allows you to dynamically add a descriptive suffix to the public ID in the delivery URL. This can be useful:

* If the asset was not given a suitable public ID during the upload process.
* To support different languages for describing a single asset.
* To reflect specific content on certain pages.

> **NOTE**: Using this feature when delivering a URL does not incur additional [transformation counts](transformation_counts) as the suffix is resolved on the CDN layer. 

Even when transformations are applied, if you deliver the identical transformation URL multiple times, where only the SEO suffix differs, then only the first delivered (or eagerly generated) of those URLs, regardless of SEO suffix, is charged for initially generating the transformed asset.

To add a dynamic SEO suffix, the **asset type** and **delivery type** elements of the URL need to be merged into a single component: 

* For assets stored using the `upload` delivery type: 
  * replace `/image/upload` with `/images`  
  * replace `/video/upload` with `/videos`
  * replace `/raw/upload` with `/files`
* For private **image** uploads, replace `/image/private` with `/private_images`
* For authenticated **image** uploads, replace `image/authenticated` with `/authenticated_images`.
  
Afterwards, any custom suffix can then be dynamically appended to the public ID by adding a slash (`/`) and the SEO name.

> **NOTE**:
>
> Currently, private and authenticated **videos** do not support SEO suffixes.

For example the default delivery URL for the `t8sn7wg4jd74j` image in JPEG format is:

`https://demo-res.cloudinary.com/image/upload/t8sn7wg4jd74j.jpg`

The delivery URL with the suffix `basketball-game` added to the public ID is:

`https://demo-res.cloudinary.com/images/t8sn7wg4jd74j/basketball-game.jpg`

In the URL below, the same image is given a suffix in Spanish:

`https://demo-res.cloudinary.com/images/t8sn7wg4jd74j/baloncesto-juego.jpg`

All the URLs above deliver the same uploaded image.

#### Generating URLs with SEO suffixes using SDKs

To generate a URL with an SEO suffix using an SDK, pass the relevant `url_suffix`/`suffix` configuration parameter. The SDK will then generate the URL with correct merged asset type & delivery type component and append the specified suffix. 

The example below shows a URL with multiple chained transformations and a Spanish SEO suffix. 

![Spanish SEO suffix](https://res.cloudinary.com/demo/images/ar_1.0,c_thumb,g_face,w_0.6,z_0.7/r_max/co_brown,e_outline/t8sn7wg4jd74j/baloncesto-juego.jpg "thumb: w_250")

```nodejs
cloudinary.image("t8sn7wg4jd74j.jpg", {url_suffix: "baloncesto-juego", transformation: [
  {aspect_ratio: "1.0", gravity: "face", width: "0.6", zoom: "0.7", crop: "thumb"},
  {radius: "max"},
  {color: "brown", effect: "outline"}
  ]})
```

```react
new CloudinaryImage("t8sn7wg4jd74j.jpg")
  .resize(
    thumbnail()
      .width(0.6)
      .aspectRatio("1.0")
      .zoom(0.7)
      .gravity(focusOn(face()))
  )
  .roundCorners(max())
  .effect(outline().color("brown"))
  .setSuffix("baloncesto-juego");
```

```vue
new CloudinaryImage("t8sn7wg4jd74j.jpg")
  .resize(
    thumbnail()
      .width(0.6)
      .aspectRatio("1.0")
      .zoom(0.7)
      .gravity(focusOn(face()))
  )
  .roundCorners(max())
  .effect(outline().color("brown"))
  .setSuffix("baloncesto-juego");
```

```angular
new CloudinaryImage("t8sn7wg4jd74j.jpg")
  .resize(
    thumbnail()
      .width(0.6)
      .aspectRatio("1.0")
      .zoom(0.7)
      .gravity(focusOn(face()))
  )
  .roundCorners(max())
  .effect(outline().color("brown"))
  .setSuffix("baloncesto-juego");
```

```js
new CloudinaryImage("t8sn7wg4jd74j.jpg")
  .resize(
    thumbnail()
      .width(0.6)
      .aspectRatio("1.0")
      .zoom(0.7)
      .gravity(focusOn(face()))
  )
  .roundCorners(max())
  .effect(outline().color("brown"))
  .setSuffix("baloncesto-juego");
```

```python
CloudinaryImage("t8sn7wg4jd74j.jpg").image(url_suffix="baloncesto-juego", transformation=[
  {'aspect_ratio': "1.0", 'gravity': "face", 'width': "0.6", 'zoom': "0.7", 'crop': "thumb"},
  {'radius': "max"},
  {'color': "brown", 'effect': "outline"}
  ])
```

```php
(new ImageTag('t8sn7wg4jd74j.jpg'))
	->resize(Resize::thumbnail()->width(0.6)
->aspectRatio(1.0)
->zoom(0.7)
	->gravity(
	Gravity::focusOn(
	FocusOn::face()))
	)
	->roundCorners(RoundCorners::max())
	->effect(Effect::outline()
	->color(Color::BROWN)
	)
	->suffix("baloncesto-juego");
```

```java
cloudinary.url().transformation(new Transformation()
  .aspectRatio("1.0").gravity("face").width(0.6).zoom(0.7).crop("thumb").chain()
  .radius("max").chain()
  .color("brown").effect("outline")).suffix("baloncesto-juego").imageTag("t8sn7wg4jd74j.jpg");
```

```ruby
cl_image_tag("t8sn7wg4jd74j.jpg", url_suffix: "baloncesto-juego", transformation: [
  {aspect_ratio: "1.0", gravity: "face", width: 0.6, zoom: 0.7, crop: "thumb"},
  {radius: "max"},
  {color: "brown", effect: "outline"}
  ])
```

```csharp
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .AspectRatio("1.0").Gravity("face").Width(0.6).Zoom(0.7).Crop("thumb").Chain()
  .Radius("max").Chain()
  .Color("brown").Effect("outline")).Suffix("baloncesto-juego").BuildImageTag("t8sn7wg4jd74j.jpg")
```

```dart
cloudinary.image('t8sn7wg4jd74j.jpg').transformation(Transformation()
	.resize(Resize.thumbnail().width(0.6)
.aspectRatio('1.0')
.zoom(0.7)
	.gravity(
	Gravity.focusOn(
	FocusOn.face()))
	)
	.roundCorners(RoundCorners.max())
	.effect(Effect.outline()
	.color(Color.BROWN)
	)
	.urlSuffix("baloncesto-juego"));
```

```swift
imageView.cldSetImage(cloudinary.createUrl().setSuffix( "baloncesto-juego").setTransformation(CLDTransformation()
  .setAspectRatio("1.0").setGravity("face").setWidth(0.6).setZoom(0.7).setCrop("thumb").chain()
  .setRadius("max").chain()
  .setColor("brown").setEffect("outline")).generate("t8sn7wg4jd74j.jpg")!, cloudinary: cloudinary)
```

```android
MediaManager.get().url().transformation(new Transformation()
  .aspectRatio("1.0").gravity("face").width(0.6).zoom(0.7).crop("thumb").chain()
  .radius("max").chain()
  .color("brown").effect("outline")).suffix("baloncesto-juego").generate("t8sn7wg4jd74j.jpg");
```

```flutter
cloudinary.image('t8sn7wg4jd74j.jpg').transformation(Transformation()
	.addTransformation("ar_1.0,c_thumb,g_face,w_0.6,z_0.7/r_max/co_brown,e_outline")
	.urlSuffix("baloncesto-juego"));
```

```kotlin
cloudinary.image {
	publicId("t8sn7wg4jd74j.jpg")
	 resize(Resize.thumbnail() { width(0.6F)
 aspectRatio(1.0F)
 zoom(0.7F)
	 gravity(
	Gravity.focusOn(
	FocusOn.face()))
	 })
	 roundCorners(RoundCorners.max())
	 effect(Effect.outline() {
	 color(Color.BROWN)
	 })
	 urlSuffix("baloncesto-juego") 
}.generate()
```

```jquery
$.cloudinary.image("t8sn7wg4jd74j.jpg", {url_suffix: "baloncesto-juego", transformation: [
  {aspect_ratio: "1.0", gravity: "face", width: "0.6", zoom: "0.7", crop: "thumb"},
  {radius: "max"},
  {color: "brown", effect: "outline"}
  ]})
```

```react_native
new CloudinaryImage("t8sn7wg4jd74j.jpg")
  .resize(
    thumbnail()
      .width(0.6)
      .aspectRatio("1.0")
      .zoom(0.7)
      .gravity(focusOn(face()))
  )
  .roundCorners(max())
  .effect(outline().color("brown"))
  .setSuffix("baloncesto-juego");
```

## Error handling

If you have a problem when accessing a Cloudinary transformation URL (e.g., a blank result in your browser), it might be a simple matter of using the wrong transformation syntax. To understand more, check the **X-Cld-Error** HTTP response header which is where Cloudinary reports any errors it encounters (e.g., invalid syntax, invalid parameters, limit issues, etc.).

For example, trying to access the following URL would result in a `X-Cld-Error: Invalid width - abc` error, as the width parameter is used with an integer value and not a string:

`https://res.cloudinary.com/demo/image/upload/w_abc/sample.jpg`

To view the X-Cld-Error header on a Chrome browser for example, select **Developers Tools** from the **View** menu. Then select the **Network** tab, refresh your page, click on the image name with the 400 status code and look for **X-Cld-Error** under **Response Headers**.

### X-Cld-Error inspector tool

You can use the interactive tool below to inspect the X-Cld-Error header for any Cloudinary transformation URL. Paste a Cloudinary transformation URL and click "Inspect Headers" to see if there are any errors reported in the response headers.

  
    Cloudinary URL:
    
  
  
    Inspect Headers
  
  
    
      ⏳ Inspecting URL...
    
    
  

> **NOTE**: If the tool fails to retrieve headers, you can inspect them manually using your browser's Developer Tools Network tab.

### Error Report

Accounts on the [Advanced plan](https://cloudinary.com/pricing) or higher can also use the Cloudinary Console to see errors from API calls or delivery URL requests. 

To access the Error Report, go to **[Home > Error Reports](https://console.cloudinary.com/app/home/error-reports)**. For more details, see [Error reports](programmable_media_asset_usage_data#error_reports).

### Using a default image placeholder

Default images can be used in the case that a requested image does not exist. For example, a site that automatically stores user profile pictures with the same name as the user themselves, allowing you to reference the pictures by user name (unless the user has not uploaded a profile picture yet). 
Specify a default image to use with the `default_image` parameter (`d` in URLs) and the public ID + format of a previously uploaded image, for example, `d_placeholder.png` to use the image with the public ID of `placeholder` as the default image. Any requested transformations are also applied on the default image as well.

For example, to use the PNG image called `avatar` as a default image in the case that the image called `non_existing_id` does not exist:

![avatar used as default image when requested image does not exist](https://res.cloudinary.com/demo/image/upload/d_avatar.png/non_existing_id.png)

```nodejs
cloudinary.image("non_existing_id.png", {default_image: "avatar.png"})
```

```react
new CloudinaryImage("non_existing_id.png").delivery(defaultImage("avatar.png"));
```

```vue
new CloudinaryImage("non_existing_id.png").delivery(defaultImage("avatar.png"));
```

```angular
new CloudinaryImage("non_existing_id.png").delivery(defaultImage("avatar.png"));
```

```js
new CloudinaryImage("non_existing_id.png").delivery(defaultImage("avatar.png"));
```

```python
CloudinaryImage("non_existing_id.png").image(default_image="avatar.png")
```

```php
(new ImageTag('non_existing_id.png'))
	->delivery(Delivery::defaultImage("avatar.png"));
```

```java
cloudinary.url().transformation(new Transformation().defaultImage("avatar.png")).imageTag("non_existing_id.png");
```

```ruby
cl_image_tag("non_existing_id.png", default_image: "avatar.png")
```

```csharp
cloudinary.Api.UrlImgUp.Transform(new Transformation().DefaultImage("avatar.png")).BuildImageTag("non_existing_id.png")
```

```dart
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.delivery(Delivery.defaultImage("avatar.png")));
```

```swift
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setDefaultImage("avatar.png")).generate("non_existing_id.png")!, cloudinary: cloudinary)
```

```android
MediaManager.get().url().transformation(new Transformation().defaultImage("avatar.png")).generate("non_existing_id.png");
```

```flutter
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.delivery(Delivery.defaultImage("avatar.png")));
```

```kotlin
cloudinary.image {
	publicId("non_existing_id.png")
	 delivery(Delivery.defaultImage("avatar.png")) 
}.generate()
```

```jquery
$.cloudinary.image("non_existing_id.png", {default_image: "avatar.png"})
```

```react_native
new CloudinaryImage("non_existing_id.png").delivery(defaultImage("avatar.png"));
```

The same example as above, but with transformation parameters applied to scale down to 200 pixels wide and rotate by 45 degrees:

![avatar used as default image when requested image doesn't exist](https://res.cloudinary.com/demo/image/upload/a_45/c_scale,w_200/d_avatar.png/non_existing_id.png)

```nodejs
cloudinary.image("non_existing_id.png", {transformation: [
  {angle: 45},
  {width: 200, crop: "scale"},
  {default_image: "avatar.png"}
  ]})
```

```react
new CloudinaryImage("non_existing_id.png")
  .rotate(byAngle(45))
  .resize(scale().width(200))
  .delivery(defaultImage("avatar.png"));
```

```vue
new CloudinaryImage("non_existing_id.png")
  .rotate(byAngle(45))
  .resize(scale().width(200))
  .delivery(defaultImage("avatar.png"));
```

```angular
new CloudinaryImage("non_existing_id.png")
  .rotate(byAngle(45))
  .resize(scale().width(200))
  .delivery(defaultImage("avatar.png"));
```

```js
new CloudinaryImage("non_existing_id.png")
  .rotate(byAngle(45))
  .resize(scale().width(200))
  .delivery(defaultImage("avatar.png"));
```

```python
CloudinaryImage("non_existing_id.png").image(transformation=[
  {'angle': 45},
  {'width': 200, 'crop': "scale"},
  {'default_image': "avatar.png"}
  ])
```

```php
(new ImageTag('non_existing_id.png'))
	->rotate(Rotate::byAngle(45))
	->resize(Resize::scale()->width(200))
	->delivery(Delivery::defaultImage("avatar.png"));
```

```java
cloudinary.url().transformation(new Transformation()
  .angle(45).chain()
  .width(200).crop("scale").chain()
  .defaultImage("avatar.png")).imageTag("non_existing_id.png");
```

```ruby
cl_image_tag("non_existing_id.png", transformation: [
  {angle: 45},
  {width: 200, crop: "scale"},
  {default_image: "avatar.png"}
  ])
```

```csharp
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Angle(45).Chain()
  .Width(200).Crop("scale").Chain()
  .DefaultImage("avatar.png")).BuildImageTag("non_existing_id.png")
```

```dart
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.rotate(Rotate.byAngle(45))
	.resize(Resize.scale().width(200))
	.delivery(Delivery.defaultImage("avatar.png")));
```

```swift
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setAngle(45).chain()
  .setWidth(200).setCrop("scale").chain()
  .setDefaultImage("avatar.png")).generate("non_existing_id.png")!, cloudinary: cloudinary)
```

```android
MediaManager.get().url().transformation(new Transformation()
  .angle(45).chain()
  .width(200).crop("scale").chain()
  .defaultImage("avatar.png")).generate("non_existing_id.png");
```

```flutter
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.rotate(Rotate.byAngle(45))
	.resize(Resize.scale().width(200))
	.delivery(Delivery.defaultImage("avatar.png")));
```

```kotlin
cloudinary.image {
	publicId("non_existing_id.png")
	 rotate(Rotate.byAngle(45))
	 resize(Resize.scale() { width(200) })
	 delivery(Delivery.defaultImage("avatar.png")) 
}.generate()
```

```jquery
$.cloudinary.image("non_existing_id.png", {transformation: [
  {angle: 45},
  {width: 200, crop: "scale"},
  {default_image: "avatar.png"}
  ]})
```

```react_native
new CloudinaryImage("non_existing_id.png")
  .rotate(byAngle(45))
  .resize(scale().width(200))
  .delivery(defaultImage("avatar.png"));
```

If the public ID of the default image contains forward slashes, replace the slashes with colons (e.g., an image with public ID `docs/placeholders/samples/avatar.png` should be referenced as `d_docs:placeholders:samples:avatar.png`). The intended image should be specified with slashes as normal. For example:

![avatar used as default image when requested image doesn't exist](https://res.cloudinary.com/demo/image/upload/c_scale,w_100/d_docs:placeholders:samples:avatar.png/non_existing_id.png)

```nodejs
cloudinary.image("non_existing_id.png", {transformation: [
  {width: 100, crop: "scale"},
  {default_image: "docs:placeholders:samples:avatar.png"}
  ]})
```

```react
new CloudinaryImage("non_existing_id.png")
  .resize(scale().width(100))
  .delivery(defaultImage("docs:placeholders:samples:avatar.png"));
```

```vue
new CloudinaryImage("non_existing_id.png")
  .resize(scale().width(100))
  .delivery(defaultImage("docs:placeholders:samples:avatar.png"));
```

```angular
new CloudinaryImage("non_existing_id.png")
  .resize(scale().width(100))
  .delivery(defaultImage("docs:placeholders:samples:avatar.png"));
```

```js
new CloudinaryImage("non_existing_id.png")
  .resize(scale().width(100))
  .delivery(defaultImage("docs:placeholders:samples:avatar.png"));
```

```python
CloudinaryImage("non_existing_id.png").image(transformation=[
  {'width': 100, 'crop': "scale"},
  {'default_image': "docs:placeholders:samples:avatar.png"}
  ])
```

```php
(new ImageTag('non_existing_id.png'))
	->resize(Resize::scale()->width(100))
	->delivery(Delivery::defaultImage("docs:placeholders:samples:avatar.png"));
```

```java
cloudinary.url().transformation(new Transformation()
  .width(100).crop("scale").chain()
  .defaultImage("docs:placeholders:samples:avatar.png")).imageTag("non_existing_id.png");
```

```ruby
cl_image_tag("non_existing_id.png", transformation: [
  {width: 100, crop: "scale"},
  {default_image: "docs:placeholders:samples:avatar.png"}
  ])
```

```csharp
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(100).Crop("scale").Chain()
  .DefaultImage("docs:placeholders:samples:avatar.png")).BuildImageTag("non_existing_id.png")
```

```dart
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.resize(Resize.scale().width(100))
	.delivery(Delivery.defaultImage("docs:placeholders:samples:avatar.png")));
```

```swift
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(100).setCrop("scale").chain()
  .setDefaultImage("docs:placeholders:samples:avatar.png")).generate("non_existing_id.png")!, cloudinary: cloudinary)
```

```android
MediaManager.get().url().transformation(new Transformation()
  .width(100).crop("scale").chain()
  .defaultImage("docs:placeholders:samples:avatar.png")).generate("non_existing_id.png");
```

```flutter
cloudinary.image('non_existing_id.png').transformation(Transformation()
	.resize(Resize.scale().width(100))
	.delivery(Delivery.defaultImage("docs:placeholders:samples:avatar.png")));
```

```kotlin
cloudinary.image {
	publicId("non_existing_id.png")
	 resize(Resize.scale() { width(100) })
	 delivery(Delivery.defaultImage("docs:placeholders:samples:avatar.png")) 
}.generate()
```

```jquery
$.cloudinary.image("non_existing_id.png", {transformation: [
  {width: 100, crop: "scale"},
  {default_image: "docs:placeholders:samples:avatar.png"}
  ]})
```

```react_native
new CloudinaryImage("non_existing_id.png")
  .resize(scale().width(100))
  .delivery(defaultImage("docs:placeholders:samples:avatar.png"));
```
> **NOTES**:
>
> * If the requested image does not exist and the default placeholder image is delivered instead, the `x_cld_error` header will also be included in the response.

> * The default placeholder image must be of [type](image_transformations#delivery_types) `upload`, i.e. publicly available.

#### Video tutorial: Image placeholders with JavaScript

Watch this video tutorial to learn how to display placeholders as a fallback when images fail to load using the JavaScript SDK.

  This video is brought to you by Cloudinary's video player - embed your own!Use the controls to set the playback speed, navigate to chapters of interest and select subtitles in your preferred language.

#### Tutorial contents This tutorial presents the following topics. Click a timestamp to jump to that part of the video.
### Why image placeholders matter
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=0 :sec=00 :player=cld} | Broken images are one of the most noticeable ways to create a poor user experience on your site. This is especially common when loading images dynamically—for example, when linking images to product IDs or usernames.
|

### Preventing broken links with placeholders
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=0 :sec=25 :player=cld} | Instead of showing broken image links, Cloudinary lets you use image placeholders. If you're managing images at scale with Cloudinary, you're likely uploading them programmatically via an upload pipeline and referencing them by public ID. If something breaks during that upload process, you could be left with broken image links on your site. Fortunately, Cloudinary offers a `defaultImage` parameter to serve a backup image instead.
|

### Rendering a dynamic image list
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=0 :sec=54 :player=cld} | In the demo, we iterate over a list of image IDs (in this case, letters of the alphabet). In your app, this could be product images, user avatars, or anything else. The code uses the Cloudinary SDK to define the image list, render the images by mapping over them, and construct image tags with optimized URLs. Assuming you've already [installed and configured the JavaScript SDK](javascript_configuration_tutorial), here's the code for iterating and preparing the images for display:
|

```javascript
const images = ["a", "b", "c"]

const imageTags = images.map(image => {
  const srce = cloudinary
    .image('cloudinary-javascript-fallback-demo/${image}')
    .format("auto")
    .quality("auto")
    .resize(scale().width(410))
    .toURL()

  return `img src="${src}" />`
})
```

### What happens when an image is missing
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=1 :sec=32 :player=cld} | When one of the image IDs in the list doesn’t exist in your Cloudinary Media Library (for example, when changing the image public ID from `c` to `see`) the image fails to load, resulting in a broken image. This creates a poor user experience. Fortunately, Cloudinary allows you to configure a default image that's delivered automatically in such cases, ensuring your layout remains visually consistent and user-friendly.
|

### Setting the default image
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=1 :sec=50 :player=cld} | First, upload your fallback image to your Media Library. In the example, the fallback image is called `sea.jpg.` Then, update your code to chain the `.delivery()` method and add the `.defaultImage()` setting. This tells Cloudinary to serve `sea.jpg` whenever one of the requested images is missing.
|

```javascript
const images = ["a", "b", "see"]

const imageTags = images.map(image => {
  const srce = cloudinary
    .image('cloudinary-javascript-fallback-demo/${image}')
    .format("auto")
    .quality("auto")
    .resize(scale().width(410))
    .delivery(defaultImage("cloudinary-javascript-fallback-demo:sea.jpg"))
    .toURL()

  return `img src="${src}" />`
})
```

### Verifying the fallback behavior
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=2 :sec=25 :player=cld} | With this setup, whenever an image is missing, Cloudinary displays the default image. In the demo, changing the image IDs (e.g., from `a` to `x` or `b` to `y`) shows that the placeholder image is used consistently. This ensures that users never encounter a broken image again.
|

