---
name: implementing-ui-bundle-file-upload
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves uploading, attaching, or dropping files. Use this skill when adding file upload functionality to a UI bundle app. Provides progress tracking and Salesforce ContentVersion integration. This feature provides programmatic APIs ONLY — build custom UI using the upload() API. ALWAYS use this instead of building file upload from scratch with FormData or XHR."
metadata:
version: "1.0"
---
# File Upload API (workflow)
When the user wants file upload functionality in a React UI bundle, follow this workflow. This feature provides **APIs only** — you must build the UI components yourself using the provided APIs.
## CRITICAL: This is an API-only package
The package exports **programmatic APIs**, not React components or hooks. You will:
- Use the `upload()` function to handle file uploads with progress tracking
- Build your own custom UI (file input, dropzone, progress bars, etc.)
- Track upload progress through the `onProgress` callback
**Do NOT:**
- Expect pre-built components like `` — they are not exported
- Try to import React hooks like `useFileUpload` — they are not exported
- Look for dropzone components — they are not exported
The source code contains reference components for demonstration, but they are **not available** as imports. Use them as examples to build your own UI.
## 1. Install the package
```bash
npm install @salesforce/ui-bundle-template-feature-react-file-upload
```
Dependencies are automatically installed:
- `@salesforce/ui-bundle` (API client)
- `@salesforce/sdk-data` (data SDK)
## 2. Understand the three upload patterns
### Pattern A: Basic upload (no record linking)
Upload files to Salesforce and get back `contentBodyId` for each file. No ContentVersion record is created.
**When to use:**
- User wants to upload files first, then create/link them to a record later
- Building a multi-step form where the record doesn't exist yet
- Deferred record linking scenarios
```tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";
const results = await upload({
files: [file1, file2],
onProgress: (progress) => {
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
},
});
// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: undefined (no record linked)
```
### Pattern B: Upload with immediate record linking
Upload files and immediately link them to an existing Salesforce record by creating ContentVersion records.
**When to use:**
- Record already exists (Account, Opportunity, Case, etc.)
- User wants files immediately attached to the record
- Direct upload-and-attach scenarios
```tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";
const results = await upload({
files: [file1, file2],
recordId: "001xx000000yyyy", // Existing record ID
onProgress: (progress) => {
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
},
});
// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: "068..." (linked to record)
```
### Pattern C: Deferred record linking (record creation flow)
Upload files without a record, then link them after the record is created.
**When to use:**
- Building a "create record with attachments" form
- Record doesn't exist until form submission
- Need to upload files before knowing the final record ID
```tsx
import {
upload,
createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
// Step 1: Upload files (no recordId)
const uploadResults = await upload({
files: [file1, file2],
onProgress: (progress) => console.log(progress),
});
// Step 2: Create the record
const newRecordId = await createRecord(formData);
// Step 3: Link uploaded files to the new record
for (const file of uploadResults) {
const contentVersionId = await createContentVersion(
new File([""], file.fileName),
file.contentBodyId,
newRecordId,
);
}
```
## 3. Build your custom UI
The package provides the backend — you build the frontend. Here's a minimal example:
```tsx
import {
upload,
type FileUploadProgress,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
import { useState } from "react";
function CustomFileUpload({ recordId }: { recordId?: string }) {
const [progress, setProgress] = useState