LWC

LWCとApexでファイルをレコードへ添付する実装

画面でファイルを選ぶとBase64に変換してApexへ渡し、ContentVersionとして保存してからレコードへ紐づけます。標準コンポーネントとの使い分けとサイズの上限を説明します。

2023.05.08

ファイル添付には2つのやり方がある

Salesforceのレコードにファイルを添付するだけなら、lightning-file-uploadを画面に置くのが最短です。ファイルの読み込みからレコードへの紐づけまでを内部で完結してくれます。

一方で、アップロード前に内容を検証したい、他のデータと一緒にApexへ渡して1つのトランザクションで処理したい、というときは、ファイルの読み込みから保存までを自前で書きます。この記事では後者の、LWCとApexを組み合わせて自前で実装する方法を扱います。

構成図。Lightning Web コンポーネントの枠の中に、Apexクラスの枠が入っている
この記事で作るものの関係です。画面側の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へは変換済みの文字列だけを渡します。

Base64にすると、元のファイルよりデータ量が3割ほど増えます。10MBの画像なら13MB前後の文字列になり、それを丸ごと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へ上げています。

ホームページにコンポーネントを配置する

  1. ホームページを編集モードで開く

    「ホーム」タブを開き、右上の歯車アイコンから「ページを編集」を選びます。Lightning App Builderが開きます。

  2. コンポーネントをドラッグする

    左側のコンポーネント一覧から、作成したLWCを探してキャンバスへドラッグします。

  3. 保存して有効化する

    「保存」を押した後、「有効化」からこのページを表示するユーザーやアプリを選びます。

ホーム画面。右側に置いたファイルアップロードのコンポーネントが赤枠で示されている
ホームへ配置したコンポーネントです。赤枠の中でファイルを選び、アップロードボタンを押します。

動作確認

コンポーネントの「ファイルアップロード」欄から、.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との連携まで承ります。状況を伺ったうえで、進め方をご提案します。