implementing-ui-bundle-file-upload について
Salesforce React UI bundleでAPIを使ったファイルアップロードを実装するスキルです。独自のアップロード画面を構築する開発者向けで、進捗追跡やキャンセル、レコードへの即時・後続の関連付けに対応します。
主な機能
- 01コールバックによる進捗追跡
- 02Salesforceレコードへの関連付け
- 03アップロード後のContentVersion作成
- 04AbortControllerによるキャンセル
- 05現在のSalesforceユーザーIDの取得
活用シーン
- 01既存レコードへのファイル添付
- 02レコード作成前のファイルアップロード
- 03独自のアップロード・進捗画面の構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill implementing-ui-bundle-file-uploadインストール後、Claude Code で/implementing-ui-bundle-file-uploadを入力して読み込まれたか確認します。
詳細
ファイルアップロード API(ワークフロー)
React UI バンドルでファイルアップロード機能が必要な場合は、このワークフローに従ってください。この機能が提供するのは API のみ です。提供される API を使って、UI コンポーネントはご自身で構築する必要があります。
重要:このパッケージは API のみです
このパッケージがエクスポートするのは プログラムから利用する API であり、React コンポーネントやフックではありません。以下のように利用します。
upload()関数を使って、進行状況を追跡しながらファイルをアップロードする- 独自のカスタム UI(ファイル入力、ドロップゾーン、進行状況バーなど)を構築する
onProgressコールバックを通じてアップロードの進行状況を追跡する
次のことはしないでください。
<FileUpload />のような構築済みコンポーネントがあると思わないでください。エクスポートされていませんuseFileUploadのような React フックをインポートしようとしないでください。エクスポートされていません- ドロップゾーンコンポーネントを探さないでください。エクスポートされていません
ソースコードにはデモ用の参考コンポーネントが含まれていますが、インポートして利用することはできません。これらを例として、独自の UI を構築してください。
1. パッケージをインストールする
npm install @salesforce/ui-bundle-template-feature-react-file-upload
依存関係は自動的にインストールされます。
@salesforce/ui-bundle(API クライアント)@salesforce/sdk-data(データ SDK)
2. 3 つのアップロードパターンを理解する
パターン A:基本的なアップロード(レコードへの関連付けなし)
ファイルを Salesforce にアップロードし、各ファイルの contentBodyId を取得します。ContentVersion レコードは作成されません。
使用する場面:
- 先にファイルをアップロードし、後からレコードを作成または関連付ける場合
- レコードがまだ存在しない状態で複数ステップのフォームを作成する場合
- レコードへの関連付けを後回しにする場合
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)
パターン B:レコードへの即時関連付けを伴うアップロード
ファイルをアップロードし、ContentVersion レコードを作成して、既存の Salesforce レコードにすぐに関連付けます。
使用する場面:
- レコード(Account、Opportunity、Case など)がすでに存在する場合
- ファイルをすぐにレコードに添付したい場合
- アップロードと添付を直接行う場合
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)
パターン C:レコードへの関連付けを後から行う(レコード作成フロー)
レコードを指定せずにファイルをアップロードし、レコードの作成後に関連付けます。
使用する場面:
- 「添付ファイル付きでレコードを作成する」フォームを構築する場合
- フォームの送信までレコードが存在しない場合
- 最終的なレコード ID がわかる前にファイルをアップロードする必要がある場合
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. カスタム UI を構築する
このパッケージが提供するのはバックエンドです。フロントエンドはご自身で構築します。以下に最小限の例を示します。
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. アップロードの進行状況を追跡する
onProgress コールバックは、各ファイルの処理段階に応じて複数回呼び出されます。
| ステータス | タイミング | 進行状況の値 |
|---|---|---|
"pending" | ファイルがアップロード待ちになったとき | 0 |
"uploading" | アップロード中(XHR) | 0-100(パーセント) |
"processing" | ContentVersion の作成中(recordId が指定された場合) | 0 |
"success" | アップロード完了時 | 100 |
"error" | アップロード失敗時 | 0 |
必ず視覚的なフィードバックを表示してください。
- ファイル名を表示する
- 現在のステータスを表示する
- ステータスが
"uploading"の場合は進行状況バーを表示する - ステータスが
"error"の場合はエラーメッセージを表示する
5. アップロードをキャンセルする(任意)
AbortController を使って、ユーザーがアップロードをキャンセルできるようにします。
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. 現在のユーザーに関連付ける(特別なケース)
ユーザー自身のプロフィールまたは個人ライブラリにファイルをアップロードする場合:
import {
upload,
getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
const userId = await getCurrentUserId();
await upload({ files, recordId: userId });
API リファレンス
upload(options)
進行状況を追跡しながら一連の処理を完了する、メインのアップロード API です。
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
}
戻り値: Promise<FileUploadResult[]>
createContentVersion(file, contentBodyId, recordId)
以前にアップロードしたファイルから、ContentVersion レコードを手動で作成します。
async function createContentVersion(
file: File,
contentBodyId: string,
recordId: string,
): Promise<string | undefined>;
パラメーター:
file— File オブジェクト(名前などのメタデータに使用)contentBodyId— 以前のアップロードで取得した ContentBody IDrecordId— FirstPublishLocationId に使用するレコード ID
戻り値: 成功した場合は ContentVersion ID
getCurrentUserId()
現在のユーザーの Salesforce ID を取得します。
async function getCurrentUserId(): Promise<string>;
戻り値: 現在のユーザー ID
一般的な UI パターン
ボタン付きファイル入力
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />
ドラッグ&ドロップ領域
ネイティブイベントを使って、独自のドロップゾーンを作成します。
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>
);
}
進捗バー
{
progress.status === "uploading" && (
<div style={{ width: "100%", background: "#eee" }}>
<div
style={{
width: `${progress.progress}%`,
background: "#0176d3",
height: "8px",
}}
/>
</div>
);
}
エージェント向け判断ツリー
ユーザーがファイルアップロード機能を求めている場合:
-
レコードのコンテキストについて確認します:
- 「アップロードしたファイルを特定のレコードにリンクしますか。それとも、まずアップロードして後からリンクしますか?」
-
回答に応じて:
- 既存のレコードにリンクする →
recordIdを指定してパターン B を使用します - 先にアップロードして後からリンクする → パターン A(
recordIdなし)を使用し、その後、リンクにはパターン C を使用します - 現在のユーザーにリンクする →
getCurrentUserId()を使用してパターン B を使用します
- 既存のレコードにリンクする →
-
UI を構築します:
- ファイル入力またはドロップゾーンを作成します(パッケージには含まれていません)
- 各ファイルの進捗表示(ステータスと進捗バー)を追加します
- UI でエラーを処理します
-
実装をテストします:
- 進捗コールバックが正しく呼び出されることを確認します
contentBodyIdが返されることを確認しますrecordIdを指定した場合は、contentVersionIdが返されることを確認します
リファレンス実装
パッケージには、src/features/fileupload/ にリファレンス実装が含まれています。
FileUpload.tsx— ドロップゾーンとダイアログを備えた完全なコンポーネントFileUploadDialog.tsx— 進捗追跡ダイアログFileUploadDropZone.tsx— ドラッグ&ドロップ領域useFileUpload.ts— 状態管理用の React フック
これらはエクスポートされていませんが、例として参照できます。独自の UI を構築するパターンを理解するには、ソースファイルをお読みください。
トラブルシューティング
アップロードが CORS エラーで失敗する場合:
- UI バンドルが Salesforce に正しくデプロイされているか、
localhostで実行されていることを確認します - 組織の CORS 設定でオリジンが許可されているか確認します
進捗が更新されない場合:
onProgressコールバックが指定されていることを確認します- コールバック関数が React の状態を正しく更新しているか確認します
ContentVersion が作成されない場合:
upload()関数にrecordIdが指定されていることを確認します- レコード ID が有効で、組織内に存在することを確認します
- ユーザーに ContentVersion レコードを作成する権限があることを確認します
ファイルはアップロードされるが、レコードに表示されない場合:
recordIdが正しいことを確認します- ContentVersion が作成されていることを確認します(結果に
contentVersionIdがあるか確認します) - ユーザーがそのレコード上のファイルを表示するアクセス権を持っていることを確認します
次のことはしないでください
- ❌ XHR/fetch によるアップロード処理を一から構築しないでください —
upload()API を使用します - ❌
<FileUpload />コンポーネントをインポートしようとしないでください — エクスポートされていません - ❌
useFileUploadフックをインポートしようとしないでください — エクスポートされていません - ❌ この機能がある場合に、サードパーティ製のファイルアップロードライブラリを使用しないでください
- ❌ 進捗追跡を省略しないでください — 常にユーザーに進捗を伝えます
- ❌ エラーを無視しないでください — 必ずエラーメッセージを処理して表示します