Skip to main content

Cloudflare R2

Better Messages can keep the files members send in a Cloudflare R2 bucket instead of your server's uploads folder. R2 charges nothing for downloads, browsers upload files straight to the bucket, and files open from Cloudflare instead of from your server.

Cloud storage works in the free version and in the WebSocket version, from Better Messages 3.1.

You need a Cloudflare account with R2 turned on. Cloudflare asks for a payment method before it turns R2 on, even when your use stays within the free allowance.

Choose how files open#

Pick this first, because it decides what you set up in Cloudflare. You can change it later.

How files openWho can open a fileWhat you set up in Cloudflare
Members of the conversation only (default)Only members, through links Better Messages signs for each of themA bucket and an API token
Members only, through a WorkerOnly members. Files load from your own domain and Cloudflare's cacheA bucket, an API token and a Worker
Anyone with the link, custom domainAnyone who has a file's linkA bucket, an API token and a domain connected to the bucket
Anyone with the link, r2.dev addressAnyone who has a file's link. For testing onlyA bucket, an API token and the bucket's r2.dev address

Members only is the default and needs nothing but the bucket and the token. Use a public URL only for files that are not private, and keep them in a bucket of their own.

1. Create a bucket#

In the Cloudflare dashboard, open Storage & databases → R2 Object Storage and click Create bucket.

R2 Object Storage overview in the Cloudflare dashboard with the Create bucket button

Enter a name, for example my-site-chat-files. A bucket name cannot be changed later. Leave Location on Automatic and Default Storage Class on Standard, then click Create bucket.

Create a bucket form with the bucket name filled in, Automatic location and Standard storage class

A new bucket is private, which is what members-only delivery needs. Do not turn on its public access.

2. Create an API token#

Better Messages uses an R2 API token to upload, check and delete files. On the R2 overview page, click Manage API Tokens under Account Details, then Create Account API token.

R2 API Tokens page with the Create Account API token button

An account token keeps working when the person who made it leaves your Cloudflare account, so it suits a live site. Name it, for example Better Messages - my-site.com, and choose Admin Read & Write.

Create Account API Token form with Admin Read & Write selected

Admin Read & Write lets Better Messages set up the bucket's CORS rule for you with Configure CORS. Object Read & Write limited to your bucket also works for files, but then you add the CORS rule yourself, see Uploads straight to the bucket.

Click Create Account API Token. The next screen shows the token's credentials once. Copy the Access Key ID and the Secret Access Key. Better Messages does not need the token value.

You also need your Account ID. It is under Account Details on the R2 overview page, in the first picture above.

3. Connect R2 in Better Messages#

In WordPress, go to Better Messages → Settings → Attachments. In the Storage section, click Connect next to Cloudflare R2.

Storage section of the Attachments settings with the Connect button of Cloudflare R2

Fill in the connection:

  • Account ID, Bucket, Access Key ID and Secret Access Key, from the previous steps.
  • Folder: where in the bucket this site keeps its files, bm by default. Give each site its own folder if several sites share one bucket.

Leave Who can open files on Members of the conversation only for now, and Upload straight to the bucket on.

Click Configure CORS, then Test Connection. The test uploads a small file, reads it back, opens it the way members will, checks that the bucket refuses anyone else, checks that browsers can upload to the bucket and deletes the file. Every line should say Passed.

Connect Cloudflare R2 dialog filled in, with every Test Connection check passed

Click Connect. It runs the same test and saves R2 when the test passes. When a check fails, nothing is saved: fix what the check says, or click Save Anyway.

Connecting R2 does not send any files there yet. You choose that in step 5.

4. Who can open files#

Members of the conversation only#

This is the default. The bucket stays private, and every member gets links to the files that Better Messages signs for them. A link works for one to one and a half days, and the messenger renews links on its own while the member keeps the conversation open, so nobody sees a broken file. Someone who is not a member of the conversation gets no link, and without one R2 opens nothing.

Files load from R2's own address. Nothing else is needed. To load them from your own domain and Cloudflare's cache instead, add a Worker.

Serve files through a Cloudflare Worker#

