# Creating a Website Chat Widget with Gradio

**URL:** <https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412>\
**Category:** Beginners\
**Created:** [November 13, 2025, 8:42pm UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412 "2025-11-13T20:42:50Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![T40FaceHugger](https://avatars.discourse-cdn.com/v4/letter/t/71c47a/32.png) [@T40FaceHugger](https://discuss.huggingface.co/u/T40FaceHugger)\
**Post date:** [November 13, 2025, 8:42pm UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412/1 "2025-11-13T20:42:50Z")

</div>

I’m pretty new to Python, and I’ve been playing with the gradio chat windows. I can run them locally no problem.

But I want to use the chat widget for gradio as detailed here:

[https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot)

Here is a very basic version of code that I’m using (whatever the users says it just replies with “hello”), I’m just trying to test functionality -

> _import_ gradio _as_ gr
> 
> def chat(_message_, _history_):
> 
> ```auto
> *return* f"Hello"
> 
> ```
> 
> gr.ChatInterface(_fn_=chat, _type_=“messages”).launch()

It works fine locally, and if I upload the code to spaces it also works fine there. The problem is when I try to call it from the widget it doesn’t work and in the devtools pane it says:

> Hello
> 
> contact:74 Error: TypeError: result.data[0].join is not a function  
> at sendMessage (contact:71:51)

It receives “hello” so that shows it is correctly communicating with the gradio chat app on spaces, but the error suggests that this response it has received is not in the correct format (it is getting a string but seems to expect an array of strings or some kind of structured data)

I keep going round in circles trying to fix this, does any one have some basic python code that actually works with this widget?

---

<div class="post-metadata">

**Author:** ![John6666](https://sea2.discourse-cdn.com/hellohellohello/user_avatar/discuss.huggingface.co/john6666/32/27664_2.png) [@John6666](https://discuss.huggingface.co/u/John6666)\
**Post date:** [November 14, 2025, 12:18am UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412/2 "2025-11-14T00:18:46Z")

</div>

Gradio is a GUI that runs on a combination of Python and JavaScript, so if you’re building the JavaScript part yourself, you need to be careful to ensure it communicates properly with the Python side. I don’t use site embedding, so I don’t have much to do with it…

* * *

Below is a small, self-contained Gradio app that actually works with the official **“Creating a Website Widget from a Gradio Chatbot”** widget JavaScript. I will first give the code, then walk through it slowly and clearly so you can see why it works and how it connects to the widget.

* * *

## 1. The basic working `app.py`

This version is written to match exactly what the Gradio widget guide expects:

- It uses `gr.ChatInterface`.
- It uses `multimodal=True`, so the first argument is a `{"text": ..., "files": [...]}` dict, just like in the docs. ([Gradio](https://www.gradio.app/docs/gradio/chatinterface "ChatInterface"))
- It returns a **list of strings** , so the widget JavaScript can safely do `result.data[0].join('\n')`, as shown in the guide. ([Gradio](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))

```python
# app.py
# Minimal Gradio chatbot that works with the official website widget example.

import gradio as gr

def chat(message, history):
    """
    This function is called every time the user sends a message
    from the widget (or the Gradio UI).

    Parameters
    ----------
    message : dict
        Because we set `multimodal=True` on ChatInterface, Gradio passes
        a dictionary here with the following shape:
          {
              "text": "<user message as a string>",
              "files": ["/path/to/file1", "/path/to/file2", ...]
          }
        The widget example sends exactly this shape.

    history : list
        A list representing the chat history.
        We don't need it for this simple example, but it must be accepted.

    Returns
    -------
    list[str]
        A list of strings. The JavaScript widget receives this list
        as `result.data[0]` and then calls `.join('\\n')` on it.
        So returning a list is critical.
    """
    # Safely get the text the user typed
    if isinstance(message, dict):
        user_text = message.get("text", "")
    else:
        # Fallback, in case message is not a dict for some reason
        user_text = str(message)

    # Build a list of strings that will become the bot's reply.
    # Each item in this list will become one "line" in the widget.
    response_lines = [
        "Hello from your Gradio Space!",
        f"You said: {user_text}",
        "",
        "This response is a *list of strings*,",
        "so the website widget's `result.data[0].join('\\n')` works.",
    ]

    return response_lines

# Create the chatbot interface.
# - type="messages" → history is handled in messages format (as in docs)
# - multimodal=True → input is a dict {text, files}
# - api_name="chat" → HTTP endpoint is /chat (the widget calls this)
demo = gr.ChatInterface(
    fn=chat,
    type="messages",
    multimodal=True,
    title="Widget Demo Bot",
    api_name="chat", # this matches the "/chat" path used in the JS guide
)

if __name__ == " __main__":
    # Run this locally with: python app.py
    demo.launch()

```

This file alone is enough to:

- run locally in a Gradio browser window, and
- work correctly when called from the official widget JavaScript in the guide. ([Gradio](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))

* * *

## 2. How this ties into the official widget guide

The Gradio guide “Creating a Website Widget from a Gradio Chatbot” shows JavaScript like this (simplified): ([Gradio](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))

```js
const result = await client.predict("/chat", {
  message: { text: userMessage, files: [] },
});

const botMessage = result.data[0].join('\n');
appendMessage(botMessage, 'bot');

```

There are three very important expectations here:

1. **Endpoint name (`"/chat"`)**

2. **Input format (multimodal dict)**

3. **Output format (list of strings)**

In your original code, you did:

```python
def chat(message, history):
    return "Hello" # a plain string

```

That is perfectly valid for the Gradio UI, but then:

- `result.data[0]` is `"Hello"` (a string),
- `result.data[0].join('\n')` throws a TypeError,
- and you see the exact error you described.

The only real change you need is: **return a list of strings instead of a plain string**.

* * *

## 3. How to run this locally (step-by-step)

1. Create a virtual environment (optional but recommended):

2. Install Gradio:

3. Save the `app.py` from section 1 in the same directory.

4. Run:

5. Gradio prints a local URL like `http://127.0.0.1:7860`. Open it in your browser.

At this point, you know that:

- The Python code runs.
- The Gradio UI works.
- The function receives a dict with `"text"` and `"files"`, and returns a list of strings.

* * *

## 4. How to deploy it as a Hugging Face Space

The widget guide is written around using a public Space, like `abidlabs/gradio-playground-bot`. ([Gradio](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))  
You can use your own Space instead.

### 4.1 Required files

Create two files:

1. `app.py` – the code above.

2. `requirements.txt` – to tell Spaces which packages to install:

Nothing else is strictly required for this minimal example.

### 4.2 Steps on Hugging Face

1. Go to Hugging Face → “New Space” → choose **Gradio** template.
2. Name it something like `your-username/website-widget-test`.
3. Upload `app.py` and `requirements.txt`.
4. Wait for the build to finish.
5. Once it is running, visit the Space and confirm the chatbot works.

The Space URL will look like:

```plaintext
https://your-username-website-widget-test.hf.space

```

You will use this URL inside your website’s JavaScript.

* * *

## 5. How to hook it up to your website widget

The official guide gives you HTML/CSS/JS for a small floating chat window. The key part is the JavaScript that connects to your Space. ([Gradio](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))

A minimal version looks like this:

```html
<script type="module">
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";

  async function initChatWidget() {
    // 1. Connect to YOUR Space
    const client = await Client.connect("https://your-username-website-widget-test.hf.space");

    // 2. Grab DOM elements (buttons, input, container, etc.)
    const chatToggle = document.getElementById("chat-toggle");
    const chatContainer = document.getElementById("chat-container");
    const closeChat = document.getElementById("close-chat");
    const chatInput = document.getElementById("chat-input");
    const sendButton = document.getElementById("send-message");
    const messagesContainer = document.getElementById("chat-messages");

    chatToggle.addEventListener("click", () => {
      chatContainer.classList.remove("hidden");
    });

    closeChat.addEventListener("click", () => {
      chatContainer.classList.add("hidden");
    });

    function appendMessage(text, sender) {
      const div = document.createElement("div");
      div.className = `message ${sender}-message`;
      div.textContent = text;
      messagesContainer.appendChild(div);
      messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    async function sendMessage() {
      const userMessage = chatInput.value.trim();
      if (!userMessage) return;

      appendMessage(userMessage, "user");
      chatInput.value = "";

      try {
        // 3. Call your /chat endpoint with a multimodal message
        const result = await client.predict("/chat", {
          message: { text: userMessage, files: [] },
        });

        // 4. Get the bot reply: this is an array of strings
        console.log("raw data[0]:", result.data[0]);
        const botMessage = result.data[0].join("\n");
        appendMessage(botMessage, "bot");
      } catch (error) {
        console.error("Error from widget:", error);
        appendMessage("Sorry, there was an error.", "bot");
      }
    }

    sendButton.addEventListener("click", sendMessage);
    chatInput.addEventListener("keydown", (e) => {
      if (e.key === "Enter") sendMessage();
    });
  }

  initChatWidget();
</script>

```

Because your Python code:

- uses `multimodal=True`
- returns a `list[str]`

the call `result.data[0].join("\n")` now works exactly as in the official Gradio guide.

* * *

## 6. Mental model: data shapes (beginner-safe summary)

To keep it conceptually clear:

- Think of the Gradio app as a function you expose over HTTP.

- For **each prediction** , Gradio sends your function:

- Your function returns something (string, list of strings, message dicts, etc.).

When you call that app from JavaScript using `@gradio/client`:

- `result.data[0]` is exactly whatever you returned from Python (first output), but in JavaScript form. ([Gradio](https://www.gradio.app/docs/gradio/chatinterface "ChatInterface"))

So:

- If you return `"Hello"` → `result.data[0]` is a string → `.join()` fails.
- If you return `["Hello"]` → `result.data[0]` is an array → `.join()` works.

The widget JS in the guide was written for the **playground bot Space** , whose backend returns a **list of strings**. ([Hugging Face](https://huggingface.co/spaces/abidlabs/gradio-playground-bot/tree/main "abidlabs/gradio-playground-bot at main"))  
Your original code returned just a string, which is why you saw `join is not a function`.

The `app.py` given at the top simply makes your Space behave like the example Space regarding input and output shapes, so the widget code “just works”.

---

<div class="post-metadata">

**Author:** ![T40FaceHugger](https://avatars.discourse-cdn.com/v4/letter/t/71c47a/32.png) [@T40FaceHugger](https://discuss.huggingface.co/u/T40FaceHugger)\
**Post date:** [November 14, 2025, 8:51am UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412/3 "2025-11-14T08:51:23Z")

</div>

Hi, thanks for that very comprehensive answer, your explanation made it all make a lot more sense.

I used the code you provided, and it works fine as an app on HF space, and it has corrected the initial error with the widget. But then the widget gave a new error:

 ![image](https://us1.discourse-cdn.com/hellohellohello/original/3X/6/7/67be4910c142f98d2aeab5e8e9c31d6e55eecad8.png)

It seems the java code in the widget was referencing “marked” but had not called the library. I inserted a call to the library in the widget code, and now it all seems to work as expected (now I just need to write the AI part of the chat bot, but that should be the easy bit!)

Bit weird that the published widget code had an error in it though, unless I’m missing something.

---

<div class="post-metadata">

**Author:** ![John6666](https://sea2.discourse-cdn.com/hellohellohello/user_avatar/discuss.huggingface.co/john6666/32/27664_2.png) [@John6666](https://discuss.huggingface.co/u/John6666)\
**Post date:** [November 14, 2025, 9:00am UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412/4 "2025-11-14T09:00:29Z")

</div>

Oh, maybe you’re right.

* * *

You didn’t miss anything – your diagnosis is exactly right.

### 1. What that new error means

The error in your screenshot:

```plaintext
ReferenceError: marked is not defined

```

means:

- JavaScript sees `marked` being used somewhere,
- but there is **no variable named `marked` in the current page** (no import, no `<script>` that defines it). ([Stack Overflow](https://stackoverflow.com/questions/10102862/referenceerror-and-the-global-object "ReferenceError and the global object - javascript"))

In the Gradio widget guide, the relevant lines are:

```js
function appendMessage(text, sender) {
    const messageDiv = document.createElement('div');
    messageDiv.className = `message ${sender}-message`;

    if (sender === 'bot') {
        messageDiv.innerHTML = marked.parse(text); // <-- uses `marked`
    } else {
        messageDiv.textContent = text;
    }
    messagesContainer.appendChild(messageDiv);
    messagesContainer.scrollTop = messagesContainer.scrollHeight;
}

```

from the official page. ([gradio.app](https://www.gradio.app/guides/creating-a-website-widget-from-a-gradio-chatbot "Creating A Website Widget From A Gradio Chatbot"))

So the docs’ JS **assumes a global `marked` object exists** , but the snippet never shows how to load it. On the Gradio docs site they already load `marked` (they use it for their own Markdown rendering in chat components ([Zenn](https://zenn.dev/machinelearning/articles/mesop-comp-use "Mesopの基本コードとGradioとの比較"))), so the example works there. When you copy it into your own page, that global is missing, so you get the `ReferenceError`.

Your fix – adding the `marked` library yourself – is exactly what’s needed.

* * *

### 2. What `marked` is and why they’re using it

`marked` is a small JavaScript library that converts **Markdown text → HTML**. It’s very common in chat/markdown widgets. ([GitHub](https://github.com/markedjs/marked "markedjs/marked: A markdown parser and compiler. Built ..."))

- Your bot’s reply is plain text with Markdown (e.g. ` **bold** `, code blocks).
- `marked.parse(text)` converts that Markdown string into an HTML string.
- They then set `messageDiv.innerHTML = ...` so the chat bubble renders bold, code blocks, links, etc.

So the intention is:

- **User messages** → `textContent` (shown literally, no HTML parsing).
- **Bot messages** → run through `marked.parse` and then put in `innerHTML` so Markdown is rendered nicely.

That’s why `marked` is referenced in the snippet at all.

* * *

### 3. How to include `marked` cleanly (the simple way)

For the exact code in the docs (which expects a **global** `marked`), the easiest fix is:

```html
<!-- Load marked first (non-module script, defines global `marked`) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

<!-- Then your widget script from the guide -->
<script type="module">
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";

  async function initChatWidget() {
    // ... everything from the guide, including:
    // messageDiv.innerHTML = marked.parse(text);
  }

  initChatWidget();
</script>

```

Key points:

- Use the non-module `<script src="...marked.min.js"></script>` form so it adds a global `marked` variable, as shown in the official `marked` docs. ([marked.js.org](https://marked.js.org/ "Marked Documentation"))
- Load it **before** the `<script type="module">` that uses `marked`, so it exists by the time your widget code runs.

That’s essentially what you did – and that’s why it now works.

* * *

### 4. Alternative: drop Markdown support for now (even simpler)

If you don’t care about fancy Markdown rendering yet, you can also just remove `marked` from the equation entirely.

Change the `appendMessage` function to:

```js
function appendMessage(text, sender) {
  const messageDiv = document.createElement('div');
  messageDiv.className = `message ${sender}-message`;

  // Just always treat as plain text
  messageDiv.textContent = text;

  messagesContainer.appendChild(messageDiv);
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
}

```

Then you don’t need `marked` at all; both user and bot messages are displayed as straight text.

Later, when your AI starts returning Markdown (e.g. lists, code blocks), you can reintroduce `marked` to make it look nicer.

* * *

### 5. Why the published widget code “has an error”

Short version: the docs snippet is **environment-dependent** and they forgot to show that dependency.

- On gradio.app, they already load `marked` globally because `gr.Chatbot` and `gr.Markdown` components now use it in the frontend for Markdown parsing. ([Zenn](https://zenn.dev/machinelearning/articles/mesop-comp-use "Mesopの基本コードとGradioとの比較"))
- The widget guide reuses that global `marked` without re-declaring it, so it appears to work fine on their own site.
- When you copy the snippet into a clean page that doesn’t load `marked` first, JavaScript quite reasonably says: **“ReferenceError: marked is not defined.”**

So you are not missing anything; it’s just a small docs oversight. If you feel like it, you could even open a quick issue on the Gradio GitHub repo suggesting they add the `marked` `<script>` tag to the guide. ([GitHub](https://github.com/markedjs/marked "markedjs/marked: A markdown parser and compiler. Built ..."))

* * *

### 6. Where you are now

You’ve already:

1. Fixed the return type from Python (`list[str]` instead of `str`), so `.join('\n')` works.
2. Added the `marked` library, so Markdown rendering works and the `ReferenceError` is gone.

At this point, the plumbing – browser ⇄ widget ⇄ Gradio Space – is in good shape, and you can focus on the “actual AI” part: plugging in a model and gradually upgrading that simple `chat` function.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/hellohellohello/original/2X/d/de4155eb4aa4108ecb32a1389d7cc37ae69f88b7.png) [@system](https://discuss.huggingface.co/u/system)\
**Post date:** [November 14, 2025, 9:00pm UTC](https://discuss.huggingface.co/t/creating-a-website-chat-widget-with-gradio/170412/5 "2025-11-14T21:00:43Z")

</div>

This topic was automatically closed 12 hours after the last reply. New replies are no longer allowed.
