Q: How do I block messages with bad words?
A: Use an array of words and check with
.some()
like in the default handler.
This allows developers to inject custom JavaScript logic into the chat system and interact with messages in real time. Below you’ll find a full explanation of how this works, what tools are available, and how to write and test your own handler functions.
Programmable Chat enables message-level moderation, transformation, or dynamic response handling by attaching custom JavaScript code to your chat. It runs before a message is submitted to the database and allows for fine-grained control.
You can customize how messages are processed using programmable logic. Common use cases include:
These use cases can be combined or expanded to tailor chat behavior to your community’s needs. And of course, you can always experiment with your own logic.
Your custom code is structured as an asynchronous handler function, which receives several runtime parameters:
const handler = async (message, user, accept, resolve, reject, fetch) => { // Your logic here};
The following arguments are provided:
message
: The user’s message objectuser
: The sender user objectaccept()
: Approves the message (unchanged)resolve(...)
: Replaces the message or responds on behalf of the botreject("reason")
: Blocks the message and shows an error to the user* For more information about object fields, see:
At the bottom of your script, you must always export the handler like this:
export default await handler( env.MESSAGE, env.USER, env.ACCEPT, env.RESOLVE, env.REJECT, env.FETCH);
Do not remove or modify the export line or the handler declaration. These are required for execution.
accept()
return accept();
resolve()
accept()
if called without arguments.With currently available parameters:
return resolve( { message: { body: "Rewritten message here", attachments: [ { file_url: "https://example.com/file.png", file_blur_hash: "LEHV6nWB2yk8pyo0adR*.7kCMdnj", file_content_type: "image/png", file_width: 800, file_height: 600, }, ], }, }, { isReplaceBody: true });
isReplaceBody
is true
, the original user message will be replaced with your versionfalse
, your message will appear as a bot response.reject("reason")
return reject("Message blocked due to inappropriate content.");
Here is the default JavaScript example used for basic profanity filtering:
// vvv Don`t remove or change the line below! vvvconst handler = async (message, user, accept, resolve, reject, fetch) => { const body = message.body;
// Reject the message if it contains any prohibited words const prohibitedWords = ["asshole", "fuck", "bullshit"]; if (prohibitedWords.some((word) => body.includes(word))) { return reject("Message blocked by moderation."); }
return accept();};
// vvv Don`t remove or change the line below! vvvexport default await handler( env.MESSAGE, env.USER, env.ACCEPT, env.RESOLVE, env.REJECT, env.FETCH);
This handler checks for banned words and rejects the message if any are found. Otherwise, it allows the message through.
You can test your logic directly on the page using the Check button:
// vvv Don’t remove or change the line below! vvv
accept
, resolve
, or reject
) should be made per messageresolve
with isReplaceBody: true
is useful for:
fetch
in test mode — use saved code to verify external logicQ: How do I block messages with bad words?
A: Use an array of words and check with
.some()
like in the default handler.
Q: Can I rewrite user messages?
A: Yes. Use
resolve({message: { body: "..." }}, { isReplaceBody: true })
.
Q: Can I send attachments?
A: Yes, inside the
attachments
array in themessage
object.
Q: Can I call APIs or use async functions?
A: Yes, but be cautious with CORS and always test in production mode.
Q: Can I use external libraries or APIs?
A: You can use
fetch
to call external APIs or services, but only in the saved handler — not during test mode, due to browser CORS restrictions. However, importing external JavaScript libraries (e.g., usingimport
statements, CDN scripts, or npm packages) is not supported. You must rely on built-in JavaScript and browser APIs only.
Responds with a welcome message when the user says hello.
const handler = async (message, user, accept, resolve, reject, fetch) => { const greetings = ["hi", "hello", "hey", "yo"]; const body = message.body.toLowerCase();
if (greetings.some((g) => body.startsWith(g))) { return resolve({ message: { body: `Hi ${ user.first_name || "there" }! 👋 How can I help you today?`, }, }); }
return accept();};
If the user types :cat:
, send a cat image and replace their message.
const handler = async (message, user, accept, resolve, reject, fetch) => { if (message.body.trim() === ":cat:") { const res = await fetch("https://api.thecatapi.com/v1/images/search"); const cat = (await res.json())[0]; return resolve( { message: { body: "Here's a cat for you! 🐱", attachments: [ { file_url: cat.url, file_content_type: "image/jpeg", file_width: cat.width, file_height: cat.height, }, ], }, }, { isReplaceBody: true } ); }
return accept();};
Blocks messages that mention spoilers.
const handler = async (message, user, accept, resolve, reject, fetch) => { const body = message.body.toLowerCase(); const spoilers = [ "snape kills dumbledore", "bruce willis was dead", "tony dies", ];
if (spoilers.some((line) => body.includes(line))) { return reject("🚫 Spoiler detected! Your message was blocked."); }
return accept();};
Detects and redacts URLs in the message text.
const handler = async (message, user, accept, resolve, reject, fetch) => { const urlRegex = /https?:\/\/[^\s]+/g; const newBody = message.body.replace(urlRegex, "[link]");
return resolve({ message: { body: newBody } }, { isReplaceBody: true });};
If the user is shouting in all caps, convert the message to lowercase.
const handler = async (message, user, accept, resolve, reject, fetch) => { const body = message.body;
if (body === body.toUpperCase() && body.length > 5) { return resolve( { message: { body: body.toLowerCase() }, }, { isReplaceBody: true } ); }
return accept();};
Replaces :emoji:
style codes with actual emojis.
const handler = async (message, user, accept, resolve, reject, fetch) => { const body = message.body; const transformed = body .replace(/:smile:/g, "😄") .replace(/:fire:/g, "🔥");
return resolve( { message: { body: transformed } }, { isReplaceBody: true } );};
✏️ Want more examples or want to share your own? Let us know in Discord or contribute via GitHub.