Custom subdomain for assets?

Hi, I’d like to create a custom subdomain for my assets.
Is this is as simple as creating a CNAME for the subdomain and pointing it to

So for example I would like

to point to

Or is there something extra I would need to do to achieve this?

Hello @jacktcunningham_publisher

A CNAME would not work, unfortunately.

You could however do that by setting up a reverse proxy on your (sub)domain to point to our domain. Using something like nginx you can do that with ease, and if you want to implement some caching on it, you can use varnish (or even nginx itself)

Or, if you want to completely replace the domain and self host your assets on a custom server/domain you can see our options here, both subject to a fee:
Custom AWS S3 storage - DatoCMS
Custom Google Cloud Storage - DatoCMS

I have, the same problem,

You’re saying that a simple CNAME won’t work,
Even if it is proxied on Cloudflare?
This would act as a reverse proxy automatically i think.

replacing the in the example with

In case a Worker is needed, could you provide an implementation to do it?

Thank You

Can I ask, please, why you need to serve the assets from a custom domain, just to better understand your use case?

To answer your question, though, a CNAME isn’t quite the same thing as a reverse proxy, unfortunately. Cloudflare won’t let you CNAME to another account’s domain.

You CAN do this with a Cloudflare Worker, but beware that doing so can subject you to additional billing from Cloudflare, since you’re putting DatoCMS’s images behind another layer, paying for Workers invocations and possibly caching fees, etc.

To fetch without caching, see this example:
Custom Domain with Images · Cloudflare Workers docs

If you want to cache DatoCMS’s images behind Cloudflare, probably something like this example is what you’d want: Cache using fetch · Cloudflare Workers docs

But doing so means you would also have to separately manage Cloudflare’s cache and invalidations, which can be tricky.

The reason is that i want to cache the assets, cause this website has millions of visits,
and caching can prevent imgix and dato traffic/ costs to rise.

1 Like

@l.ponticelli, just wanted to follow up on this. Were you able to get the caching working as you wanted with the Cloudflare Workers examples I provided?

Hi @roger, Yes! Thank you for your hints.
Here is a small summary on how i solved this issue:

I’have made a Cloudflare Worker to proxy DatoCMS’s assets and used its fetch caching capabilities.
Then I have created a CNAME with a subdomain ( ) that point to the worker.

I used that subdomain inside the site, replacing the “datocms/projectid” part of url for images and files.
So all assets are now passing through the worker, that respond with the cached version if is a hit or with the original url instead.

Here the code of the worker i’m using, hope will help someone with similar problems.

export default {
  async fetch(request, env, ctx) {

    try {
    const maxAge = 31536000 //a year in seconds ;
    const projectId= "YOUR-DATOCMS-PROJECT-ID";
     const serviceUrl = `${projectId}`

      //current url
      const url = new URL(request.url);
      let response=null ;
      //compose source url
      const path  = url.pathname ? url.pathname: ""
      const qs  =  decodeURIComponent( : "";
      const originUrl = `${serviceUrl}${path}${qs}`;

      //get it from origin
      response = await fetch(originUrl, {cf: {
        // Always cache this fetch regardless of content type
        // for a max of TOT seconds before revalidating the resource
        cacheTtl: maxAge,
        cacheEverything: true,

      // Must use Response constructor to inherit all of response's fields
      response = new Response(response.body, response);
      // Cache API respects Cache-Control headers. Setting s-max-age to 10
      // will limit the response to be in cache for 10 seconds max

      // Any changes made to the response here will be reflected in the cached value
      //reverse proxy
      response.headers.append("Cache-Control", `s-maxage=${maxAge}`);
      return response;

    } catch (error) {
      return new Response("Error thrown " + error.message);
1 Like

@l.ponticelli , glad that worked, and thanks for sharing that code! I love it when users on the forum here help each other :slight_smile:

Hi @l.ponticelli ,

I have just a doubt. I see that CloudFlare examples about the usage of fetch in workers often reuse the incoming request: like this one. Simplifying a bit:

export default {
  async fetch(request) {
    // `fetch` is called passing down the incoming `request`
    let response = await fetch(request, {
     // Some other options

    return response;

I took that as a best practice: that way, the headers present in the request are used for the fetch: that makes sense to me because those headers can include Cache-Control headers and others, which can be helpful to the server receiving the request.

In your case, you could try reusing the request headers like this:

response = await fetch(originUrl, {
  cf: {
    cacheTtl: maxAge,
    cacheEverything: true,
  headers: request.headers,

What do you think?


@sistrall I guess You’re right , I will definitely add incoming request headers as you suggest, thanks.

1 Like