Skip to guide
form koi.Start free ↗

File
uploads.

A brief, a screenshot, a small document. Let visitors attach the context you need, with private storage and a clear lifetime.

1. Enable uploads

  1. Enable uploads in form settings and choose a retention period from 1–365 days for new files.
  2. Copy the updated example from the connection guide. Native HTML needs enctype="multipart/form-data".
  3. Use a separate file field such as attachments. For AJAX, submit a FormData body and let the browser set Content-Type.
  4. Upload a small test file, find its message and download it from the inbox.
File input — add inside your multipart form
<label for="attachments">Attachments (optional)</label>
<input id="attachments" name="attachments" type="file" multiple
  accept=".pdf,.png,.jpg,.jpeg,.webp,.txt,.csv"
  aria-describedby="files-help">
<p id="files-help">Up to 3 files. 5 MiB each, 10 MiB total.</p>

The accept attribute helps the picker; the server independently checks limits and formats. Upload directly to your form’s public API endpoint. Do not send files through a management endpoint.

2. Add the file input

LimitAccepted
Count and sizeUp to three files, 5 MiB per file and 10 MiB total per submission.
FormatsPDF, PNG, JPEG, WebP, UTF-8 plain text and CSV.
Text fieldsStill independently limited to 64 KiB. Multipart parsing allows an additional 128 KiB overhead beyond file bytes.
Optional inputAn untouched empty file input is ignored. A named zero-byte file is rejected.
Workspace capacityCheck storage usage in the workspace. Reserved and pending-deletion bytes continue to count until cleanup succeeds.
Files are not malware-scanned.

Format and extension checks are not a safety guarantee. Handle visitor files as untrusted content and apply your own organization’s review process before opening or processing them.

File-only submissions are supported. Changing file contents, names or order changes the payload for idempotent retries. The generated React example fingerprints file bytes so an edited attachment gets a new request key.

3. Download attachments

The inbox shows file names, sizes, availability and expiry. Downloads require the owning workspace’s session, or a server management token with files:read. Message-read permission alone cannot download file bytes.

Files are forced downloads with private, non-cacheable responses. There is no public bucket URL or transferable download link. Email notifications list the attachments and link to the message; the files themselves are not attached to the email. A notification recipient still needs access to the owning workspace to download.

Server integrations use GET /v1/submissions/{id}/attachments/{attachmentId}. Get attachment metadata from the message detail first. See management API permissions.

4. Attachment expiry

The chosen retention applies to newly received files. Existing files keep their original expiry. A new file cannot outlive its message’s automatic expiry. Disabling uploads prevents new attachments but does not delete previous ones.

Downloads stop at expiry, even if physical deletion is still queued. Deleting a message or form removes file access immediately and schedules storage cleanup. Storage capacity is released after physical deletion succeeds; it may not drop the instant you delete a message.

Expired metadata can remain alongside its message so the history explains what was received. If you need a file longer, download it before its expiry and follow your own retention policy.