# upload

> `Upload(onUploaded: Ingest) { … }` is a file picker wearing whatever you put inside it. When someone chooses a file its bytes are stored and your action receives an `UploadedFile` — the path they landed at, plus the name, type and size the browser reported. The bytes never pass through your code.

<!-- id: ui-upload · area: ui · stability: preview · html: https://osysharp.com/reference/ui/upload/ -->

## Summary        {#summary}
`Upload` is a **file picker you supply the appearance of**. Put anything inside it — a button, a row with an icon,
a whole drop zone — and it becomes the thing a person clicks to choose a file.

When they choose one, the bytes are stored and your action is called with an **`UploadedFile`**:

| field | what it is |
|---|---|
| `Path` | where the bytes were stored. This is the value you keep. |
| `FileName` | the original name the browser reported |
| `ContentType` | the type the browser reported |
| `Length` | the size in bytes, as the browser reported it |

The same value comes back from [camera and microphone](https://osysharp.com/reference/ui/capture/)'s `Camera.Capture()` and both `StopRecording()`s, so one
`Ingest(UploadedFile f)` serves a chosen file and a photograph alike.

## Signature      {#signature}
```osy syntax
Upload(onUploaded: Ingest) { Text("Choose a file"); }     // your own content is the button
Upload(accept: "image/*", onUploaded: Ingest) { … }       // narrow what the picker offers
```

## Description    {#description}

### The content inside it IS the button   {#content}
There is no built-in appearance to override. Whatever you render inside the `Upload` is what a person sees and
clicks — so it takes your icons, your spacing and your variants like anything else.

```osy title="a styled upload button" sample=file-manager/model/pages/manager.osy#UploadButton
[Composable] component UploadButton(Action<UploadedFile> onUploaded) {
  variants {
    base { Display = Display.InlineFlex; H = "32px"; Px = 3; Bg = Colors.Primary; Color = Colors.OnPrimary; Rounded = Radius.Md;
           FontSize = FontSize.Body; FontWeight = FontWeight.Semibold; WhiteSpace = WhiteSpace.Nowrap; Transition = Motion.Fast; }
  }
  render {
    Upload(onUploaded: onUploaded) {
      Row(gap: 2, align: Align.Center, justify: Justify.Center) { Icon(Icons.Upload, size: 18); Text("Upload"); }
    }
  }
}
```

### The bytes never reach your code   {#bytes}
By the time your action runs, the file is already stored. You receive a **path**, not a buffer — so a 9 MB
photograph costs your action nothing, and there is no way to accidentally hold one in a field.

What you do with the path is the interesting part: usually build a `FileAsset` from it, which is what gives the
file an owner, grants, deduplication and a quota. `demo/file-manager` is the worked version.

### What may be uploaded   {#types}
Images, audio, video, and a few text formats (`text/plain`, `text/markdown`, `text/csv`, `application/json`). The
limit is **10 MB** per file.

⛔ **SVG is deliberately refused**, even though it is an image. An SVG is a *document* that can carry script, so a
stored one served back from your own origin is a cross-site-scripting vector; every other image format is inert.
If you want vector art in your app, ship it as an asset instead — see [textures](https://osysharp.com/reference/ui/textures/) for which folder each kind of
art belongs in.

### Uploading is an authorized act   {#authorization}
The file store accepts writes from a **signed-in user**. An app with no users can show a picker and keep nothing —
and the refusal arrives as an error your action can catch and report, not as a silent nothing.

This is the same rule [camera and microphone](https://osysharp.com/reference/ui/capture/) runs into, and for the same reason: a file that arrives from a browser has an
owner, and an app with no users has nobody to be one.

### `accept:` narrows the picker, it does not enforce anything   {#accept}
`accept: "image/*"` tells the browser which files to offer by default. It is a convenience for the person choosing,
not a guarantee — the server decides what it will actually store, and refuses everything outside the list above.

## See also   {#see-also}
- [camera and microphone](https://osysharp.com/reference/ui/capture/) — the camera and microphone, which answer the same `UploadedFile`
- [Files (addressing something the app stores)](https://osysharp.com/reference/storage/index/) — what to do with the path once you have it
- [textures](https://osysharp.com/reference/ui/textures/) — art the app SHIPS, which is a different thing from a file a person uploads
- [component](https://osysharp.com/reference/ui/component/) — actions, and where an `onUploaded` handler lives
