FileUploadField
Drag-and-drop file uploads for Framer with real cloud storage, native form integration, live progress tracking, and complete visual customisation — zero code required.
Overview #
FileUploadField is a production-ready Framer component that adds fully functional file upload capability to any Framer site or prototype. Drop it inside a Framer Form, pick a cloud storage provider, and files uploaded by your visitors are sent directly to your storage bucket — with the resulting URL automatically included in your form submission data.
How it works
When a visitor selects or drops a file, the component uploads it directly from their browser to your chosen storage provider — no backend server required. The file travels from the visitor's device straight to the bucket using a technique called direct upload.
Once the upload finishes, the file's public URL is placed into a hidden form field. When the visitor submits the form, this URL is included alongside all other field data.
The component stores URLs, not file content. Framer's own database never receives the file — only the link to where it lives in your chosen storage provider.
Quick Start #
The fastest path to a working upload field using Cloudinary — the recommended provider for most projects. Free plan includes 25 GB storage and 25 GB bandwidth/month.
- Create a free Cloudinary account
Go to cloudinary.com → click Sign up for free. No credit card needed. Verify your email.
- Copy your Cloud Name
After login, your Dashboard shows your Cloud Name — a short string like
dxyz12abc. Copy it (it's case-sensitive). - Create an unsigned Upload Preset
Go to Settings (gear icon) → Upload → Upload Presets → Add upload preset. Give it a name like
framer_uploads. Set Signing Mode toUnsigned. Save.🚨The preset must be Unsigned. A signed preset requires a backend server to generate signatures — uploads will fail if you use one.
- Configure in Framer
Select the component → set Provider to
Cloudinary ⭐→ paste your Cloud Name and preset name into the respective fields. - Publish and test
Publish your site and upload a file. Within seconds it appears in Cloudinary's Media Library. The file row shows a green success state. Done!
All Features #
progress event.<input> automatically carries uploaded URLs into Framer's form submission payload.Form Integration #
FileUploadField is designed to sit inside Framer's native Form component. The uploaded file URL travels with the rest of the form data automatically on submit.
The hidden field mechanism
The component renders a <input type="hidden" name="file_url"> element. As uploads complete, this input's value updates with the public URL(s). Framer serialises it on submit exactly like any other input field.
For multiple files, URLs are joined by commas: https://cdn.example.com/a.pdf,https://cdn.example.com/b.pdf.
Setting the field name
The Form Field Name property (default: file_url) controls the key used in your submission data. Change it to match whatever your form integration (Airtable, email template, Notion, etc.) expects.
Timing
Uploads start the moment a file is selected — not on form submit. On a normal connection, files finish well before the submit button is clicked. For large files on slow connections, the hidden field may be empty if the user submits before completion.
To guarantee all uploads complete before form submission, you'd need custom code to disable the Submit button until every file row shows a success state. This is outside the component's built-in scope.
File Validation #
All validation runs in the browser before any upload begins. Users get instant feedback without wasting bandwidth on rejected files.
Size limit
The Max Size property (default: 10 MB) rejects any file exceeding the limit. The error appears below the drop zone and the upload never starts. Set to a high value like 500 to effectively disable the limit.
File type restriction
The Allowed Types group has a toggle for each of the 37 supported extensions. The rule is simple:
- All toggles off — any file type is accepted
- One or more toggles on — only those types are permitted
The hint text below the drop zone updates automatically to reflect active constraints, e.g. Max 10 MB · PDF, JPG, PNG.
Validation is two-layered: the browser file picker is filtered via the accept attribute, and a JavaScript check runs again when files are dropped. This prevents bypassing via file rename.
Text & Labels #
Every visible string is editable from the Text group in the Properties panel. Each label also has a paired Font control for family, size, weight, line height, and letter spacing.
| Property | Default | Where It Appears |
|---|---|---|
| title | "Drop files here or click to browse" | Main heading in the idle drop zone. |
| description | "Supports any file type" | Sub-text below the title. Leave blank to hide. |
| releaseText | "Release to upload" | Replaces the title while a file is being dragged over the zone. |
| button | "Browse Files" | Label on the Browse button (only shown if Show Button is on). |
| helper | "" | Caption rendered below the entire component. Good for extra instructions. |
| uploadingText | "Uploading…" | Status text in the file row while uploading. |
| successMessage | "Uploaded successfully" | Status text after a successful upload, shown next to the file size. |
| errorMessage | "Upload failed. Please try again." | Fallback status text if the upload fails with no specific server error. |
Colors #
The Colors group provides 18 individual colour controls. Framer's colour picker supports solid colours, opacity, and CSS gradients.
Gradient values work for background-type properties (button, drop zone background). They cannot be applied as border or SVG stroke colours — the component falls back safely to a solid colour in those cases.
Drop zone
| Property | Default | Controls |
|---|---|---|
| background | #F9F9F9 | Drop zone fill, idle state. |
| activeBg | #EEF4FF | Drop zone fill while dragging a file over it. |
| borderColor | #DDDDDD | Drop zone border, idle. |
| activeBorderColor | #4F6EF7 | Drop zone border while dragging. |
| titleColor | #333333 | Title text. |
| releaseTextColor | #4F6EF7 | "Release to upload" text during drag. |
| descriptionColor | #888888 | Sub-text below the title. |
| accent | #4F6EF7 | Browse button background; icon circle; uploading icon. |
| accentText | #FFFFFF | Text on the Browse button. |
| muted | #888888 | Drop zone icon colour (idle state). |
| hintColor | #888888 | Constraints hint text. |
| helperColor | #888888 | Helper caption below the component. |
File rows
| Property | Default | Controls |
|---|---|---|
| successColor | #16A34A | Row tint, icon, and status text on success. |
| errorColor | #DC2626 | Row tint, icon, and status text on failure. Also validation errors. |
| progressColor | #4F6EF7 | Filled portion of the progress bar. |
| fileNameColor | #222222 | File name text in each row. |
| rowUploadingBg | #F8F8F8 | Row background while uploading. |
| rowUploadingBorder | #E5E5E5 | Row border while uploading. |
Border & Shape #
| Property | Default | Description |
|---|---|---|
| border.width | 1.5 px | Border thickness. Set to 0 to remove it. |
| border.style | Dashed | Border style: Solid, Dashed, or Dotted. |
| border.radius | 12 px | Drop zone corner radius (also applied, capped at 10 px, to file rows). |
| buttonRadius | 8 px | Browse button corner radius, independent of the drop zone. |
Icons #
Icons appear in three places and are each configured independently.
Drop zone icon
Toggle Drop Zone Icon on/off. When on, choose: Cloud (default), Arrow Up, Paperclip, Folder, Custom Image (any Framer asset), or None. The icon sits in an accent-coloured circle that pulses on drag-hover.
File row success icon
Toggle Row File Icon on/off. When on, choose Check, File, Custom Image, or None. While uploading, a pulsing file icon appears automatically.
Remove button icon
Choose Trash (default), X / Close, Custom Image, or None. Setting to None hides the remove button entirely — users won't be able to remove files once added.
Layout & Spacing #
| Property | Default | Controls |
|---|---|---|
| dropZonePaddingV | 32 px | Top and bottom padding inside the drop zone. |
| dropZonePaddingH | 24 px | Left and right padding inside the drop zone. |
| dropZoneGap | 12 px | Vertical gap between elements inside the drop zone (icon → title → description → button → hint). |
| rowPaddingV | 10 px | Top and bottom padding inside each file row. |
| rowPaddingH | 12 px | Left and right padding inside each file row. |
| rowListGap | 6 px | Gap between multiple file rows. |
Width fills 100% of the container. Height is automatic and grows as files are added. In single-file mode, the drop zone hides after the first file is selected to keep the UI clean.
Upload Providers #
Six providers are available. All do the same job — receive a file and return a public URL — but differ in setup, pricing, and use-case fit.
⚡ Public — tmpfiles.org #
For testing only. Files are publicly accessible and deleted after 1 hour. Never use for real user data or production forms.
Zero configuration required. Select it from the Provider dropdown and uploads work immediately — no sign-up, no credentials. Use it to verify that drag-and-drop, the progress bar, and the file row UI all look correct before connecting a real provider.
☁️ Cloudinary #
Recommended for most projects. Direct browser uploads, no backend required. Free plan: 25 GB storage, 25 GB bandwidth/month.
Required credentials
dxyz12abc.Setup guide
- Create a free account
Sign up at cloudinary.com. No credit card. Verify email.
- Copy your Cloud Name
Log in → Dashboard. Your Cloud Name appears prominently — e.g.
dxyz12abc. Copy it exactly. - Create an unsigned upload preset
Settings (gear icon) → Upload → Upload Presets → Add upload preset. Name it (e.g.
framer_uploads), set Signing Mode toUnsigned, optionally set a Folder. Save. - Configure in Framer
Set Provider to
Cloudinary ⭐→ paste Cloud Name and preset name. - Publish and test
Upload a file. Check Cloudinary's Media Library — it should appear within seconds.
🗄️ Supabase #
Open-source backend with built-in Storage. Free plan: 1 GB storage. Files upload directly from the browser via the Storage REST API.
Required credentials
https://abcdefgh.supabase.co. Found in Settings → API.anon key — a long eyJ… string. Found in Settings → API.uploads.Setup guide
- Create a Supabase project
Go to supabase.com, sign up, create a new project. Wait ~1 minute for provisioning.
- Get your Project URL and Anon Key
Go to Settings → API. Copy the Project URL and the anon public key (the long
eyJ…string). - Create a public storage bucket
In the sidebar, click Storage → New Bucket. Name it
uploads. Check Public Bucket so files are accessible by URL. Create it.⚠️Without Public Bucket enabled, the upload URL will return a 403 error when accessed.
- Add an RLS policy for anonymous uploads
Supabase Row Level Security blocks all writes by default. You must add a policy explicitly.
Storage → click your bucket → Policies → New policy → For full customisation:
- Policy name:
Allow public uploads - Allowed operation:
INSERT - Target roles:
anon - USING clause:
true
Click Review → Save policy.
🚨This is the step most people miss. If uploads return 403, this RLS policy is almost certainly the cause.
- Policy name:
- Configure in Framer
Set Provider to
Supabaseand fill in the three fields.
The component prepends a timestamp to every filename (1713456789_document.pdf) to prevent collisions when users upload files with identical names.
📁 Google Drive #
Files upload to a Drive folder via a Google Apps Script Web App you deploy from your own Google account. Free within Drive's storage quota, but requires more setup steps.
Required credentials
.../exec URL of your deployed Google Apps Script.downloadable — direct download link. preview — Google Drive preview page.Setup guide
- Create a new Google Apps Script
Go to script.google.com → New project. Delete all existing code and paste:
Apps Script (JavaScript)function doPost(e) { var params = e.queryString ? parseQuery(e.queryString) : {}; var filename = params.filename || "upload_" + Date.now(); var mimeType = params.mimeType || "application/octet-stream"; var bytes = JSON.parse(e.postData.contents); var blob = Utilities.newBlob(bytes, mimeType, filename); // To upload to a specific folder, replace the next line with: // var folder = DriveApp.getFolderById("YOUR_FOLDER_ID"); var folder = DriveApp.getRootFolder(); var file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return ContentService .createTextOutput(JSON.stringify({ success: true, fileId: file.getId() })) .setMimeType(ContentService.MimeType.JSON); } function parseQuery(q) { return q.split("&").reduce(function(o, p) { var kv = p.split("="); o[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || ""); return o; }, {}); } - (Optional) Target a specific folder
Find your folder's ID from its Drive URL:
drive.google.com/drive/folders/THIS_IS_THE_ID. ReplacegetRootFolder()withgetFolderById("PASTE_ID"). - Deploy as a Web App
Click Deploy → New deployment → Web app.
- Execute as: Me
- Who has access: Anyone ← required
Authorise Drive access when prompted. Copy the
/execURL after deploying.⚠️After editing the script, always create a new deployment (not update the existing one) for changes to take effect.
- Configure in Framer
Set Provider to
Google Drive, paste the/execURL into Web App URL, and choose your File Link Type.
🔶 Cloudflare R2 #
S3-compatible object storage with zero egress fees. Great for video or large assets. Requires deploying a small Cloudflare Worker as the upload endpoint.
Required credentials
https://file-upload-worker.yourname.workers.devSetup guide
- Create a Cloudflare account and R2 bucket
Sign up at cloudflare.com. Go to R2 Object Storage → Create bucket. Name it e.g.
framer-uploads. - Enable public access
Open your bucket → Settings → Public Access → Allow Access. Note the public domain (e.g.
pub-xxxx.r2.dev). - Create a Worker
Workers & Pages → Create application → Create Worker. Name it
file-upload-worker. Replace the default code with:Cloudflare Worker (JavaScript)export default { async fetch(request, env) { if (request.method === "OPTIONS") return new Response(null, { headers: cors() }); if (request.method !== "POST") return new Response("Method not allowed", { status: 405 }); const form = await request.formData(); const file = form.get("file"); if (!file) return new Response("No file", { status: 400 }); const key = `${Date.now()}_${file.name}`; await env.BUCKET.put(key, file.stream(), { httpMetadata: { contentType: file.type } }); // Replace with your pub-xxxx.r2.dev domain: const url = `https://YOUR-BUCKET-PUBLIC-DOMAIN/${key}`; return new Response(JSON.stringify({ url }), { headers: { "Content-Type": "application/json", ...cors() } }); } }; function cors() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" }; } - Bind the R2 bucket to the Worker
In the Worker → Settings → Variables → R2 Bucket Bindings → Add binding. Set the variable name to exactly
BUCKET, select your R2 bucket. Save and redeploy. - Configure in Framer
Set Provider to
Cloudflare R2and paste the Worker URL (no trailing slash) into Worker URL.
📤 Uploadcare #
Dedicated upload CDN. Free plan: 3 GB storage. Simplest setup of all paid providers — just one public key.
Required credentials
demopublickey.Setup guide
- Create an account
Sign up at uploadcare.com. Create a new project from your dashboard.
- Copy your Public Key
Go to API Keys. Copy the Public Key (not the Secret Key — never put the secret in browser code).
- Configure in Framer
Set Provider to
Uploadcareand paste your public key. Done.
Uploaded files are served from https://ucarecdn.com/FILE_UUID/. Append transformation parameters to resize or convert images on the fly, e.g. /-/scale_crop/400x400/.
All Properties #
Complete reference. All properties are optional — the component ships with defaults for everything.
Behaviour
false. In single-file mode the drop zone hides after the first file.10. Range: 0.1–500.name attribute of the hidden form field. Default: file_url.true.Provider
public / cloudinary / supabase / gdrive / r2 / uploadcarehttps://xxxx.supabase.co.eyJ… anon key.uploads./exec URL.downloadable or preview.Drop Zone & Icons
cloud / arrow / paperclip / folder / custom / noneiconStyle is custom.40. Range: 16–120.8 px.check / file / custom / none. Success state icon.custom.16 px.trash / x / custom / none. none hides the remove button.custom.16 px.Spacing
32 px.24 px.12 px.10 px.12 px.6 px.Supported File Types #
37 extensions, each individually toggleable. All off = any type accepted.
| Ext | MIME Type | Category |
|---|---|---|
| 7z | application/x-7z-compressed | Archive |
| aac | audio/aac | Audio |
| avi | video/x-msvideo | Video |
| avif | image/avif | Image |
| csv | text/csv | Data |
| doc | application/msword | Document |
| docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document | Document |
| flac | audio/flac | Audio |
| gif | image/gif | Image |
| gz | application/gzip | Archive |
| heic | image/heic | Image |
| html | text/html | Web |
| jpg | image/jpeg | Image |
| json | application/json | Data |
| mkv | video/x-matroska | Video |
| mov | video/quicktime | Video |
| mp3 | audio/mpeg | Audio |
| mp4 | video/mp4 | Video |
| ogg | audio/ogg | Audio |
| application/pdf | Document | |
| png | image/png | Image |
| ppt | application/vnd.ms-powerpoint | Document |
| pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation | Document |
| rar | application/vnd.rar | Archive |
| svg | image/svg+xml | Image |
| tar | application/x-tar | Archive |
| txt | text/plain | Text |
| wav | audio/wav | Audio |
| webm | video/webm | Video |
| webp | image/webp | Image |
| xls | application/vnd.ms-excel | Data |
| xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Data |
| xml | application/xml | Data |
| zip | application/zip | Archive |
Troubleshooting #
Drop zone is greyed out and unclickable
Missing provider credentials detected. Check that all required fields for your chosen provider are filled in the Properties panel.
Upload fails — "Network error" or HTTP 403
Almost always a CORS issue. Provider-specific fixes:
- Cloudinary — upload preset must be
Unsigned. - Supabase — add an RLS INSERT policy for the
anonrole on the bucket. - Cloudflare R2 Worker — ensure the Worker returns
Access-Control-Allow-Origin: *and handles OPTIONS preflight. - Google Drive — Web App must be deployed with access set to Anyone.
File URL missing from form submission
Either the form was submitted before the upload finished (wait for the green success state), or the Form Field Name property doesn't match the key your integration expects.
File picker doesn't open on click
Expected behaviour in Framer's canvas preview — browsers block file picker triggers in sandboxed iframes. Test on the published live site URL.
Type validation accepts files it shouldn't
If all type toggles are off, any file is accepted. To restrict types, turn at least one toggle on — only enabled types will be permitted.
Google Drive link returns 403
Your Google Workspace organisation may have external link sharing disabled. Try a personal Gmail account or ask your admin to allow external sharing.
Supabase URL returns 400 or 404
The bucket is likely not set to public. Go to Supabase Storage → your bucket → Settings → enable Public Bucket.
How to split multiple URLs in automations
Multiple URLs are comma-separated: https://cdn.../a.pdf,https://cdn.../b.pdf. In Zapier or Make, use a "Split text" step with , as the delimiter. In code: value.split(",").
For any issue not listed here, open the browser developer console (F12 → Console) and look for specific error messages. HTTP status codes like 400, 401, 403, or 413 each point to a specific configuration step that the error text will describe.