A Cloudflare Worker serves the files from your own domain, such as files.my-site.com, and keeps a copy in Cloudflare's cache near each member. Files stay members only: the Worker opens a file only for a link Better Messages signed, and a file deleted in Better Messages stops opening at once, even from the cache.

In the R2 dialog, tick Serve files through a Cloudflare Worker. The setup steps appear under it.

Worker settings under Members of the conversation only: the setup steps, Copy Worker Script, Worker URL and a generated Worker Secret

Create the Worker. In Cloudflare, open Compute → Workers & Pages and click Create application.

Workers & Pages with the Create application button

Choose Start with Hello World!.

Create an app with the Start with Hello World option

Name the Worker, for example my-site-files, and click Deploy.

Deploy Hello World with the Worker name filled in and the Deploy button

Paste the Better Messages script. In Better Messages, click Copy Worker Script. In Cloudflare, open the Worker and click Edit code.

The Worker's overview page with the Edit code button

Select all the code in worker.js, paste the copied script over it and click Deploy.

The Worker code editor with the Better Messages script pasted and the Deploy button

Bind the bucket. Open the Worker's Settings, click Add binding under Bindings and choose R2 bucket. Enter R2_BUCKET as the Variable name, choose your bucket and click Deploy.

R2 bucket binding form with the variable name R2_BUCKET and the bucket chosen

Add the secret. In Better Messages, click Generate next to Worker Secret, then Copy. In Cloudflare, click Add variable under Variables and secrets. Enter WORKER_SECRET as the Key, paste the secret as the Value, tick Secret and click Add variable and deploy.

Add environment variable form with the key WORKER_SECRET and Secret ticked

The Worker's settings now show the secret and the binding.

The Worker's settings with the WORKER_SECRET secret and the R2_BUCKET binding

The secret must be the same in Cloudflare and in Better Messages. Better Messages shows it only until you save, so copy it into Cloudflare first. If you generate a new one later, put the new one into Cloudflare too.

Give the Worker your domain. Open the Worker's Domains tab and click Add Domain.

The Worker's Domains tab with its workers.dev address and the Add Domain button

Choose your domain from the list of domains on your Cloudflare account, enter a subdomain such as files and click Add domain. Cloudflare creates its DNS record and certificate, which takes a minute or two.

Connect to my-site.com dialog with the subdomain files entered and the Add domain button

The Worker's workers.dev address, shown on the same tab, also works, for example while you test.

Finish in Better Messages. Enter the Worker's address as the Worker URL, for example https://files.my-site.com, and click Test Worker. It checks that a signed link opens a file through the Worker, that videos can seek, and that forged and expired links are refused.

Test Worker with every check passed

Click Connect, or Save if R2 is connected already.

The Worker keeps files in Cloudflare's cache for an hour. To change that, add a variable named CACHE_TTL with the number of seconds.

When a Better Messages update changes the Worker script, Test Worker says to paste the current script again. Copy it and paste it over the old one as above.

Anyone with the link#

Files open from the bucket's public address, with no check: anyone who has a file's link can open it, and your site does no work when a file is viewed. Use this only for files that are not private, and use a bucket of their own, because making a bucket public opens every file in it.

In Cloudflare, open the bucket's Settings. For a live site, click Add under Custom Domains, enter a subdomain of a domain on your Cloudflare account, for example files.my-site.com, and connect it. Cloudflare creates its DNS record.

Custom Domain dialog of an R2 bucket with a domain entered

To try it out, you can turn on the Public Development URL instead. Cloudflare limits how many requests that r2.dev address answers, so do not use it on a live site. A new r2.dev address can take a minute before it opens files.

Public Development URL of an R2 bucket, turned on

In Better Messages, choose Anyone with the link (public URL) and enter the domain, or the r2.dev address, as the Public URL. Test Connection checks that a file opens from it.

Anyone with the link chosen with a Public URL, and every Test Connection check passed

5. Choose which files go to R2#

Under File Groups, choose Cloudflare R2 for each kind of file you want there, then click Save Settings.

Cloudflare R2 connected in the Storage section, and the File Groups that send images, videos, audio, voice and video messages and documents to it

The choice applies to files sent after you save. Files in end-to-end encrypted conversations stay encrypted wherever they are kept.

Uploads straight to the bucket#

