VisualforceのcontentType指定でCSVを出力する実装
検索結果を絞り込んでからダウンロードする2ページ構成で、Visualforceのcontent属性を使ったCSV出力を説明します。cacheとビューステートの注意点も扱います。
Visualforceだけで作る理由
一覧をCSVで持ち出すだけなら、データローダかレポートのCSVエクスポートで足ります。レポートのCSVエクスポートに件数の上限はありません。
それでも専用ページを作るのは、検索条件を業務ごとに固定したい、出力する項目を絞りたい、特定のプロファイルだけに使わせたい、といった事情があるときです。LWCなどの画面部品を使わず、URLを知っていれば開けるページだけで完結させたいときにも向いています。
検索とダウンロードを処理するApexクラス
CSVDownloadController.cls
public inherited sharing class CSVDownloadController { public List<Contact> contacts {get; set;} public CSVDownloadController() { this.contacts = new List<Contact>(); } public void doSearchClick() { this.contacts = [SELECT Id, LastName, FirstName, Phone, MailingState FROM Contact ORDER BY CreatedDate DESC LIMIT 4]; } public PageReference doDownloadClick() { return Page.CSVDownloadPage.setRedirect(false); } }
取引先責任者を検索し、ダウンロードページへのPageReferenceを返します。SOQLはループの外で1回だけ発行しています。
検索結果を表示するVisualforceページ
CSVSearchPage.page
<apex:page controller="CSVDownloadController" apiVersion="67.0" title="CSVSearchPage" showHeader="true" sidebar="false" id="page"> <apex:form id="form"> <apex:pageBlock id="block"> <apex:pageBlockButtons> <apex:commandButton value="CSV Search" action="{!doSearchClick}" reRender="form" /> <apex:commandButton value="CSV Download" action="{!doDownloadClick}" rendered="{!contacts.size > 0}" /> </apex:pageBlockButtons> <apex:pageBlockTable value="{!contacts}" var="item"> <apex:column headerValue="{!$ObjectType.Contact.Fields.LastName.Label}"> <apex:outputText value="{!item.LastName}" /> </apex:column> <apex:column headerValue="{!$ObjectType.Contact.Fields.FirstName.Label}"> <apex:outputText value="{!item.FirstName}" /> </apex:column> <apex:column headerValue="{!$ObjectType.Contact.Fields.Phone.Label}"> <apex:outputText value="{!item.Phone}" /> </apex:column> <apex:column headerValue="{!$ObjectType.Contact.Fields.MailingState.Label}"> <apex:outputText value="{!item.MailingState}" /> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
検索ボタンで一覧を表示し、1件以上あるときだけダウンロードボタンを出します。apiVersionを67.0で明示しています。
このページのビューステートには、コントローラのcontactsがそのまま乗ります。Visualforceのビューステートには170KBの上限があるため、項目数や件数を絞らずに使い回すと、このページ単体でも上限に近づきます。
CSVを生成してダウンロードさせるVisualforceページ
CSVDownloadPage.page
<apex:page controller="CSVDownloadController" apiVersion="67.0" cache="false" contentType="text/csv#Extract_Contact.csv" language="en-US"> "LastName","FirstName","Phone","MailingState" <apex:repeat value="{!contacts}" var="item"> "{!item.LastName}","{!item.FirstName}","{!item.Phone}","{!item.MailingState}" </apex:repeat> </apex:page>
contentTypeを「MIMEタイプ#ファイル名」の形にすると、ダウンロード時のファイル名を指定できます。
contentTypeはどのMIMEタイプでも受け付けますが、Visualforceが実際に変換してくれるのはPDFだけです。CSVのようにそれ以外の形式では、レンダリング結果のテキストをそのままそのMIMEタイプとして流すだけなので、タグや空白が混じらないよう本文を書く必要があります。cacheのデフォルトはfalseです。検索結果は都度変わるので、ここでもfalseのままにしています。
この記事では、Summer '26の最新であるAPI 67.0を両方のページに明示しています。
ページを公開する
アプリケーションランチャーやタブに登録する
「設定」の「タブ」からVisualforceページ用のカスタムタブを作成し、対象のページを割り当てます。
アクセス権限を絞る
プロファイルまたは権限セットで、このタブとページへのアクセスを必要な範囲だけに絞ります。
動作を確認する
作成したタブを開き、検索とダウンロードが想定どおり動くか確認します。
タブを開くとCSVSearchPageが表示されます。「CSV Search」を押すと、作成日の新しい順で取引先責任者が4件まで一覧に出て、「CSV Download」ボタンが現れます。押すとCSVDownloadPageへ遷移し、ブラウザがExtract_Contact.csvという名前でファイルをダウンロードします。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
ダウンロードページのcacheをtrueにする | 検索条件を変えても古いCSVが落ちてくることがあります |
| コントローラのプロパティに不要な項目を増やす | ビューステートが膨らみ、170KBの上限に達しやすくなります |
contentTypeにCSV以外の変換を期待する | Visualforceが変換してくれるのはPDFだけなので、レンダリング結果がそのままファイルの中身になります |
大量件数を出力するとき
この構成は数件から数十件の「決まった条件での取り出し」向きです。Apex同期処理のヒープサイズ上限は6MB、ビューステートの上限は170KB、SOQLが1クエリで返せる行数は最大50,000行です。数千件を超える全件エクスポートには、この仕組みを広げるのではなく、データローダかBulk APIを使ってください。
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、contentTypeの書き方、ヒープサイズとビューステートの上限を確認しています
- コードはAPIバージョン67.0で書いています
まとめ
- 条件を固定しない全件エクスポートなら、データローダかレポートのCSVエクスポートで足ります
contentTypeを「MIMEタイプ#ファイル名」の形にすると、ダウンロード時のファイル名を指定できます- Visualforceが変換してくれるのはPDFだけです。CSVはレンダリング結果がそのままファイルの中身になります
- ビューステートの上限は170KB、Apex同期処理のヒープサイズ上限は6MBです。項目数や件数を絞って使ってください
- 数千件を超える出力には向きません。データローダかBulk APIへ切り替えてください
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。