multipart/form-data request, and the widget keeps a record of every file so you can show it, react to it, or pass its URL to another function.
Two pieces work together:
- An Upload file function that holds your endpoint URL, the file constraints, and what happens after each file finishes. Configure it on the Functions tab. See Functions.
- A
DropzoneorFileInputcomponent in the widget code that collects the files and shows their progress. See Form Inputs.
Set Up an Upload
1
Add a Dropzone or FileInput to the code
Give it a The
name and point uploadAction at the function you create in the next step.name becomes a variable holding the list of files, so photos[0].url is the URL of the first uploaded file once your endpoint returns it.2
Create the function
On the Functions tab, add a function named
uploadPhoto and set its type to Upload file. Enter your endpoint under Endpoint, choose POST or PUT, and set URL field to the path in your JSON response that holds the file’s URL, for example data.url.3
Allow the request on your server
Your endpoint must accept multipart uploads and allow CORS from
https://www.chatbase.co. The widget runs inside the Chatbase chat iframe, so that is the request’s origin wherever your site embeds the agent, and it is also where the builder preview runs. If you serve the agent from a custom domain, allow that domain as well. See Your Endpoint.4
Try it in the preview
Drop a file on the preview. Uploads in the builder go to your real endpoint with the function’s current settings, so you can see the success and error rows before you save.
How an Upload Runs
- The visitor drops or picks files. Each file is checked against the function’s Accepted types, Max size, and Max files before anything is sent. A file that fails becomes an error row with the reason, for example This file type is not accepted.
- Each accepted file is sent in its own request, up to three at a time. The row shows a progress bar.
- A
2xxresponse marks the file as uploaded. If URL field is set, the URL is read from the JSON body at that path. Any Return fields you listed are kept on the file record too. - A non-
2xxresponse marks the file as failed. The row shows the response’s status text, such as Internal Server Error, and offers a retry. The response body is never shown to the visitor. - After each file settles, the function’s On success (per file) or On failure (per file) action runs.
Your Endpoint
Each request is amultipart/form-data POST or PUT containing:
- Any Form fields you configured, appended first.
- The file, under the part name in File field. The default is
file. The original filename is preserved. - Your Headers and Query parameters, with template tokens resolved.
Content-Type header. The browser sets it so the multipart boundary is correct, and the builder blocks saving with Remove the Content-Type header, the browser sets it.
Tokens available in the URL, headers, query parameters and form fields: every widget variable, {{user}} fields, {{conversationId}}, {{chatbotId}}, {{widgetId}}, and {{file.name}}, {{file.size}}, {{file.type}} for the file being sent.
A minimal Express endpoint:
url. Drop chat.example.com from the origin list if you do not use a custom domain.
Use the Uploaded Files
Every file becomes a record in the widget’s data under the component’sname:
status is one of staged, uploading, success, or error. response holds the Return fields you chose.
Some ways to use the records:
- Show the first image:
<Image src={photos[0].url} />. - Copy a URL into another variable with an On success (per file) action of type Set variables: set
heroSrcto{{file.url}}. - Show a state only after an upload succeeded, with a state condition like
photos && photos.some(f => f.status === 'success'). - Send the URLs to another API with a Server Function whose body references
{{photos}}.
{{file.url}}, {{file.name}}, {{file.error}} and every Return field are available.
Uploaded file URLs and return fields are stored with the conversation and are included in conversation webhooks. Return URLs you are comfortable keeping there. A short-lived signed URL will stop working in the conversation history once it expires.
Trigger Uploads From a Button
A plainButton or a Form can point at an upload function too. Set the function’s Target field to the variable that should hold the records, because a button has no name of its own.
- If the field already holds staged files (from a
DropzoneorFileInputwithautoUpload={false}), the button uploads them. - Otherwise clicking the button opens the visitor’s file picker and uploads whatever they choose.
uploadDocs must be documents. The builder blocks saving otherwise.
Limits
Files that were still uploading when the visitor reloads the page are dropped from the widget. Finished and failed files stay.
The size check happens in the browser before any bytes are sent. Your endpoint should enforce its own limit as well.
Secure Your Endpoint
The upload request comes from the visitor’s browser, not from Chatbase. Anyone who can open your chat can find the URL and send requests to it, so treat it as a public endpoint.Rate limit it
Rate limit it
Limit requests per client, and return
429 Too Many Requests when the limit is hit. The widget shows the status text to the visitor and offers a retry, so a clear status text helps.Two keys work well together. The limiter runs after the multipart parser so the form field is available:- Per IP, to stop simple floods.
- Per conversation, so one visitor cannot exhaust the limit for everyone behind the same network. Add a Form field such as
conversationId={{conversationId}}and key the limit on it. Chatbase caps each upload field at 5 files, so a legitimate visitor rarely sends more than a handful of requests per conversation.
Never put a secret in the headers
Never put a secret in the headers
Header and query parameter values are sent from the browser, so the visitor can read them. The builder says so under Headers. Use a public, scoped key, or hand out a short-lived token:
- Add a Server Function that asks your API for an upload token and returns it as a Return field, with an On Success action that sets a variable such as
uploadToken. - Reference it in the upload function’s Headers as
Bearer {{uploadToken}}. - Have the token expire after a few minutes and accept only uploads.
Validate on the server, not just in the widget
Validate on the server, not just in the widget
The Accepted types, Max size and blocked extensions are checked in the browser and are easy to bypass. On the server, check the size again, detect the file type from its content rather than its extension or the client’s
Content-Type, and reject anything you do not expect with a 4xx.Restrict CORS to the origins you use
Restrict CORS to the origins you use
Allow only
https://www.chatbase.co and your custom agent domain if you have one. The request never comes from the site that embeds the chat, because the widget runs inside the Chatbase iframe. Avoid Access-Control-Allow-Origin: * on an endpoint that accepts writes, and answer OPTIONS preflight requests with the method and headers you actually use.Store files out of your web root
Store files out of your web root
Write uploads to object storage or a dedicated bucket, generate your own filenames instead of trusting the client’s, and return a URL from there. Scan files with your malware tooling before making the URL public if visitors can upload arbitrary types.
