請求書は、どのプロダクトもいずれ生成することになる書類で、しかも作るのがいちばん面白くない書類です。Dompdf や TCPDF では、レイアウトを HTML と CSS で表現することになり、それが半分しかうまくいきません。FPDF は座標を指定して文字を置く方式です。表が 1 つあるだけのページのために、ヘッドレス Chrome のワーカーを用意するのは、インフラとして大げさです。
難しいのはレイアウトではありません。請求書の見た目は毎回同じです。変わるのはデータです。宛先、明細、合計、税率です。
そこで、ここで紹介する方法は次のとおりです。請求書を 1 度デザインしてテンプレートとして保存し、請求データを JSON で PDF 生成 API に送ります。API は、完成した PDF を返します。
必要なもの
- ロゴ、住所のブロック、明細の表、合計のブロック、フッターを入れたテンプレート。エディターで作り、各レイヤーに名前を付けます。レイヤーを指定するときに、その名前を使うからです。
- ダッシュボード → API キー で作る API キー。サーバー側に保管します。
tpl_kd93mzのような形のテンプレート ID。
呼び出し
エンドポイントは createPdf です。データを渡すと、PDF のバイト列が返ります。
<?php
$payload = [
'apiKey' => getenv('BANNERIFY_API_KEY'),
'templateId' => 'tpl_invoice',
'modifications' => [
['name' => 'invoice-number', 'text' => 'INV-2026-0042'],
['name' => 'invoice-date', 'text' => '8 October 2026'],
['name' => 'customer-name', 'text' => 'Acme Studio'],
['name' => 'customer-address', 'text' => "12 Rue de Rivoli\n75001 Paris"],
[
'name' => 'line-items',
'columns' => [
['key' => 'item', 'header' => 'Item'],
['key' => 'qty', 'header' => 'Qty', 'align' => 'right'],
['key' => 'amount', 'header' => 'Amount', 'align' => 'right'],
],
'rows' => [
['item' => 'Automate plan, October', 'qty' => 1, 'amount' => '$29.00'],
['item' => 'Extra renders', 'qty' => 5, 'amount' => '$15.00'],
],
],
['name' => 'subtotal', 'text' => '$44.00'],
['name' => 'tax', 'text' => '$8.80'],
['name' => 'total', 'text' => '$52.80'],
['name' => 'pay-qr', 'qrcode' => 'https://pay.example.com/inv/2026-0042'],
],
];
$ch = curl_init('https://api.bannerify.co/v1/templates/createPdf');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 30,
]);
$pdf = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($pdf === false || $status !== 200) {
throw new RuntimeException("Invoice render failed with status {$status}");
}
file_put_contents("/invoices/INV-2026-0042.pdf", $pdf);
表のレイヤーが、ここの肝です。columns と rows を送ると、エディターで設定したスタイル(ストライプ、行の高さ、配置、ヘッダーのスタイル)が、届いたデータにそのまま適用されます。明細を 1 行足しても、レイアウトには触れません。表のレイヤーは画像でも使えるので、価格表やレポートにも役立ちます。表生成 API を見てください。
データの出どころ
上の形はわざと単純にしてあります。それが狙いで、データベースの行にそのまま対応するからです。
$lineItems = [];
foreach ($order['items'] as $item) {
$lineItems[] = [
'item' => $item['description'],
'qty' => $item['quantity'],
'amount' => '$' . number_format($item['subtotal'], 2),
];
}
$modifications[] = ['name' => 'line-items', 'rows' => $lineItems];
金額と日付は、送る前に自分の側で整えてください。テンプレートは通貨やロケールを推測しません。その処理は、おそらくお使いの請求コードにすでにあるはずです。
ストリーミングか、保存か
ファイルの渡し方は 2 通りあり、残しておきたいかどうかで選びます。
ブラウザにストリーミングする。 レスポンスは PDF そのものなので、適切なヘッダーを付けてそのまま流せます。
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="INV-2026-0042.pdf"');
echo $pdf;
保存して URL を受け取る。 createStoredPdf は、描画した結果をアップロードして、メールに載せられるリンクを返します。
{ "url": "https://cdn.bannerify.co/invoices/..." }
請求書をメールで送るときに、ファイルの保管、期限付きリンク、S3 の権限を自分で管理したくないなら、この方法を使います。
押さえておきたいこと
- レイヤー名は正確に。
line-itemsとline_itemsは別のレイヤーです。一致しないと、請求書のその部分はテンプレートの値のままになります。 - 実際にいちばん長い請求書でテストする。 どの請求システムにも、いずれ明細が 40 行ある顧客が現れます。その書類は、問題が起きる前に確認しておきましょう。
- 生成を冪等にする。 請求書番号をファイル名にして、ファイルがすでにあれば処理を飛ばします。同じ請求書を描画し直しても、何も起きないようにします。クォータをもう 1 回消費してはいけません。
- API キーはサーバー側に置く。 請求書の生成は、ブラウザではなく、請求のコードの中で行います。
PDF ライブラリではだめなのか
ライブラリでも、もちろん作れます。多くのチームがそうしています。違いは、レイアウトがどこにあるかです。ライブラリなら、レイアウトは PHP の中にあります。デザイナーは触れず、ブランドを変えるたびに、デプロイとテストが必要です。テンプレートなら、レイアウトはエディターの中にあり、PHP はデータを運ぶだけです。
試行錯誤も速くなります。テンプレートを開き、合計のブロックを 2 センチ上に動かして、保存する。プルリクエストではなく、2 分で終わる変更です。
次のステップ
無料プランには、月 100 回の生成、テンプレート 1 つ、画像やファイルの保存容量 1GB が含まれます。これだけあれば、この仕組みを組み込んで、実際の請求書でテストできます。Automate は月 $29 から、3,000 回の生成まで使えます。詳しくは料金をご覧ください。
エンドポイントのパラメーターは、PDF 作成のリファレンスにあります。画像、QR コード、バーコード、グラフの行を含む modifications オブジェクトの全体像は、要素を見てください。同じパターンは、レシート、配送ラベル、証明書にも使えます。そのために作られているのが、PDF 生成 API です。