Skip to main content
FlowFn
IntegrationsTemplatesPricingDocsBlogSign inStart free
All documentation

User file uploads (flowfn.upload)

UpdatedMay 23, 2026Reading time2 min read

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

  1. Open the playground editor.
  2. Toggle Uploads next to the Public switch in the header.
  3. 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.upload returns 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's max_playground_asset_bytes cap.
  • Per-playground daily count: defaults to 200 uploads / day (plan-tunable via max_playground_user_uploads_per_day). Past the cap, flowfn.upload rejects with daily_upload_cap until the next UTC midnight.
  • Allowed MIME: images (image/*), video (video/*), audio (audio/*), and application/pdf. Other types reject with unsupported_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-url and upload-commit exactly 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.

Spotted an issue or have feedback?

support@flowfn.com
Back to docs hub →