Skip to content

Programmable Chat Feature



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.

image.png

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:

  • Message moderation — Block messages with harmful content, profanity, or spoilers.
  • Content rewriting — Automatically fix formatting, redact links, or normalize text (e.g., converting ALL CAPS to lowercase).
  • Auto-replies — Respond to greetings, keywords, or help requests with custom messages.
  • Attachment injection — Replace or respond to specific keywords with images, files, or other media attachments.
  • Spam filtering — Detect and reject repetitive, all-caps, or link-heavy messages.
  • Dynamic interaction — Modify messages in real time before they’re shown, based on user input or context.

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 object
  • user: The sender user object
  • accept(): Approves the message (unchanged)
  • resolve(...): Replaces the message or responds on behalf of the bot
  • reject("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
);
  • Approves and sends the message unchanged.
  • Use when your logic determines that the message is safe or does not need to be changed.
return accept();
  • Same as 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 }
);
  • If isReplaceBody is true, the original user message will be replaced with your version
  • If omitted or false, your message will appear as a bot response.
  • Blocks the message and shows the specified error string to the user.
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! vvv
const 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! vvv
export default await handler(
env.MESSAGE,
env.USER,
env.ACCEPT,
env.RESOLVE,
env.REJECT,
env.FETCH
);

You can test your logic directly on the page using the Check button:

  • Type a test message in the input field
  • Click Check to simulate a message being sent
  • Check the logs for output
  • Save
    • Saves your current code to be executed on live messages.
  • Options Dropdown
    • Delete: Removes the current chat handler entirely
    • Undo: Restores the default code sample
  • Do not remove the header or footer lines:
    // vvv Don’t remove or change the line below! vvv
  • Only one call (accept, resolve, or reject) should be made per message
  • resolve with isReplaceBody: true is useful for:
    • Redacting sensitive words
    • Auto-formatting markdown or links
  • Avoid relying on fetch in test mode — use saved code to verify external logic

Q: 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 the message 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., using import statements, CDN scripts, or npm packages) is not supported. You must rely on built-in JavaScript and browser APIs only.

  1. 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();
    };
  2. 🖼️ Replace Keyword with Image Attachment

    Section titled “🖼️ Replace Keyword with Image Attachment”

    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();
    };
  3. 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();
    };
  4. 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 });
    };
  5. 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();
    };
  6. 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 }
    );
    };
  7. ✏️ Want more examples or want to share your own? Let us know in Discord or contribute via GitHub.

image.png