Overcoming S3/R2 metadata prefix (x-amz-meta-cache-tag) to attach Cloudflare Cache Tags

Hi everyone, Looking for guidance on Cache Tags with AWS S3 as the origin. We currently do purge by URL (over 1M URLs/week) and want to switch to tags to dramatically reduce the number of purge requests. On S3 we add user metadata, so instead of a Cache-Tag header we only see x-amz-meta-cache-tag (which seems typical for object stores: R2 also uses x-amz-meta-*, Azure Blob uses x-ms-meta-*). The only working pattern we have in a Cloudflare Worker is:
const head = await fetch(s3Url, { method: 'HEAD' });
const tags = parse(head.headers.get('x-amz-meta-cache-tag') || '');
const resp = await fetch(s3Url, { cf: { cacheTags: tags, cacheEverything: true } });
return resp;
const head = await fetch(s3Url, { method: 'HEAD' });
const tags = parse(head.headers.get('x-amz-meta-cache-tag') || '');
const resp = await fetch(s3Url, { cf: { cacheTags: tags, cacheEverything: true } });
return resp;
Questions: 1. Is this the “right” approach for S3→Cloudflare (first HEAD, then GET with cf.cacheTags) or is there a way to do it with one request? 2. Is there any “pre-cache” hook/pattern to apply tags read from the origin response without an extra HEAD, i.e., at the moment of writing to cache? 3. We considered Snippets, but from the docs it looks like they won’t help: Snippets run outside the caching moment and can’t assign tags (cf.cacheTags isn’t available there; adding/renaming a header after caching won’t attach tags to the object). Is that understanding correct? Thanks!
2 Replies
Hard@Work
Hard@Work5w ago
What about this?
const cache = caches.default;
let res = await cache.match(s3Url);
if(res) {
return res;
}
res = await fetch(s3Url, { cf: { cacheEverything: true }});
const tags = parse(res.headers.get('x-amz-meta-cache-tag') || '');
let cacheRes = res.clone();
cacheRes = new Response(res.body, res);
cacheRes.headers.set("cache-tag", tags.join(','));
ctx.waitUntil(cache.put(s3Url, res));
return res;
const cache = caches.default;
let res = await cache.match(s3Url);
if(res) {
return res;
}
res = await fetch(s3Url, { cf: { cacheEverything: true }});
const tags = parse(res.headers.get('x-amz-meta-cache-tag') || '');
let cacheRes = res.clone();
cacheRes = new Response(res.body, res);
cacheRes.headers.set("cache-tag", tags.join(','));
ctx.waitUntil(cache.put(s3Url, res));
return res;
I guess you could omit parse too if the x-amz-meta-cache-tag is already in the right format
Kirill Nikolaichuk
@Hard@Work | R2 Thanks a lot for the reply yesterday — the Cache API approach looks much better since it avoids an extra HEAD . Brief context: we run a hybrid setup — some S3 paths go through a Worker, and some are just proxied by Cloudflare (no Worker) to squeeze the best latency from edge cache. The “mutate response + put to cache with Cache-Tag” solution is great for the paths that already go via a Worker. Question: is there a way without Workers to have Cloudflare treat S3/R2 x-amz-meta-cache-tag as Cache-Tag before the object is written to cache? Would any Rules / Snippets let us map x-amz-meta-* → Cache-Tag at a pre-cache stage? Or is this simply not possible by design of the pipeline? If there’s a recommended pattern for this “no-Worker” proxying case, I’d really appreciate a pointer. Thank you.

Did you find this page helpful?