What it does
End-user file uploads from inside the playground iframe. Once you flip Uploads on in the editor header, public visitors can call flowfn.upload(file) from your playground JS and the file lands in a private bucket the team can access through the bottom drawer's Uploads tab.
Use it to collect attachments, profile photos, resumes, screenshots, audio clips — anything you'd reach for a form file field for, but inside an interactive mini-app.
Enabling uploads
- Open the playground editor.
- Toggle Uploads next to the Public switch in the header.
- Click Save.
The toggle is off by default — when off, flowfn.upload rejects synchronously with uploads_disabled.
Calling from JS
flowfn.upload accepts a File, a Blob, or an <input type="file"> element directly. Returns a Promise resolving to { id, url, filename, content_type, size }:
// HTML: <input id="picker" type="file" accept="image/*">
document.querySelector('#picker').addEventListener('change', async (e) => {
try {
const result = await flowfn.upload(e.target);
preview.src = result.url; // signed read URL, ~1h TTL
await flowfn.data.insert('submissions', {
filename: result.filename,
url: result.url,
});
} catch (err) {
if (err.code === 'file_too_large') alert('File is too big.');
else if (err.code === 'daily_upload_cap') alert('Too many uploads today.');
else alert('Upload failed: ' + err.message);
}
});
How files are stored
- Private bucket, signed URLs. Unlike owner-uploaded assets (public CloudFront URLs), end-user uploads are stored privately and accessed via short-lived signed read URLs (~1 hour TTL). The URL
flowfn.uploadreturns is one of those. - Per-slug grouping. S3 keys follow
playgrounds/user-uploads/<slug>/<uploadId>-<filename>so cleanup by playground slug is straightforward. - If you need long-term access, immediately stash the URL into a data sheet (
flowfn.data.insert) or persist it through a workflow. The signed URL expires after ~1 hour; the team can always mint a fresh one through the Uploads tab.
Limits
- Per-file size: plan-tuned via
plan.settings.max_playground_user_upload_bytes. Defaults to your plan'smax_playground_asset_bytescap. - Per-playground daily count: defaults to 200 uploads / day (plan-tunable via
max_playground_user_uploads_per_day). Past the cap,flowfn.uploadrejects withdaily_upload_capuntil the next UTC midnight. - Allowed MIME: images (
image/*), video (video/*), audio (audio/*), andapplication/pdf. Other types reject withunsupported_type. - Rate limit: 30 upload-URL requests / minute / IP / slug (returns
rate_limited).
Security model
- No bearer credential. Any visitor who can render the playground can upload. If you need authenticated uploads, gate the playground itself (Password / Embed).
- Standard public access policy applies. Password / embed-token gates apply to
upload-urlandupload-commitexactly like they do to the page render. - Files are private by default. A leaked signed URL works for ~1 hour, then expires. The team can mint a fresh one any time through the Uploads pane.
- The team owns the uploads. They count against the team's plan storage budget. Manage / delete them from the bottom drawer's Uploads tab.
Error codes
The SDK rejects the upload promise with an Error whose .code mirrors the server. Common ones:
uploads_disabled— toggle is off.unsupported_type— MIME type not in the allowlist.file_too_large— exceeds your plan's per-file byte cap.daily_upload_cap— playground hit its daily count cap.rate_limited— too many upload requests from this IP in the last minute.requires_auth— playground is password / embed gated and the request didn't carry credentials.