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

# File Uploads & Downloads

> requestFileUpload for presigned upload URLs, getFileUrl for presigned download URLs.

File fields store a `fileId` string. This page covers the full lifecycle: uploading a file and retrieving a download URL.

## requestFileUpload(options, signal?)

Requests a presigned upload URL.

```javascript theme={null}
const upload = await sdk.requestFileUpload({
  fieldKey: 'attachment',         // the file field key in your entity schema
  fileName: 'invoice.pdf',
  mimeType: 'application/pdf',    // optional but recommended
  sizeBytes: file.size,
});

// upload.fileId     — the ID to store on the record
// upload.uploadUrl  — presigned PUT URL (expires in ~15 minutes)
// upload.headers    — headers to include in the PUT request
```

## Upload the File

After receiving the presigned URL, PUT the file bytes directly to storage. The file never passes through your app's server.

```javascript theme={null}
await fetch(upload.uploadUrl, {
  method: 'PUT',
  headers: {
    'Content-Type': file.type,
    ...upload.headers,
  },
  body: file,   // File or Blob
});
```

## Save the fileId on the Record

After the upload completes, store the `fileId` in the record:

```javascript theme={null}
await sdk.updateRecord('expenses', recordId, {
  attachment: upload.fileId,
});
```

## Complete Example

```javascript theme={null}
async function uploadAttachment(recordId, file) {
  const upload = await sdk.requestFileUpload({
    fieldKey: 'attachment',
    fileName: file.name,
    mimeType: file.type,
    sizeBytes: file.size,
  });

  const uploadResponse = await fetch(upload.uploadUrl, {
    method: 'PUT',
    headers: { 'Content-Type': file.type, ...upload.headers },
    body: file,
  });

  if (!uploadResponse.ok) {
    throw new Error('Upload to storage failed');
  }

  await sdk.updateRecord('expenses', recordId, {
    attachment: upload.fileId,
  });
}
```

<Note>If the presigned URL expires before the upload completes, call `requestFileUpload` again to obtain a fresh URL and retry the PUT.</Note>

***

## getFileUrl(fileId, signal?)

Returns a presigned download URL for a file attachment.

```javascript theme={null}
const file = await sdk.getFileUrl(record.attachment);
// file.url         — presigned download URL (valid for ~1 hour)
// file.fileName    — original file name as uploaded
// file.mimeType    — MIME type, or null if not recorded at upload
// file.expiresAt   — ISO 8601 expiry timestamp
```

**Opening the file in a new tab:**

```javascript theme={null}
const file = await sdk.getFileUrl(record.attachment);
window.open(file.url, '_blank');
```

**Triggering a download:**

```javascript theme={null}
const file = await sdk.getFileUrl(record.attachment);
const a = document.createElement('a');
a.href = file.url;
a.download = file.fileName;
a.click();
```

<Note>The presigned URL is valid for \~1 hour. Do not cache it long-term — call `getFileUrl` again if you need a fresh URL.</Note>

***

## Limits

| Tier       | Max single file | Max attachment storage |
| ---------- | --------------- | ---------------------- |
| Standard   | 10 MB           | 250 MB                 |
| Enterprise | 50 MB           | 5 GB                   |

Attempting to upload a file that exceeds the storage quota throws `ErrorCode.StorageLimitReached`.
