LWCとApexでファイルをレコードへ添付する実装
画面でファイルを選ぶとBase64に変換してApexへ渡し、ContentVersionとして保存してからレコードへ紐づけます。標準コンポーネントとの使い分けとサイズの上限を説明します。
ファイル添付には2つのやり方がある
Salesforceのレコードにファイルを添付するだけなら、lightning-file-uploadを画面に置くのが最短です。ファイルの読み込みからレコードへの紐づけまでを内部で完結してくれます。
一方で、アップロード前に内容を検証したい、他のデータと一緒にApexへ渡して1つのトランザクションで処理したい、というときは、ファイルの読み込みから保存までを自前で書きます。この記事では後者の、LWCとApexを組み合わせて自前で実装する方法を扱います。
画面にファイル選択とアップロードボタンを置く
lightning-inputのtype="file"でファイル選択欄を作ります。accept属性で選べる拡張子を絞れます。
<template>
<lightning-card title="ファイルアップロード Lightning Web コンポーネント" icon-name="custom:custom14">
<div class="slds-m-around_medium">
<lightning-input type="file"
accept=".xlsx, .png, .docx, .pdf"
label="添付ファイル" onchange={openfileUpload}></lightning-input>
</div>
<template if:true={fileData}>
<p>{fileData.filename}</p>
</template>
<lightning-button variant="brand" label="アップロード" title="Submit" onclick={handleClick} class="slds-m-left_x-small"></lightning-button>
</lightning-card>
</template>ファイルを1つ選び、選択後にファイル名を表示してからアップロードボタンで送信します。読み込みだけでは何も送信しません。
acceptはファイル選択ダイアログの表示を絞るだけです。選んだファイルの中身の種類までは制限しません。拡張子を偽装したファイルが渡ってくる前提でApex側も書きます。
選んだファイルをBase64に変換してApexへ渡す
FileReaderでファイルを読み込み、Base64文字列にしてから@AuraEnabledのApexメソッドへ渡します。
import { LightningElement, api } from 'lwc'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; import uploadFile from '@salesforce/apex/FileUploadSample.uploadFile' export default class FileUploadLwcSample extends LightningElement { @api recordId; fileData openfileUpload(event) { const file = event.target.files[0] var reader = new FileReader() reader.onload = () => { var base64 = reader.result.split(',')[1] this.fileData = { 'filename': file.name, 'base64': base64, 'recordId': this.recordId } } reader.readAsDataURL(file) } handleClick(){ const {base64, filename, recordId} = this.fileData uploadFile({ base64, filename, recordId }).then(result=>{ this.fileData = null let title = `${filename} アップロード成功しました` this.toast(title) }) } toast(title){ const toastEvent = new ShowToastEvent({ title, variant:"success" }) this.dispatchEvent(toastEvent) } }
選んだファイルをBase64化してから、recordIdと一緒にApexのuploadFileへ渡します。ファイル1件だけを想定した処理です。
FileReaderは標準のブラウザAPIで、Lightning Web Securityによる制限は受けません。ファイルの読み込みそのものはここで完結し、Apexへは変換済みの文字列だけを渡します。
メタデータでAPIバージョンを指定する
コンポーネントのjs-meta.xmlにAPIバージョンを書きます。当時の記事では56.0でしたが、2026年9月時点の最新であるSummer '26 = 67.0へ上げます。
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>67.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__HomePage</target> </targets> </LightningComponentBundle>
コンポーネントをホームページに配置できるように公開設定を行います。apiVersionは56.0から67.0へ上げています。
Apexでレコードに保存する
受け取ったBase64をContentVersionとして保存し、ContentDocumentLinkでレコードに紐づけます。添付ファイルの保存にAttachmentは使いません。
public with sharing class FileUploadSample { @AuraEnabled public static String uploadFile(String base64, String filename, String recordId) { ContentVersion cv = createContentVersion(base64, filename); ContentDocumentLink cdl = createContentLink(cv.Id, recordId); if (cv == null || cdl == null) { return null; } return cdl.Id; } private static ContentVersion createContentVersion(String base64, String filename) { ContentVersion cv = new ContentVersion(); cv.VersionData = EncodingUtil.base64Decode(base64); cv.Title = filename; cv.PathOnClient = filename; try { insert cv; return cv; } catch(DMLException e) { System.debug(e); return null; } } private static ContentDocumentLink createContentLink(String contentVersionId, String recordId) { if (contentVersionId == null || recordId == null) { return null; } ContentDocumentLink cdl = new ContentDocumentLink(); cdl.ContentDocumentId = [ SELECT ContentDocumentId FROM ContentVersion WHERE Id =: contentVersionId ].ContentDocumentId; cdl.LinkedEntityId = recordId; cdl.ShareType = 'V'; try { insert cdl; return cdl; } catch(DMLException e) { System.debug(e); return null; } } }
Base64をContentVersionとして保存し、ContentDocumentLinkでrecordIdへ紐づけます。保存に失敗した場合はnullを返すだけで、呼び出し元は理由を知れません。
ContentVersionを1件保存すると、Salesforceが自動的にContentDocumentを1件作ります。ContentDocumentLinkはそのContentDocumentと、取引先やケースなどのレコードを結びつけるためのオブジェクトです。ShareTypeを'V'にすると、ファイルへの閲覧権限だけを与えます(レコードの権限を引き継ぐ場合は'I'です)。
⚠️ このコードはDMLExceptionをcatchした後、System.debugに出すだけで呼び出し元へは何も伝えません。失敗すると画面には成功トーストも失敗トーストも出ず、ただ何も起きなかったように見えます。実運用ではAuraHandledExceptionにして画面へエラーを返してください。
<?xml version="1.0" encoding="UTF-8"?> <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>67.0</apiVersion> <status>Active</status> </ApexClass>
Apexクラスのメタデータです。apiVersionは56.0から67.0へ上げています。
ホームページにコンポーネントを配置する
ホームページを編集モードで開く
「ホーム」タブを開き、右上の歯車アイコンから「ページを編集」を選びます。Lightning App Builderが開きます。
コンポーネントをドラッグする
左側のコンポーネント一覧から、作成したLWCを探してキャンバスへドラッグします。
保存して有効化する
「保存」を押した後、「有効化」からこのページを表示するユーザーやアプリを選びます。
動作確認
コンポーネントの「ファイルアップロード」欄から、.xlsx・.png・.docx・.pdfのいずれかのファイルを選びます。ファイル名が表示されたら「アップロード」ボタンを押します。成功トーストが表示されたら、対象レコードの「ファイル」関連リストにアップロードしたファイルが表示されていることを確認します。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
| 大きいファイルを選ぶ | Base64化で3割ほど膨らんだ文字列を丸ごとApexへ渡すため、同期Apexのヒープ上限6MBに当たって例外になります |
recordIdを渡し忘れる | ファイルは誰のレコードにも紐づかない、アップロードしたユーザーの個人ファイルのままになります |
DMLExceptionを握りつぶす | 保存に失敗しても画面には何も表示されず、原因が追えません |
| 複数ファイルをまとめて送りたい | この実装はmultiple属性を付けていないため1件しか選べません。複数件ならlightning-file-uploadの方が向いています |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、
lightning-file-uploadの仕様、ContentVersion・ContentDocumentLinkの役割、Apexのヒープサイズ上限を確認しています
まとめ
- 単純にファイルをレコードへ添付するだけなら
lightning-file-uploadが最短です。アップロード前にApex側で検証や加工が要る場合に、自前実装を検討します - 添付ファイルの保存には
ContentVersionとContentDocumentLinkを使います。Attachmentは使いません - Base64変換でデータ量が3割ほど増えるため、大きいファイルは同期Apexのヒープ上限6MBに当たります
js-meta.xmlと.cls-meta.xmlのapiVersionは、当時の56.0からSummer '26の67.0へ上げています- 例外を
System.debugだけで握りつぶすと、失敗が画面に伝わりません
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。