With Upload straight to the bucket on, the default, a member's browser or the mobile app sends a file straight to R2, using a link Better Messages signs for that one file. Files never pass through your server, and they are in the bucket the moment they are sent.

The bucket has to allow uploads from your site, with a CORS rule. Configure CORS adds one, named better-messages, for your site's address and the mobile app, and keeps any other rule the bucket has. You can see it in the bucket's Settings → CORS Policy:

CORS Policy of the R2 bucket with the rule Better Messages added

If you made an Object Read & Write token, add the rule there yourself. Click Add under CORS Policy and paste this, with your site's address:

[
{
"AllowedOrigins": ["https://my-site.com", "capacitor://localhost", "https://localhost"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]

capacitor://localhost and https://localhost are the Better Messages mobile app. Leave them out if you do not use the app.

While the bucket does not allow uploads from your site, files go through your server instead, so sending never fails because of CORS. The R2 dialog says so under Browser uploads.

With Upload straight to the bucket off, every file is uploaded to your server first and copied to the bucket after its message is sent, so your server carries every upload twice.

The same rule lets members open files in end-to-end encrypted conversations, from signed links, the r2.dev address or a custom domain. The messenger downloads such a file itself to decrypt it, and without the rule the browser refuses and shows Could not download media. So keep the rule even with Upload straight to the bucket off. The Worker allows every site, so through it encrypted files open without the rule. With encryption on, Test Connection checks this too. The mobile app is not affected.

Moving files you already have#

Files sent before you connected R2 stay where they are and keep opening. To move them, click Move all files from one storage to another under File Groups, or move single files in Better Messages → Administration → Files. Moves run in the background, so you can leave the page while they run.

When a file is deleted#

When a file is deleted in Better Messages, it is marked Pending deletion in Administration → Files, and a background job deletes it from R2 within a few minutes. The file leaves the list once R2 confirms it is gone. If R2 refuses, the job tries again later.

Keeping the keys out of the database#

You can put the keys in wp-config.php instead of the settings. The R2 dialog then shows those fields as set in wp-config.php.

define( 'WORDPLUS_R2_ACCESS_KEY', 'your-access-key-id' );
define( 'WORDPLUS_R2_SECRET_KEY', 'your-secret-access-key' );
define( 'WORDPLUS_R2_WORKER_SECRET', 'your-worker-secret' );

Troubleshooting#

"The provider does not recognise this access key." The Access Key ID is wrong, or the token was deleted. Copy it again, or create a new token.

"The secret key does not match the access key." The Secret Access Key does not belong to that Access Key ID.

"The key is not allowed to do this in the bucket." The token cannot write to this bucket. Give it Admin Read & Write, or Object Read & Write that includes this bucket.

"The bucket does not exist at this endpoint." Check the bucket name and the Account ID.

"Anyone can read files in this bucket without a link." The bucket has a public address, a custom domain or the r2.dev address, while files are set to members only. Remove the public address, or keep members-only files in another bucket.

"CORS does not allow uploads from … yet." Click Configure CORS. A new rule can take a few seconds to apply, so run Test Connection again if the first check still warns.

"Encrypted files will not open in browsers on …" The bucket has no CORS rule for your site. Click Configure CORS, or add the rule from Uploads straight to the bucket yourself, then run Test Connection again.

"This server's clock is … seconds off the provider's." Signed links stop working when your server's clock is more than 15 minutes off. Ask your host to fix the server time.

Test Worker: "A signed link does not open the file through the Worker." The message shows what the Worker answered:

  • 403 Invalid link: the WORKER_SECRET in Cloudflare is not the Worker Secret in Better Messages. Generate a new one and put it into both.
  • 500 The Worker needs an R2_BUCKET binding and a WORKER_SECRET secret: add the binding or the secret, with exactly those names.
  • 404 File not found: R2_BUCKET is bound to another bucket.
  • No answer: check the Worker URL, and that its domain shows as active on the Worker's Domains tab.

Test Worker: "Partial downloads do not work" or "The Worker does not set the download name." The Worker runs an older script. Click Copy Worker Script and paste it over the Worker's code.

"The file does not open from its public URL." Check the Public URL, and that the domain shows as active in the bucket's Custom Domains. A new r2.dev address can answer 401 for about a minute.