Skip to main content
The Cache API provides programmatic control over HTTP caching. You can store responses and retrieve them later, enabling faster subsequent requests and offline functionality.

Overview

Workerd implements a subset of the standard Cache API:
  • caches.default - Access the default cache
  • caches.open(name) - Open a named cache
  • cache.match() - Retrieve cached responses
  • cache.put() - Store responses
  • cache.delete() - Remove cached responses
Implementation: src/workerd/api/cache.h and cache.c++

Accessing caches

Default cache

The caches.default cache is always available:
const cache = caches.default;

Named caches

Open or create a named cache:
const cache = await caches.open('my-cache');

Storing responses

Use cache.put() to store responses:
export default {
  async fetch(request) {
    const cache = caches.default;
    
    // Create a response
    const response = new Response('Hello World', {
      headers: {
        'Content-Type': 'text/plain',
        'Cache-Control': 'public, max-age=3600'
      }
    });
    
    // Store in cache
    await cache.put(request, response.clone());
    
    return response;
  }
};
Source: src/workerd/api/tests/cache-operations-test.js:52

Cache-Control headers

The Cache-Control header determines caching behavior:
// Cache for 1 hour
const response = new Response('data', {
  headers: {
    'Cache-Control': 'public, max-age=3600'
  }
});
await cache.put(request, response);

// Don't cache
const response = new Response('data', {
  headers: {
    'Cache-Control': 'no-store'
  }
});
await cache.put(request, response); // Won't be cached

// Server-specific max age
const response = new Response('data', {
  headers: {
    'Cache-Control': 's-maxage=7200, public'
  }
});
await cache.put(request, response);
Source: src/workerd/api/tests/cache-operations-test.js:65

Retrieving responses

Use cache.match() to retrieve cached responses:
export default {
  async fetch(request) {
    const cache = caches.default;
    
    // Try to get from cache
    let response = await cache.match(request);
    
    if (response) {
      // Cache hit
      return response;
    }
    
    // Cache miss - fetch from origin
    response = await fetch(request);
    
    // Store in cache for next time
    await cache.put(request, response.clone());
    
    return response;
  }
};

Match options

Customize matching behavior:
// Ignore HTTP method (match non-GET requests)
const response = await cache.match(request, {
  ignoreMethod: true
});
Source: src/workerd/api/cache.h:16
Cloudflare’s implementation does not support ignoreSearch or ignoreVary options. Users can remove query parameters before calling put() if needed.

Deleting cached responses

Remove responses from the cache:
const cache = caches.default;

// Delete a specific response
const deleted = await cache.delete(request);
if (deleted) {
  console.log('Cache entry deleted');
} else {
  console.log('Cache entry not found');
}
Source: src/workerd/api/tests/cache-operations-test.js:97

Delete options

Use the same options as match():
// Delete non-GET requests
const deleted = await cache.delete(request, {
  ignoreMethod: true
});

Cache patterns

Cache-first strategy

Serve from cache, fall back to network:
export default {
  async fetch(request) {
    const cache = caches.default;
    
    // Try cache first
    let response = await cache.match(request);
    
    if (!response) {
      // Fetch from network
      response = await fetch(request);
      
      // Cache the response
      await cache.put(request, response.clone());
    }
    
    return response;
  }
};

Network-first strategy

Try network first, fall back to cache:
export default {
  async fetch(request) {
    const cache = caches.default;
    
    try {
      // Try network first
      const response = await fetch(request);
      
      // Update cache
      await cache.put(request, response.clone());
      
      return response;
    } catch (error) {
      // Network failed, try cache
      const cached = await cache.match(request);
      
      if (cached) {
        return cached;
      }
      
      // No cache entry either
      return new Response('Offline', { status: 503 });
    }
  }
};

Stale-while-revalidate

Serve stale content while updating:
export default {
  async fetch(request, env, ctx) {
    const cache = caches.default;
    
    // Get cached response
    const cached = await cache.match(request);
    
    // Fetch fresh response asynchronously
    const fetchPromise = fetch(request).then(response => {
      // Update cache in background
      ctx.waitUntil(
        cache.put(request, response.clone())
      );
      return response;
    });
    
    // Return cached response immediately if available
    return cached || fetchPromise;
  }
};

Time-based invalidation

Invalidate cache entries after a time period:
export default {
  async fetch(request) {
    const cache = caches.default;
    const response = await cache.match(request);
    
    if (response) {
      // Check age
      const date = new Date(response.headers.get('Date'));
      const age = Date.now() - date.getTime();
      
      // If older than 1 hour, refresh
      if (age > 3600000) {
        const fresh = await fetch(request);
        await cache.put(request, fresh.clone());
        return fresh;
      }
      
      return response;
    }
    
    // Not in cache
    const fresh = await fetch(request);
    await cache.put(request, fresh.clone());
    return fresh;
  }
};

Cache keys

Cache keys are based on the request URL:
// These are different cache entries
await cache.put(
  new Request('https://example.com/api'),
  response1
);

await cache.put(
  new Request('https://example.com/api?param=value'),
  response2
);

Custom cache keys

Create custom cache keys by modifying the request:
// Remove query parameters for cache key
function getCacheKey(request) {
  const url = new URL(request.url);
  url.search = ''; // Remove query string
  return new Request(url.toString(), request);
}

// Use custom key
const cacheKey = getCacheKey(request);
let response = await cache.match(cacheKey);

if (!response) {
  response = await fetch(request);
  await cache.put(cacheKey, response.clone());
}

Best practices

Response bodies can only be read once. Clone before caching:
const response = await fetch(request);
await cache.put(request, response.clone());
return response;
Control caching behavior with proper headers:
const response = new Response(data, {
  headers: {
    'Cache-Control': 'public, max-age=3600',
    'Content-Type': 'application/json'
  }
});
Always check if match() returns undefined:
const cached = await cache.match(request);
if (!cached) {
  // Handle cache miss
}
Separate different types of cached content:
const apiCache = await caches.open('api-responses');
const assetCache = await caches.open('static-assets');

Implementation notes

The Cache API implementation in workerd:
  • Only supports GET requests by default
  • Does not support cache enumeration (keys(), matchAll())
  • Does not support ignoreSearch or ignoreVary options
  • Cache keys are based on request URL
Definition: src/workerd/api/cache.h:37
class Cache: public jsg::Object {
  jsg::Promise<jsg::Optional<jsg::Ref<Response>>> match(
    jsg::Lock& js,
    Request::Info request,
    jsg::Optional<CacheQueryOptions> options,
    CompatibilityFlags::Reader flags);

  jsg::Promise<void> put(
    jsg::Lock& js,
    Request::Info request,
    jsg::Ref<Response> response,
    CompatibilityFlags::Reader flags);

  jsg::Promise<bool> delete_(
    jsg::Lock& js,
    Request::Info request,
    jsg::Optional<CacheQueryOptions> options,
    CompatibilityFlags::Reader flags);
};
  • Fetch API - Making requests and handling responses
  • Headers - Managing Cache-Control headers

Build docs developers (and LLMs) love