implementing-ui-bundle-file-upload
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFile Upload API (workflow)
文件上传API(工作流程)
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.
当用户需要在React UI bundle中实现文件上传功能时,请遵循以下工作流程。此功能仅提供API——你必须使用提供的API自行构建UI组件。
CRITICAL: This is an API-only package
重要提示:这是一个仅提供API的包
The package exports programmatic APIs, not React components or hooks. You will:
- Use the function to handle file uploads with progress tracking
upload() - Build your own custom UI (file input, dropzone, progress bars, etc.)
- Track upload progress through the callback
onProgress
Do NOT:
- Expect pre-built components like — they are not exported
<FileUpload /> - Try to import React hooks like — they are not exported
useFileUpload - 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.
该包导出编程式API,而非React组件或钩子。你需要:
- 使用函数处理带进度跟踪的文件上传
upload() - 自行构建自定义UI(文件输入框、拖放区域、进度条等)
- 通过回调跟踪上传进度
onProgress
请勿:
- 期望获取如这类预构建组件——它们并未被导出
<FileUpload /> - 尝试导入这类React钩子——它们并未被导出
useFileUpload - 寻找拖放区域组件——它们并未被导出
源代码中包含用于演示的参考组件,但它们无法被导入。你可以将它们作为示例来构建自己的UI。
1. Install the package
1. 安装包
bash
npm install @salesforce/ui-bundle-template-feature-react-file-uploadDependencies are automatically installed:
- (API client)
@salesforce/ui-bundle - (data SDK)
@salesforce/sdk-data
bash
npm install @salesforce/ui-bundle-template-feature-react-file-upload依赖会自动安装:
- (API客户端)
@salesforce/ui-bundle - (数据SDK)
@salesforce/sdk-data
2. Understand the three upload patterns
2. 了解三种上传模式
Pattern A: Basic upload (no record linking)
模式A:基础上传(无记录关联)
Upload files to Salesforce and get back for each file. No ContentVersion record is created.
contentBodyIdWhen 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)将文件上传至Salesforce,并获取每个文件的。不会创建ContentVersion记录。
contentBodyId适用场景:
- 用户希望先上传文件,之后再创建/关联到记录
- 构建多步骤表单,此时记录尚未存在
- 延迟记录关联的场景
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..."(始终可用)
// results[0].contentVersionId: undefined(未关联记录)Pattern B: Upload with immediate record linking
模式B:即时关联记录的上传
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)上传文件并立即通过创建ContentVersion记录,将其关联到现有的Salesforce记录。
适用场景:
- 记录已存在(如客户、商机、案例等)
- 用户希望文件立即附加到记录上
- 直接上传并关联的场景
tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";
const results = await upload({
files: [file1, file2],
recordId: "001xx000000yyyy", // 现有记录ID
onProgress: (progress) => {
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
},
});
// results[0].contentBodyId: "069..."(始终可用)
// results[0].contentVersionId: "068..."(已关联到记录)Pattern C: Deferred record linking (record creation flow)
模式C:延迟关联记录(记录创建流程)
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,
);
}在无记录的情况下上传文件,待记录创建后再关联。
适用场景:
- 构建“创建记录并附加文件”的表单
- 记录在表单提交前不存在
- 需要在知晓最终记录ID前上传文件
tsx
import {
upload,
createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
// 步骤1:上传文件(无recordId)
const uploadResults = await upload({
files: [file1, file2],
onProgress: (progress) => console.log(progress),
});
// 步骤2:创建记录
const newRecordId = await createRecord(formData);
// 步骤3:将上传的文件关联到新记录
for (const file of uploadResults) {
const contentVersionId = await createContentVersion(
new File([""], file.fileName),
file.contentBodyId,
newRecordId,
);
}3. Build your custom UI
3. 构建自定义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<Map<string, FileUploadProgress>>(new Map());
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
await upload({
files,
recordId,
onProgress: (fileProgress) => {
setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
},
});
};
return (
<div>
<input type="file" multiple onChange={handleFileSelect} />
{Array.from(progress.entries()).map(([fileName, fileProgress]) => (
<div key={fileName}>
{fileName}: {fileProgress.status} - {fileProgress.progress}%
{fileProgress.error && <span>Error: {fileProgress.error}</span>}
</div>
))}
</div>
);
}该包提供后端支持——你需要构建前端。以下是一个最简示例:
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<Map<string, FileUploadProgress>>(new Map());
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
await upload({
files,
recordId,
onProgress: (fileProgress) => {
setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
},
});
};
return (
<div>
<input type="file" multiple onChange={handleFileSelect} />
{Array.from(progress.entries()).map(([fileName, fileProgress]) => (
<div key={fileName}>
{fileName}: {fileProgress.status} - {fileProgress.progress}%
{fileProgress.error && <span>Error: {fileProgress.error}</span>}
</div>
))}
</div>
);
}4. Track upload progress
4. 跟踪上传进度
The callback fires multiple times for each file as it moves through stages:
onProgress| Status | When | Progress Value |
|---|---|---|
| File queued for upload | |
| Upload in progress (XHR) | |
| Creating ContentVersion (if recordId provided) | |
| Upload complete | |
| Upload failed | |
Always provide visual feedback:
- Show file name
- Display current status
- Render progress bar for "uploading" status
- Show error message if status is "error"
onProgress| 状态 | 触发时机 | 进度值范围 |
|---|---|---|
| 文件已排入上传队列 | |
| 上传进行中(XHR) | |
| 正在创建ContentVersion(若提供了recordId) | |
| 上传完成 | |
| 上传失败 | |
务必提供视觉反馈:
- 显示文件名
- 展示当前状态
- 为"uploading"状态渲染进度条
- 若状态为"error",显示错误信息
5. Cancel uploads (optional)
5. 取消上传(可选)
Use an to allow users to cancel uploads:
AbortControllertsx
const abortController = new AbortController();
const handleUpload = async (files: File[]) => {
try {
await upload({
files,
signal: abortController.signal,
onProgress: (progress) => console.log(progress),
});
} catch (error) {
console.error("Upload cancelled or failed:", error);
}
};
const cancelUpload = () => {
abortController.abort();
};使用允许用户取消上传:
AbortControllertsx
const abortController = new AbortController();
const handleUpload = async (files: File[]) => {
try {
await upload({
files,
signal: abortController.signal,
onProgress: (progress) => console.log(progress),
});
} catch (error) {
console.error("Upload cancelled or failed:", error);
}
};
const cancelUpload = () => {
abortController.abort();
};6. Link to current user (special case)
6. 关联到当前用户(特殊场景)
If the user wants to upload files to their own profile or personal library:
tsx
import {
upload,
getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
const userId = await getCurrentUserId();
await upload({ files, recordId: userId });若用户希望将文件上传至自己的个人资料或私人库:
tsx
import {
upload,
getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
const userId = await getCurrentUserId();
await upload({ files, recordId: userId });API Reference
API参考
upload(options)
upload(options)
Main upload API that handles complete flow with progress tracking.
typescript
interface UploadOptions {
files: File[];
recordId?: string | null; // If provided, creates ContentVersion
onProgress?: (progress: FileUploadProgress) => void;
signal?: AbortSignal; // Optional cancellation
}
interface FileUploadProgress {
fileName: string;
status: "pending" | "uploading" | "processing" | "success" | "error";
progress: number; // 0-100 for uploading, 0 for other states
error?: string;
}
interface FileUploadResult {
fileName: string;
size: number;
contentBodyId: string; // Always available
contentVersionId?: string; // Only if recordId was provided
}Returns:
Promise<FileUploadResult[]>处理完整上传流程并带进度跟踪的核心上传API。
typescript
interface UploadOptions {
files: File[];
recordId?: string | null; // 若提供,将创建ContentVersion
onProgress?: (progress: FileUploadProgress) => void;
signal?: AbortSignal; // 可选的取消信号
}
interface FileUploadProgress {
fileName: string;
status: "pending" | "uploading" | "processing" | "success" | "error";
progress: number; // 上传中为0-100,其他状态为0
error?: string;
}
interface FileUploadResult {
fileName: string;
size: number;
contentBodyId: string; // 始终可用
contentVersionId?: string; // 仅在提供recordId时返回
}返回值:
Promise<FileUploadResult[]>createContentVersion(file, contentBodyId, recordId)
createContentVersion(file, contentBodyId, recordId)
Manually create a ContentVersion record from a previously uploaded file.
typescript
async function createContentVersion(
file: File,
contentBodyId: string,
recordId: string,
): Promise<string | undefined>;Parameters:
- — File object (used for metadata like name)
file - — ContentBody ID from previous upload
contentBodyId - — Record ID for FirstPublishLocationId
recordId
Returns: ContentVersion ID if successful
从已上传的文件手动创建ContentVersion记录。
typescript
async function createContentVersion(
file: File,
contentBodyId: string,
recordId: string,
): Promise<string | undefined>;参数:
- — 文件对象(用于获取名称等元数据)
file - — 之前上传得到的ContentBody ID
contentBodyId - — FirstPublishLocationId对应的记录ID
recordId
返回值: 成功时返回ContentVersion ID
getCurrentUserId()
getCurrentUserId()
Get the current user's Salesforce ID.
typescript
async function getCurrentUserId(): Promise<string>;Returns: Current user ID
获取当前用户的Salesforce ID。
typescript
async function getCurrentUserId(): Promise<string>;返回值: 当前用户ID
Common UI patterns
常见UI模式
File input with button
带按钮的文件输入框
tsx
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />tsx
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />Drag-and-drop zone
拖放区域
Build your own dropzone using native events:
tsx
function DropZone({ onDrop }: { onDrop: (files: File[]) => void }) {
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
onDrop(files);
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
style={{ border: "2px dashed #ccc", padding: "2rem" }}
>
Drop files here
</div>
);
}使用原生事件构建自己的拖放区域:
tsx
function DropZone({ onDrop }: { onDrop: (files: File[]) => void }) {
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
onDrop(files);
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
style={{ border: "2px dashed #ccc", padding: "2rem" }}
>
拖放文件至此处
</div>
);
}Progress bar
进度条
tsx
{
progress.status === "uploading" && (
<div style={{ width: "100%", background: "#eee" }}>
<div
style={{
width: `${progress.progress}%`,
background: "#0176d3",
height: "8px",
}}
/>
</div>
);
}tsx
{
progress.status === "uploading" && (
<div style={{ width: "100%", background: "#eee" }}>
<div
style={{
width: `${progress.progress}%`,
background: "#0176d3",
height: "8px",
}}
/>
</div>
);
}Decision tree for agents
智能体决策树
User asks for file upload functionality:
-
Ask about record context:
- "Do you want to link uploaded files to a specific record, or upload them first and link later?"
-
Based on response:
- Link to existing record → Use Pattern B with
recordId - Upload first, link later → Use Pattern A (no recordId), then Pattern C for linking
- Link to current user → Use Pattern B with
getCurrentUserId()
- Link to existing record → Use Pattern B with
-
Build the UI:
- Create file input or dropzone (not provided by package)
- Add progress display for each file (status + progress bar)
- Handle errors in the UI
-
Test the implementation:
- Verify progress callbacks fire correctly
- Check that is returned
contentBodyId - If was provided, verify
recordIdis returnedcontentVersionId
用户询问文件上传功能:
-
询问记录上下文:
- "你希望将上传的文件关联到特定记录,还是先上传之后再关联?"
-
根据回复选择方案:
- 关联到现有记录 → 使用带的模式B
recordId - 先上传,后关联 → 使用无recordId的模式A,之后使用模式C进行关联
- 关联到当前用户 → 使用带的模式B
getCurrentUserId()
- 关联到现有记录 → 使用带
-
构建UI:
- 创建文件输入框或拖放区域(包未提供)
- 为每个文件添加进度展示(状态+进度条)
- 在UI中处理错误
-
测试实现:
- 验证进度回调是否正确触发
- 检查是否返回
contentBodyId - 若提供了,验证是否返回
recordIdcontentVersionId
Reference implementation
参考实现
The package includes a reference implementation in with:
src/features/fileupload/- — Complete component with dropzone and dialog
FileUpload.tsx - — Progress tracking dialog
FileUploadDialog.tsx - — Drag-and-drop zone
FileUploadDropZone.tsx - — React hook for state management
useFileUpload.ts
These are NOT exported but can be viewed as examples. Read the source files to understand patterns for building your own UI.
该包在目录中包含参考实现,包括:
src/features/fileupload/- — 完整的带拖放区域和对话框的组件
FileUpload.tsx - — 进度跟踪对话框
FileUploadDialog.tsx - — 拖放区域
FileUploadDropZone.tsx - — 用于状态管理的React钩子
useFileUpload.ts
这些组件并未被导出,但可作为示例查看。阅读源文件以了解构建自定义UI的模式。
Troubleshooting
故障排除
Upload fails with CORS error:
- Ensure the UI bundle is properly deployed to Salesforce or running on
localhost - Check that the org allows the origin in CORS settings
No progress updates:
- Verify callback is provided
onProgress - Check that the callback function updates React state correctly
ContentVersion not created:
- Verify is provided to
recordIdfunctionupload() - Check that the record ID is valid and exists in the org
- Ensure user has permissions to create ContentVersion records
Files upload but don't appear in record:
- Verify is correct
recordId - Check that ContentVersion was created (look for in results)
contentVersionId - Confirm user has access to view files on the record
上传因CORS错误失败:
- 确保UI bundle已正确部署到Salesforce或在运行
localhost - 检查组织是否在CORS设置中允许该源
无进度更新:
- 确认已提供回调
onProgress - 检查回调函数是否正确更新React状态
未创建ContentVersion:
- 确认已向函数提供
upload()recordId - 检查记录ID是否有效且存在于组织中
- 确保用户拥有创建ContentVersion记录的权限
文件已上传但未显示在记录中:
- 确认正确
recordId - 检查是否已创建ContentVersion(在结果中查看)
contentVersionId - 确认用户有权查看记录上的文件
DO NOT do these things
请勿执行以下操作
- ❌ Build XHR/fetch upload logic from scratch — use the API
upload() - ❌ Try to import component — it's not exported
<FileUpload /> - ❌ Try to import hook — it's not exported
useFileUpload - ❌ Use third-party file upload libraries when this feature exists
- ❌ Skip progress tracking — always provide user feedback
- ❌ Ignore errors — always handle and display error messages
- ❌ 从零开始构建XHR/fetch上传逻辑——请使用API
upload() - ❌ 尝试导入组件——它并未被导出
<FileUpload /> - ❌ 尝试导入钩子——它并未被导出
useFileUpload - ❌ 当此功能存在时使用第三方文件上传库
- ❌ 跳过进度跟踪——务必向用户提供反馈
- ❌ 忽略错误——务必处理并显示错误信息