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 open | Who can open a file | What you set up in Cloudflare |
|---|---|---|
| Members of the conversation only (default) | Only members, through links Better Messages signs for each of them | A bucket and an API token |
| Members only, through a Worker | Only members. Files load from your own domain and Cloudflare's cache | A bucket, an API token and a Worker |
| Anyone with the link, custom domain | Anyone who has a file's link | A bucket, an API token and a domain connected to the bucket |
| Anyone with the link, r2.dev address | Anyone who has a file's link. For testing only | A 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.
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.
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.
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.
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.
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,
bmby 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.
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.
Create the Worker. In Cloudflare, open Compute → Workers & Pages and click Create application.
Choose Start with Hello World!.
Name the Worker, for example my-site-files, and click Deploy.
Paste the Better Messages script. In Better Messages, click Copy Worker Script. In Cloudflare, open the Worker and click Edit code.
Select all the code in worker.js, paste the copied script over it and click Deploy.
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.
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.
The Worker's settings now show the secret and the 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.
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.
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.
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.
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.
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.
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.
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:
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: theWORKER_SECRETin 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_BUCKETis 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.






















