Custom Emojis loaded from S3/R2 bypass CDN routing

Overview

when using S3 or cloudflare R2 for uploads alongside a custom CDN URL, custom emoji uploads do not respect the CDN configuration and attempt to load directly from the raw bucket URL.

The issue

when an admin uploads a custom emoji, the uploader creates an upload record and saves the raw bucket URL to the database (e.g., //my-bucket.s3.amazonaws.com/... or //my-bucket.r2.cloudflarestorage.com/...) - this is standard Discourse behavior.

however, when app/models/emoji.rb generates the emoji cache for /site.json, it passes the upload.url directly to the emoji object:

e.url = emoji.upload&.url

because it skips the CDN helper, the frontend receives the raw bucket URL. so depending on how strict the bucket’s access policies are, this results in broken images or forces Discourse to route the emojis through the internal avatar_proxy.

Solution

i have opened a PR that wraps the URL assignments in Discourse.store.cdn_url(), which makes the custom emoji loader align with how standard post images and avatars are routed.

Interim fix

while waiting for the PR to be reviewed and merged, i created a lightweight theme component that swaps the raw bucket URL for the proper CDN URL right before the custom emoji renders in the DOM (works for both posts and chat).

if you are experiencing this bug on your site, you can install this component and configure your S3 strings in the theme admin settings to fix any broken custom emojis:

Note: if you have already uploaded custom emojis that are currently borked, running discourse remap "//my-raw-bucket-url.com" "https://my-cdn.com" in the container will fix the old ones in the database, while the theme component will fix any newly uploaded ones until the PR fix is merged into core.

5 Likes

Testing a default set emoji

:smiley:

Testing a custom emoji

:falco:

2 Likes

yea maybe it’s a cloudflare R2 only thing then. they are breaking on my instance. it only seems to break for new ones uploaded too.

without the theme component fix, i have to run the remap every time i upload a new one. the PR might need a bit of work - i’m not super good with the emoji code.

2 Likes

Discourse uses a two CDN setup, one for assets and one proxying the app.

Standard emoji uses one CDN, custom emoji uses the other CDN, but both are protected by a CDN on a properly configured website with a working two CDN setup.

I went over that in the first related topic here

Does your site have both CDNs setup and working?

4 Likes

oh i’ll have to have a look - i didn’t know that. thanks!

edit:

i wrote the section for Cloudflare R2 so assume i have it setup correctly? what might i be missing?

2 Likes

1 Like

just confirming here that i installed and tested the PR branch after the recent changes to it and it fixes the issue.

1 Like

It is this section of the wiki guide I wrote:

Do you have both set ?

This is an artifact of moving Meta from AWS back to Metal, it was missing a new cook, just rebuilt HTML to fix it.

Do your test site have both CDN ENV vars set?

3 Likes

i do have them set, but for gods sake i had a typo in my cloudflare config for the cdn-specific dns record that DISCOURSE_CDN_URL points to and i never tested it when i set it up because my site was working :laughing: what a gong show.

at least i learned more about emoji code creating that pointless PR…
:woman_facepalming:t2:

thanks Falco!

3 Likes

Ha, no worries!

That is how I learn the most, chasing rabbit holes that will never amount to anything tangible. But the knowledge from those is golden!

3 Likes

wow what a ride this was. i basically reconfigured my whole Cloudfare R2 object storage and Discourse instance, and i think this bug is legit for R2. when i fixed my cloudflare dns record and rebuilt the instance so that DISCOURSE_CDN_URL actually pointed to it, it borked a bunch of other stuff like translation strings and threw multiple errors in the console including some CORS errors. it led me down many rabbit holes today. so i guess using DISCOURSE_CDN_URL seems incompatible with Cloudflare R2. (this was very weird - when i originally setup my original dns entry, i had incorrectly entered the cdn.mysite.com dns record so that it was resolving as cdn.mysite.com.mysite.com). setting DISCOURSE_CDN_URL correctly seems incompatible with Cloudflare R2 object storage. there may be some other stuff i’m not fully understanding here.

anyways, when i rebuild with my PR branch it is all fixed because it wraps the assignment in Discourse.store.cdn_url() so that custom emojis uploads obey the same S3 CDN routing and fallback logic as standard post image uploads.

i reopened the PR and edited the description. but i guess if Discourse team chooses not to merged it, that is fine because the theme component fixes the issue at the client level. Note the PR fix should only affect R2 object storage configurations for custom emoji and not other regular S3 compatible like AWS.

2 Likes

this PR was merged and the custom emoji are now fixed when using Cloudflare R2 object storage for uploads. I have posted a suggested update to the relevant R2 documentation here: Configure an S3 compatible object storage provider for uploads

3 Likes

Issue is resolved, file a new report if you see this again