How CMS Assets Works

CMS Assets is built on Cloudflare Workers. The primary flow is to proxy read-only CMS API requests through the same project edge domain that also serves assets:

  • https://your-project.cmsassets.com/~api/... for read-only CMS API calls
  • https://your-project.cmsassets.com/... for assets

That gives you one rollout model for hidden credentials, edge caching, and proxy-ready asset URLs in API responses. If you only need media delivery, the asset proxy can still be used independently.


API request flow

Here's what happens when your app sends a read-only CMS API request:

1. Your app requests:
   https://your-project.cmsassets.com/~api/...

2. Cloudflare Worker receives the request

3. Worker extracts the project identifier from the hostname

4. Worker loads project config → apiOrigin, auth mode, cache TTL, previewBypassParam, transformApiUrls

5. Worker validates:
   - Request method is GET
   - API proxy is configured for the project

6. Worker normalizes the upstream URL:
   - strips /~api
   - removes proxy-only params like preview bypass and parsed toggle
   - sorts query params for stable cache keys

7. Worker checks API cache:
   - Cache HIT → return cached response immediately
   - Cache MISS → fetch upstream CMS API
   - Cache BYPASS → fetch upstream without reading or writing cache

8. On cache miss or bypass, worker injects the stored token server-side

9. If response parsing is enabled, worker rewrites CMS asset URLs in JSON responses to proxy URLs

10. Worker returns the response with cache headers like X-Cache and X-Parsed

Asset request flow

Here's what happens when a browser requests an asset:

1. Browser requests:
   https://your-project.cmsassets.com/path/to/image.png

2. Cloudflare Worker receives the request

3. Worker extracts the project identifier from the hostname

4. Worker loads project config → origin, cache TTL, allowed types, etc.

5. Worker validates:
   - Request method is GET or HEAD
   - User-Agent is not a blocked bot

6. Worker checks edge cache:
   - Cache HIT → return cached response immediately
   - Cache MISS → fetch from origin

7. Worker fetches asset from origin CMS CDN

8. Worker checks the response Content-Type against allowedTypes (if configured)

9. Worker caches the response at the edge

10. Worker returns the asset to the browser

11. Usage is recorded asynchronously — no latency added to the response

Architecture overview

┌──────────┐      ┌─────────────────────┐      ┌──────────────┐
│  Browser  │ ───→ │  Cloudflare Worker   │ ───→ │  Origin CMS  │
│           │ ←─── │  (Edge Proxy)        │ ←─── │  CDN         │
└──────────┘      └─────────────────────┘      └──────────────┘
                     │       │       │
                     │       │       │
               ┌─────┘       │       └──────┐
               ▼             ▼              ▼
         ┌──────────┐  ┌──────────┐  ┌──────────────┐
         │  Edge     │  │  KV      │  │  Nuxt API    │
         │  Cache    │  │  (Config  │  │  (Usage +    │
         └──────────┘  │  + Domain │  │   Billing)   │
                       │  Lookup)  │  └──────────────┘
                       └──────────┘         │
                                            ▼
                                     ┌──────────────┐
                                     │  Cloudflare  │
                                     │  D1 Database │
                                     └──────────────┘
  • Cloudflare Worker — The edge proxy that handles all asset requests. Deployed globally.
  • Edge Cache — Cloudflare's built-in cache. Assets and parsed API responses are cached with separate keys and configurable TTLs.
  • Cloudflare KV — Stores project proxy configuration (proxy:{slug}) and custom domain mappings (domain:{hostname}). This is the only data source the worker reads in the hot path.
  • Nuxt API — Server-side API for project management, billing (Stripe), and usage tracking. Usage data is stored in D1, not KV.

Edge caching

Assets are cached at the Cloudflare edge closest to the user. This means:

  • First request — Cache miss. The worker fetches the asset from the origin CMS CDN, caches it, and returns it.
  • Subsequent requests — Cache hit. The asset is served directly from edge cache with no origin request. Sub-10ms latency.

