File Upload

Ask the user for files that meet stated rules (type, size, aspect ratio), checked before they're sent.

Pattern

≈ 731 tokens per use (tool 430 · guidance 204 · typical payload 97)How token costs are estimated

Example: File Upload

Launch the autumn blend ad on Instagram Stories and Feed.

Autumn blend creatives

Story imageKeep text off the top and bottom edges.
Feed image

When to use

  • The agent needs files from the user to continue: ad creatives, a receipt, a profile photo, a document to review.
  • The files have rules the agent can state up front: type, size or image shape.

When not to use

  • The file is already in a connected system. Fetch it instead.
  • The agent only needs a link or a short piece of text. Ask in text.

How the agent should use it

  • Use one slot per distinct file, with the platform's real requirements, e.g. Story 9:16 and Feed 1:1.
  • State accept, maxSizeMB and aspectRatio instead of describing them in prose.
  • Never ask for identity documents, payment cards or other sensitive files.
  • The agent receives metadata only. The host passes the files to its tools; the agent shouldn't claim to have seen the contents.

Interaction

Each slot is a drop zone and a button: drag files onto it, or press it (Enter or Space) to browse. Every file is checked on arrival:

RuleCheck
acceptMIME type (image/png), wildcard (image/*) or extension (.pdf; .jpg also matches .jpeg)
maxSizeMBFile size
aspectRatioThe image's real dimensions, within ±2%

Files that fail stay in the list with the reason, e.g. "This image is 1080×1080. It needs to be 9:16, e.g. 1080×1920", until removed. Images get a preview (object URLs, revoked on remove and unmount). A single-file slot replaces its file when a new one arrives. On submit, a missing required file or a failing file moves focus to that slot. Additions, rejections and removals are announced to screen readers.

The checks are plain functions exported from the schema file (validateUpload, matchesAccept, matchesAspectRatio, parseAspectRatio), so the server can run the same rules on the uploaded files.

Installation

npx shadcn@latest add https://your-site.com/r/file-upload.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { fileUploadTool } from "@/components/genui/file-upload.schema"

const tools = {
  // No execute(): the call is completed by the user's upload in the UI.
  [fileUploadTool.name]: tool({
    description: fileUploadTool.description,
    inputSchema: fileUploadTool.inputSchema,
  }),
}

File objects can't be sent to the agent, so the component hands them to the host with onFiles (once per slot, before onResult). Upload them there and give your tools a reference:

components/message.tsx
import { FileUpload } from "@/components/genui/file-upload"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-request_files") return null

  return (
    <FileUpload
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onFiles={(slotId, files) => uploadFiles(part.toolCallId, slotId, files)}
      onResult={(result) =>
        addToolResult({
          tool: "request_files",
          toolCallId: part.toolCallId,
          output: result,
        })
      }
    />
  )
}

The agent receives one of:

{
  action: "submit",
  files: [
    { slotId: "story", name: "autumn-story.png", type: "image/png", size: 1843200, width: 1080, height: 1920 },
    { slotId: "feed", name: "autumn-feed.jpg", type: "image/jpeg", size: 902144, width: 1080, height: 1080 },
  ],
}
{ action: "cancel" }

With LangGraph

Push it from a graph node with ui.push({ name: "request_files", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer. Upload the files from onFiles to storage your graph can read.

Restoring from history

Pass the stored result back as result. The component lists the submitted file names and sizes, without previews (the files are no longer in the browser), and never calls onResult again.

<FileUpload
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
titlestringWhat's being asked for: "Upload ad creatives"
descriptionstring?One short line under the title. Skip it when it restates the title
slotsUploadSlot[]1–6
submitLabelstring?Verb for the submit button. Default "Submit"

UploadSlot

FieldTypeNotes
idstringReturned with each file as slotId
labelstring"Story image"
descriptionstring?Guidance on what to upload. Read to screen readers with the drop zone, not shown; the accepted types, ratio and size limit are always shown
acceptstring[]MIME types or extensions
maxSizeMBnumber?
aspectRatiostring?width:height, e.g. 9:16. ±2% is accepted
requiredbooleanDefault true
multiplebooleanDefault false

Host props: status (streaming, ready, submitting, submitted, cancelled), onFiles, onResult, result and className.

Source

On this page