Upload Widget
Receive files from forms on your site, on Webflow or any framework
Last updated: October 19, 2026
Upload Widget
Add a file upload field to any form on your site. Visitors pick a file, it uploads to Flowdrive, and the form gets the file's link.
The quickest way to set it up is the Upload Widget page in your dashboard: it walks you through adding your site, installing the widget, checking your page and sending a test file.
1. Add your site
Uploads only work on the sites listed under Upload Widget → Your sites. Until a site is listed, the widget shows on your page but every upload is refused.
On Webflow, there's nothing to add here. When you turn on uploads for a site in the Flowdrive app, Flowdrive reads the site's webflow.io address and custom domains from Webflow and lists them under Webflow sites, marked Added by the Webflow app. Webflow addresses can't be typed in by hand.
Any other site: add it under Other sites (not built on Webflow). Subdomains are included, so acme.com also covers www.acme.com. Add your staging domain too if it's on a different address (for example acme-staging.netlify.app).
2. Install the widget
Webflow
In the Webflow Designer, open the Flowdrive app, click Upload widget and Enable uploads, then add the field to your form and publish. On Webflow, use the app rather than the script below: the app adds the script to your site through Webflow's Custom Code. The full guide is Flowdrive for Webflow.
Any other site (not built on Webflow)
Add the script once, in your page's <head> or before </body>. Your upload key is on the Upload Widget page.
<script data-fd-id="YOUR_UPLOAD_KEY" src="https://files.tryflowdrive.com/widget/1.1.15/index.min.js" integrity="sha256-C+zaPFmxVCrCymax8LpmG3jlN7TIgqkeqh47eIWcN24=" crossorigin="anonymous"></script>Then add the upload field inside your form:
<form>
<div fd-upload="default" fd-input="file_url"></div>
</form>When a file finishes uploading, its link is put in a form field called file_url, so it's sent with the rest of the form.
If your site uses a Content Security Policy
A Content-Security-Policy header makes the browser block anything it doesn't list. If your site sends one, add these sources, or the widget won't load, its styles and image previews won't show, or uploads will fail:
| Directive | Add | Why |
|---|---|---|
script-src | https://files.tryflowdrive.com | The widget script |
style-src | https://files.tryflowdrive.com | The widget's stylesheet |
img-src | blob: data: https: | Previews of chosen images, and the uploaded files |
connect-src | https://api.tryflowdrive.com https://files.tryflowdrive.com | Uploads, and signed links for secured files |
Webflow sites don't restrict scripts, styles or images unless you add your own policy, so they usually need nothing here.
Attributes reference
Every attribute the upload widget reads, in one place. Attributes go on the elements of your page; values are plain text.
Adding an attribute in Webflow
To change an upload field the Flowdrive app added, beyond the options in the app. The app adds a custom field, so the attributes marked Custom field below, and unmarked ones such as fd-folder, work on it. Those marked Built-in field don't. See Flowdrive for Webflow.
- In the Designer, select the upload field the Flowdrive app added.
- Open the Settings panel (the ⚙ tab, or press D) and find Custom attributes.
- Click +, type the attribute in Name (for example
fd-max-size) and its value in Value (for example10MB). For an attribute with no value, leave Value empty. - Publish. Custom code doesn't run inside the Designer, so test on your published or staging site.
On any other site, write the attribute straight into your HTML:
<div fd-upload="default" fd-upload-text="Attach your CV"></div>On the script
| Attribute | What it does |
|---|---|
data-fd-id="YOUR_UPLOAD_KEY" | Identifies your workspace. Your upload key is on the Upload Widget page. It's upload-only, so it's safe in a public page. |
On the upload field
The element with fd-upload. Some attributes only work on the built-in field (default), others only on your own design (custom).
| Attribute | What it does |
|---|---|
fd-upload="default" | Turns the element into an upload field. default is the built-in field; custom uses your own button and preview (see Custom design). |
fd-input="file_url" | Where the file's link goes. A name (file_url) adds a hidden field with that name to the form; a selector (#cv, .cv, [name=cv]) uses a field you already have. That field must be a text, URL, search or hidden input, or a textarea. The widget won't write to password, email, phone or number inputs or dropdowns; to fields whose autocomplete marks them as a password, username, one-time-code or card field; to fields whose name or id contains password, passwd, csrf or xsrf, or a word such as pwd, otp, token, nonce, secret, cvc, cvv or "card number"; to reCAPTCHA, hCaptcha or Turnstile response fields; or to any field in a form that has a card field identified that way, or inside Webflow's checkout form. Names outside these lists are protected only by the field's type and autocomplete. It logs a console warning instead of writing, and it won't add a hidden field whose name another field in the form already uses. With custom, use a selector. |
fd-folder="FOLDER_ID" | Saves uploads into a folder. The ID is the last part of the folder's address in Files. |
fd-type="image" | Built-in field. Which files can be picked: pdf, image, video, audio or document (Word, Excel, PowerPoint and PDF). |
fd-multiple | Built-in field. Allows more than one file; the links go into the field one after another. On a custom field, add multiple to your file input instead. No value. |
fd-required | Built-in field. The form can't be sent until a file is uploaded. No value. |
fd-upload-text="Attach your CV" | Built-in field. The text on the upload button. Default: Upload files. |
fd-accept="image, .pdf" | Custom field. Which files are allowed: the groups image, video, audio, code and docs, extensions like .pdf, or types like image/png, comma separated. |
fd-max-size="10MB" | Custom field. The largest file allowed: a number with B, KB, MB or GB. Larger files are refused before they upload. |
fd-show-preview="cv" | Custom field. Shows each file in the preview list with that name (see below). |
Inside a custom field
| Attribute | What it does |
|---|---|
fd-upload="trigger" | On your button: opens the file picker. |
multiple | On your <input type="file">: allows more than one file. No value. |
On the preview list
| Attribute | What it does |
|---|---|
fd-preview-list="cv" | Names the list, so a field can point at it with fd-show-preview. |
fd-prepend | New files go to the top of the list instead of the bottom. No value. |
fd-display="flex" | How the list is shown once it has a file (any CSS display value). Default: block, so you can hide the list until then. |
Inside a preview item
| Attribute | What it does |
|---|---|
fd-preview="item" | The template copied for each file. Everything below goes inside it. |
fd-preview="img" | On an <img>: a thumbnail for images, a file icon for anything else. |
fd-preview="name" | The file's name. |
fd-preview="size" | The file's size. |
fd-preview="progress-fill" | A bar whose width follows the upload. |
fd-preview="progress-number" | The upload's percentage, as text. |
fd-preview="cancel" | On a button: stops the upload, or removes the file and its link from the form. |
fd-show-on-complete | Shown when the upload finishes. fd-hide-on-complete hides instead. No value. |
fd-show-on-fail | Shown when the upload fails. fd-hide-on-fail hides instead. No value. |
fd-display="flex" | On the item: how the elements above are shown (any CSS display value). Default: block. |
Secured files
fd-secure and fd-download are for showing protected files, not for uploads. See Secured files.
Custom design
To use your own design instead of the built-in field, use fd-upload="custom" with your own button and file input, and an optional preview list:
<div fd-upload="custom" fd-input="#file_url" fd-show-preview="cv" fd-max-size="10MB" fd-accept="docs">
<button type="button" fd-upload="trigger">Choose a file</button>
<input type="file" hidden />
</div>
<div fd-preview-list="cv">
<div fd-preview="item">
<img fd-preview="img" alt="" />
<span fd-preview="name"></span>
<span fd-preview="size"></span>
<div><div fd-preview="progress-fill"></div></div>
<span fd-preview="progress-number"></span>
<span fd-show-on-complete>Uploaded</span>
<span fd-show-on-fail>Upload failed</span>
<button type="button" fd-preview="cancel">Remove</button>
</div>
</div>
<input id="file_url" type="hidden" name="file_url" />Every attribute used here is explained in the Attributes reference.
Frameworks (React, Svelte, Vue)
The same script works in single-page apps. Upload fields that appear later, after a route change, in a modal or on a later form step, are set up when they appear.
Two things differ from a plain HTML page:
- Let your framework keep its element. On a plain page the widget replaces the
<div>. A framework that rendered that div would break on its next update, so in an app the widget builds the uploader inside it. React is detected automatically. For Svelte, Vue and server-rendered apps, addfd-mount="inside". - Read the link from the event. The widget fires
fduploadon the element with the file's link.
React
React never runs onChange for hidden inputs, so listen for the event:
function UploadField({ onUploaded }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
const handle = (e) => onUploaded(e.detail.url);
el.addEventListener('fdupload', handle);
return () => el.removeEventListener('fdupload', handle);
}, [onUploaded]);
return <div ref={ref} fd-upload="default" fd-input="file_url" />;
}Svelte
<script>
let fileUrl = $state('');
</script>
<div
fd-upload="default"
fd-mount="inside"
fd-input="file_url"
onfdupload={(e) => (fileUrl = e.detail.url)}
></div>Vue
<template>
<div
fd-upload="default"
fd-mount="inside"
fd-input="file_url"
@fdupload="(e) => (fileUrl = e.detail.url)"
></div>
</template>Svelte's bind:value and Vue's v-model on the field named by fd-input also work: the widget fires input and change when it fills it in.
Events
All events bubble, so you can listen on the field or any parent.
| Event | When | event.detail |
|---|---|---|
fdupload | A file finished uploading | { url, name, size, type } |
fdremove | The visitor removed a file | { url, name } |
fderror | An upload failed | { name, code, message } |
Troubleshooting
The upload button shows, but uploads fail. The site isn't on your upload key yet. On Webflow, turn on uploads for the site in the Flowdrive app, then publish. If uploads are already on and a domain is still refused, for example a custom domain you added in Webflow later, disable and enable uploads for that site in the app, then publish: Flowdrive reads the site's domains from Webflow each time uploads are turned on. On any other site, add it under Upload Widget → Your sites → Other sites (not built on Webflow); subdomains are included.
The field stays an empty box in my app.
Check the script is in your page's <head>. Then use Check your page on the Upload Widget page: it confirms the script, your key and the field.
My React state doesn't update.
Listen for fdupload as shown above. React doesn't run onChange for hidden inputs.
The script is on the page twice. Usually a copy pasted by hand plus the one the Webflow app adds. Remove the hand-pasted one; only one runs either way.