> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# storeFile

> Upload a file to the workspace's object-store bucket and return a signed or public URL.

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CAPABILITY" />

`storeFile` uploads a file to the workspace's object-store bucket and returns either a signed URL (private, expiring) or a public URL (permanent). Use it from a Form block with a file input to capture user uploads, or from a page-side handler to store generated content. By the end of this page you can wire a Form to accept an upload and render the returned URL — no manual bucket setup required, since every workspace gets its own object-store bucket auto-provisioned.

## Invoke from a Form block

Attach a file field to a Form block and bind `storeFile` to `on_submit`. The runtime passes the uploaded file's bytes as `body`.

```json upload-form.json theme={null}
{
  "block": "Form",
  "props": {
    "schema_table": "uploads",
    "fields": ["label", "file"],
    "on_submit": {
      "capability": "storeFile",
      "args": {
        "filename": "avatar.png",
        "content_type": "image/png",
        "body": "iVBORw0KGgoAAAANSUhEUgAA...",
        "expires_in_seconds": 86400
      }
    }
  }
}
```

In real usage the file field's name, MIME type, and base64-encoded bytes come from the submitted form rather than literal strings — the runtime substitutes them at dispatch time.

## Inputs

<DefList>
  <Def term="filename" type="string" required>
    Destination filename in the bucket, e.g. `"avatar.png"`.
  </Def>

  <Def term="content_type" type="string" required>
    MIME type of the file, e.g. `"image/png"` or `"application/pdf"`.
  </Def>

  <Def term="body" type="ArrayBuffer | string" required>
    File content as raw bytes (`ArrayBuffer`) or a base64-encoded string.
  </Def>

  <Def term="expires_in_seconds" type="number" defaultValue="3600">
    How long the signed URL remains valid. Defaults to one hour. Ignored when `public` is `true`.
  </Def>

  <Def term="public" type="boolean" defaultValue="false">
    If `true`, returns a permanent public URL instead of a signed URL.
  </Def>
</DefList>

## Outputs

<DefList>
  <Def term="url" type="string">
    Signed URL (when `public` is `false`) or permanent public URL (when `public` is `true`).
  </Def>

  <Def term="object_key" type="string">
    Stable key identifying the object in the bucket. Persist this if you need to mint a fresh signed URL after the original expires.
  </Def>

  <Def term="bytes" type="number">Size of the stored file in bytes.</Def>

  <Def term="uploaded_at" type="string">
    ISO 8601 timestamp when the file was written to the bucket.
  </Def>
</DefList>

## Provisioning

No manual setup. Every workspace gets a dedicated object-store bucket on first use. Files are scoped to the workspace and are not accessible across workspace boundaries.

<Warning>
  Signed URLs expire. The default TTL is one hour (`expires_in_seconds: 3600`). A URL used after `uploaded_at + expires_in_seconds` returns 403. To serve the same object again later, store `object_key` from the upload response and request a fresh URL when needed.
</Warning>

## Error codes

<ErrorTable
  errors={[
{
  code: "file_too_large",
  cause: "The uploaded file exceeds the workspace's size limit.",
  fix: "Compress or split the file, or raise the workspace size limit if your tier allows.",
},
{
  code: "invalid_content_type",
  cause: "The `content_type` value is not an accepted MIME type.",
  fix: "Pass a recognized MIME type, e.g. `image/png` or `application/pdf`.",
},
{
  code: "invalid_input",
  cause: "One or more required fields failed schema validation.",
  fix: "Inspect `error.details` for field-level messages and correct the args.",
},
]}
/>

Every error returns the standard envelope:

<ExpectedOutput caption="Error envelope">
  ```json error-envelope.json theme={null}
  {
    "ok": false,
    "error": {
      "code": "file_too_large",
      "message": "Uploaded file exceeds the 25 MB workspace limit.",
      "details": { "bytes": 31457280, "limit": 26214400 }
    }
  }
  ```
</ExpectedOutput>

## Related

<Columns cols={2}>
  <Card title="authUser" icon="user" href="/capabilities/auth-user">
    Read the signed-in user's profile to associate uploads with the user.
  </Card>

  <Card title="File uploads guide" icon="file-arrow-up" href="/guides/file-uploads">
    End-to-end walkthrough: Form block, file field, and rendering the result.
  </Card>
</Columns>
