Runtime Support: ChatGPT Apps onlyThis hook is not available in MCP Apps. Calling it in an MCP Apps environment will throw an error.
The useFiles hook provides methods for uploading, selecting, and downloading files within your widget. Files are managed by the host and can be referenced across tool calls.
Basic usage
import { useFiles } from "skybridge/web";
import { useState } from "react";
function FileUploader() {
const { upload } = useFiles();
const [fileId, setFileId] = useState<string | null>(null);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const { fileId } = await upload(file);
setFileId(fileId);
}
};
return (
<div>
<input type="file" onChange={handleUpload} />
{fileId && <p>Uploaded file ID: {fileId}</p>}
</div>
);
}
Returns
upload
upload: (file: File, options?: { library?: boolean }) => Promise<{ fileId: string }>
Uploads a file to the host. Returns a promise that resolves with the file metadata containing a unique fileId.
file: File
- The file object to upload
options?: { library?: boolean }
- Pass
{ library: true } to also save the upload into the user’s ChatGPT file library (when available)
selectFiles
selectFiles: () => Promise<Array<{ fileId: string; fileName?: string; mimeType?: string }>>
Opens ChatGPT’s file library picker and returns app-authorized files selected by the user. Feature-detect before using — this method may not be available on all host versions.
getDownloadUrl
getDownloadUrl: (file: { fileId: string }) => Promise<{ downloadUrl: string }>
Get the download URL of a file. Returns a promise that resolves with a downloadUrl that can be used to fetch the file content.
file: { fileId: string }
- An object containing the
fileId of the file to download
Works for files uploaded by the widget, files selected via selectFiles(), or files provided via tool/file params.
Examples
Image Upload and Preview
import { useFiles } from "skybridge/web";
import { useState } from "react";
function ImageUploader() {
const { upload, getDownloadUrl } = useFiles();
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
setIsUploading(true);
try {
const { fileId } = await upload(file);
const { downloadUrl } = await getDownloadUrl({ fileId });
setPreviewUrl(downloadUrl);
} catch (error) {
console.error("Upload failed:", error);
} finally {
setIsUploading(false);
}
};
return (
<div>
<input
type="file"
accept="image/*"
onChange={handleUpload}
disabled={isUploading}
/>
{isUploading && <p>Uploading...</p>}
{previewUrl && <img src={previewUrl} alt="Preview" />}
</div>
);
}
Upload to File Library
import { useFiles } from "skybridge/web";
import { useState } from "react";
function LibraryUploader() {
const { upload } = useFiles();
const [fileId, setFileId] = useState<string | null>(null);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Save to the user's ChatGPT file library
const { fileId } = await upload(file, { library: true });
setFileId(fileId);
};
return (
<div>
<input type="file" onChange={handleUpload} />
{fileId && <p>Saved to library: {fileId}</p>}
</div>
);
}
Select Files from Library
import { useFiles } from "skybridge/web";
import type { FileMetadata } from "skybridge/web";
import { useState } from "react";
function FileSelector() {
const { selectFiles, getDownloadUrl } = useFiles();
const [files, setFiles] = useState<FileMetadata[]>([]);
const handleSelect = async () => {
const selected = await selectFiles();
setFiles(selected);
};
const handleDownload = async (fileId: string) => {
const { downloadUrl } = await getDownloadUrl({ fileId });
window.open(downloadUrl, "_blank");
};
return (
<div>
<button onClick={handleSelect}>Select from Library</button>
<ul>
{files.map((file) => (
<li key={file.fileId}>
{file.fileName ?? file.fileId}
{file.mimeType && <span> ({file.mimeType})</span>}
<button onClick={() => handleDownload(file.fileId)}>
Download
</button>
</li>
))}
</ul>
</div>
);
}
Document Manager
import { useFiles } from "skybridge/web";
import { useState } from "react";
type Document = {
name: string;
fileId: string;
};
function DocumentManager() {
const { upload, getDownloadUrl } = useFiles();
const [documents, setDocuments] = useState<Document[]>([]);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const { fileId } = await upload(file);
setDocuments((prev) => [...prev, { name: file.name, fileId }]);
};
const handleDownload = async (doc: Document) => {
const { downloadUrl } = await getDownloadUrl({ fileId: doc.fileId });
window.open(downloadUrl, "_blank");
};
return (
<div>
<input type="file" onChange={handleUpload} />
<ul>
{documents.map((doc) => (
<li key={doc.fileId}>
{doc.name}
<button onClick={() => handleDownload(doc)}>Download</button>
</li>
))}
</ul>
</div>
);
}
Multiple File Upload
import { useFiles } from "skybridge/web";
import { useState } from "react";
function MultiFileUploader() {
const { upload } = useFiles();
const [uploadedIds, setUploadedIds] = useState<string[]>([]);
const [progress, setProgress] = useState<number>(0);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
if (files.length === 0) return;
const ids: string[] = [];
for (let i = 0; i < files.length; i++) {
const { fileId } = await upload(files[i]);
ids.push(fileId);
setProgress(((i + 1) / files.length) * 100);
}
setUploadedIds(ids);
setProgress(0);
};
return (
<div>
<input type="file" multiple onChange={handleUpload} />
{progress > 0 && <progress value={progress} max={100} />}
{uploadedIds.length > 0 && (
<p>Uploaded {uploadedIds.length} files</p>
)}
</div>
);
}