Cache key

The cache key is built from the full URL path with query parameters sorted alphabetically (so ?w=300&fm=webp and ?fm=webp&w=300 produce the same cache key).

If stripQueryParamsFor is configured for specific file extensions, query parameters are removed from the cache key for those types. This prevents CMS-generated query params from creating duplicate cache entries. When not configured, query parameters are always included.

Cache TTL

Default cache TTL is 2 days (172,800 seconds). You can customize this per project from 60 seconds up to 30 days.


API cache behavior

API responses use a separate cache flow from assets:

  • API cache is GET only
  • default API cache TTL is 60 seconds
  • preview requests can bypass cache entirely
  • parsed and raw API responses use different cache keys
  • cache refresh works by rotating cache version keys instead of purging the edge directly

Parsed vs raw API responses

When transformApiUrls is enabled, CMS Assets can rewrite asset URLs inside JSON API responses before they are cached.

  • default behavior: parsed response with rewritten asset URLs
  • ?parsed=false: raw proxied response without URL rewriting
  • transformApiUrls=false: disables rewriting at the project level even if ?parsed=false is omitted

This is useful when some consumers want proxy-ready URLs while others need the original upstream payload.

Preview bypass

When the configured previewBypassParam is present, CMS Assets skips the API cache and fetches directly from the upstream CMS API. This keeps editorial preview flows fresh without changing your default cache strategy.


Token injection and JSON URL rewriting

For API proxy, tokens are stored encrypted and injected server-side only when CMS Assets actually needs to fetch the upstream API.

  • tokens are never returned to the browser
  • tokens are decrypted only on cache miss or bypass
  • auth mode can be bearer, basic, token, or none
  • transformed JSON responses can already contain https://your-project.cmsassets.com/... asset URLs before they are cached

That means clients can receive proxy-ready CMS data without carrying CMS credentials or manually rewriting every asset URL in application code.


Bot protection

The edge worker blocks known scrapers and crawlers by default:

  • ahrefs, semrush, uptimerobot
  • python, curl, wget, libwww, node-fetch

You can customize the blocked bots regex per project in your project configuration.


Allowed file types

The allowedTypes setting lets you restrict which Content-Types the proxy will serve. When configured, responses with a Content-Type that doesn't match any allowed type return a 403 Asset type not allowed error.

If allowedTypes is not configured for a project, all content types are served. Common allowed types include svg, pdf, mp4, jpeg, jpg, png, webp, avif.


Video support

Video files (mp4, webm, mov, m4v) are handled with special consideration:

  • Range requests are fully supported for seeking and streaming
  • If your project has a separate videoOrigin configured, video requests route to that origin
  • Range requests bypass the edge cache to ensure correct partial content delivery

For Contentful, the video origin is automatically set to https://videos.ctfassets.net/{spaceId}.


CORS

CORS behavior depends on Cloudflare Workers defaults. The edge worker does not explicitly add CORS headers to responses. If your frontend requires specific CORS headers, verify that the origin server or Cloudflare's built-in handling provides them for your use case.


Usage tracking

Every request (both cache hits and cache misses) is tracked for usage. The worker sends a fire-and-forget POST to the usage ingest endpoint with:

  • Number of requests (1)
  • Number of bytes (from Content-Length header)

This data is aggregated per project per month and used for billing and quota enforcement. Usage tracking never blocks the response — if the ingest endpoint is unavailable, the asset is still served.


Quota enforcement

Usage data is tracked asynchronously and aggregated server-side by the Nuxt API. Quota enforcement is handled at the API/billing layer, not per-request in the edge worker. The worker does not perform quota checks before fetching from origin — it always serves the request and records usage via fire-and-forget ingest events.

Plan limits (requests, bandwidth) are enforced when usage is reconciled at the billing level. See Limits & Usage for plan details.

Need help understanding this?Ask CMS Assets Copilot about features, setup, or integrations.
Ask Copilot →