.png&w=3840&q=75)
月末の帳票作業に、毎月どれくらい時間を使っているでしょうか。
注文管理画面を開いて、取引先ごとに注文を探して、金額を確認して、請求書のひな形に転記して、印刷する。1 件 5 分として 40 件なら 3 時間半。しかもこの作業は、売上が伸びるほど時間も伸びます。
この記事では、Shopify で複数注文の帳票をまとめて印刷する仕組みを自分で組む手順をステップごとに解説します。Liquid と CSS で印刷用ページを作り、注文ごとにページを区切り、税率別の内訳まで出すところまでを見ていきます。そのうえで、「ここまで自作するのは重い」と感じた場合の選択肢として、既製のアプリも紹介します。
最終的に目指すのは、次の条件を満たす印刷用ページです。
複数の注文を 1 つのページにまとめて表示し、注文ごとに改ページする。明細を表で出し、税率ごとの対象金額と消費税額を集計した区分表を付ける。ブラウザの印刷機能で A4 に収まる形で出力できる。適格請求書発行事業者登録番号を印字する。
つまり、月末に「今月の法人取引 40 件分」をまとめて開いて、そのまま印刷ボタンを押せる状態です。
前提として、Shopify のテーマで扱える範囲には限りがあります。Liquid から複数注文をまとめて読み込むことは、標準のテンプレートでは原則できません。 注文情報を Liquid で扱えるのは注文状況ページなど限られた文脈だけで、そこでも扱えるのは基本的にその 1 件です。
そのため、本格的にやるならアプリ側で Admin API を使って注文を取得し、印刷用の HTML を返す構成になります。この記事では、その構成を前提に、HTML と CSS の組み立て部分を中心に解説します。ここが帳票の品質を決める部分で、かつ流用が効く部分だからです。
まず、1 件分の帳票を関数として切り出せる形にします。複数注文をループする前に、1 件の構造を固めておくと後が楽になります。
日本の帳票でよく使われる構成は、上から順に「タイトル帯」「宛名と金額」「発行者情報」「明細表」「税率区分表」「サマリー」「備考」です。
<article class="doc" data-doc-type="invoice">
<header class="doc__band">
<h1 class="doc__title">請求書</h1>
<p class="doc__no">No. 1042</p>
</header>
<div class="doc__head">
<div class="doc__to">
<p class="doc__recipient">株式会社サンプル 御中</p>
<p class="doc__amount">
<span>ご請求金額</span>
<strong>¥143,000</strong>
</p>
</div>
<div class="doc__from">
<p class="doc__shop">UnReact ストア</p>
<p>〒810-0001 福岡県福岡市中央区天神0-0-0</p>
<p>TEL 000-0000-0000</p>
<p class="doc__reg">登録番号 T0000000000000</p>
<img class="doc__seal" src="/seal.png" alt="" />
</div>
</div>
<table class="doc__items">
<thead>
<tr>
<th>日付</th><th>品名</th><th>数量</th><th>単位</th>
<th>単価</th><th>税率</th><th>金額</th>
</tr>
</thead>
<tbody><!-- 明細行 --></tbody>
</table>
<table class="doc__tax">
<thead>
<tr><th>税率</th><th>対象金額(税抜)</th><th>消費税額</th></tr>
</thead>
<tbody><!-- 税率区分 --></tbody>
</table>
<div class="doc__summary">
<p>小計(税込)<span>¥140,000</span></p>
<p>送料(税込)<span>¥3,000</span></p>
<p class="doc__total">合計(税込)<span>¥143,000</span></p>
</div>
<section class="doc__bank">
<h2>お振込先</h2>
<p>〇〇銀行 天神支店 普通 0000000 カ)アンリアクト</p>
</section>
<footer class="doc__note">
<p>お問い合わせは support@example.com までお願いいたします。</p>
</footer>
</article>
ポイントは、帳票 1 件を <article class="doc"> という 1 つのブロックに閉じることです。この単位が、後のページ区切りの単位になります。 data-doc-type に帳票の種類を持たせておくと、CSS で種類ごとの差分(領収書には収入印紙の注記を出す、請求書には振込先を出す、など)を切り替えられます。
明細表の列を 7 列にしているのは、日本の請求書でよく求められる項目を並べた結果です。列が多いほど 1 行が窮屈になるので、A4 幅に収めるには後述の列幅配分が必要になります。
複数注文を扱うので、サーバー側で取得した注文の配列をループします。ここでは Liquid での記述例を示します。
{% comment %} orders は Admin API で取得した注文の配列 {% endcomment %}
{% for order in orders %}
<article class="doc" data-doc-type="{{ document_type }}">
<header class="doc__band">
<h1 class="doc__title">{{ document_title }}</h1>
<p class="doc__no">No. {{ order.name | remove: '#' }}</p>
</header>
<div class="doc__head">
<div class="doc__to">
<p class="doc__recipient">{{ order.customer_name }} 御中</p>
<p class="doc__amount">
<span>ご請求金額</span>
<strong>{{ order.current_total_price | money }}</strong>
</p>
</div>
{% render 'doc-issuer', settings: settings %}
</div>
<table class="doc__items">
<thead>
<tr>
<th class="c-date">日付</th>
<th class="c-name">品名</th>
<th class="c-qty">数量</th>
<th class="c-unit">単位</th>
<th class="c-price">単価</th>
<th class="c-rate">税率</th>
<th class="c-amount">金額</th>
</tr>
</thead>
<tbody>
{% for li in order.line_items %}
<tr>
<td class="c-date">{{ order.processed_at | date: '%Y/%m/%d' }}</td>
<td class="c-name">
{{ li.title }}
{% if li.variant_title %}<span class="variant">/ {{ li.variant_title }}</span>{% endif %}
</td>
<td class="c-qty">{{ li.quantity }}</td>
<td class="c-unit">個</td>
<td class="c-price">{{ li.unit_price_ex_tax | money }}</td>
<td class="c-rate">{{ li.tax_rate_percent }}%</td>
<td class="c-amount">{{ li.line_price_ex_tax | money }}</td>
</tr>
{% endfor %}
{% if order.shipping_price > 0 %}
<tr>
<td class="c-date">{{ order.processed_at | date: '%Y/%m/%d' }}</td>
<td class="c-name">送料</td>
<td class="c-qty">1</td>
<td class="c-unit">式</td>
<td class="c-price">{{ order.shipping_price_ex_tax | money }}</td>
<td class="c-rate">{{ order.shipping_tax_rate_percent }}%</td>
<td class="c-amount">{{ order.shipping_price_ex_tax | money }}</td>
</tr>
{% endif %}
</tbody>
</table>
</article>
{% endfor %}
ここで実務上の注意が 2 つあります。
1 つ目は、送料を明細に含めるかどうかです。 送料を明細の外に出すと、明細の合計と請求金額が一致しなくなり、購入者側の経理から問い合わせが来ます。上の例では明細の末尾に「送料」行として入れています。送料が 0 円の注文では行ごと出さない、という分岐も入れています。
2 つ目は、バリエーション名の扱いです。 「Tシャツ / M / ブラック」のように商品名とバリエーション名を並べると、品名の列が長くなって折り返します。列幅の配分次第では 3 行に折れて表全体が崩れるため、品名の列は他より広く取っておく必要があります。
ここが一括印刷の肝です。CSS のページ区切りプロパティを使います。
@page {
size: A4 portrait;
margin: 12mm 12mm 14mm 12mm;
}
@media print {
.doc {
/* 各帳票の後で改ページする(最後の 1 件を除く) */
page-break-after: always;
break-after: page;
}
.doc:last-of-type {
page-break-after: auto;
break-after: auto;
}
/* 明細行が改ページ位置で割れないようにする */
.doc__items tr,
.doc__tax tr {
break-inside: avoid;
page-break-inside: avoid;
}
/* サマリーと振込先は分断しない */
.doc__summary,
.doc__bank {
break-inside: avoid;
}
}
page-break-after: always を .doc に付けると、帳票 1 件ごとに必ず改ページされます。:last-of-type で最後の 1 件だけ解除しておかないと、末尾に空白のページが 1 枚余分に出ます。 印刷して初めて気づくタイプの不具合なので、最初から入れておきます。
break-after / break-inside は新しい仕様のプロパティ、page-break-after / page-break-inside は旧仕様のプロパティです。両方書いておくのが安全です。 ブラウザによってどちらを見るかが違います。
明細行への break-inside: avoid も重要です。これがないと、改ページ位置に重なった 1 行が上下に割れて、上のページに文字の上半分、下のページに下半分、という読めない出力になります。
適格請求書として出すなら、税率ごとの対象金額と消費税額をまとめた区分表が必要です。ここは Liquid のループとハッシュ操作で組みます。
{% comment %} 税率ごとに対象金額(税抜)と消費税額を積み上げる {% endcomment %}
{% assign rate_keys = '' %}
{% assign base_by_rate = '' %}
{% assign tax_by_rate = '' %}
{% for li in order.line_items %}
{% assign key = li.tax_rate_percent | append: '' %}
{% unless rate_keys contains key %}
{% assign rate_keys = rate_keys | append: key | append: ',' %}
{% endunless %}
{% endfor %}
<table class="doc__tax">
<thead>
<tr><th>税率</th><th>対象金額(税抜)</th><th>消費税額</th></tr>
</thead>
<tbody>
{% assign keys = rate_keys | split: ',' %}
{% for key in keys %}
{% if key == blank %}{% continue %}{% endif %}
{% assign base_sum = 0 %}
{% assign tax_sum = 0 %}
{% for li in order.line_items %}
{% if li.tax_rate_percent == key %}
{% assign base_sum = base_sum | plus: li.line_price_ex_tax %}
{% assign tax_sum = tax_sum | plus: li.line_tax %}
{% endif %}
{% endfor %}
{% if order.shipping_tax_rate_percent == key %}
{% assign base_sum = base_sum | plus: order.shipping_price_ex_tax %}
{% assign tax_sum = tax_sum | plus: order.shipping_tax %}
{% endif %}
<tr>
<td>{{ key }}%{% if key == '8' %}(軽減税率){% endif %}</td>
<td>{{ base_sum | money }}</td>
<td>{{ tax_sum | money }}</td>
</tr>
{% endfor %}
</tbody>
</table>
この実装で大事なのは、税率をコードに書かないことです。「8% と 10% の 2 行を出す」と決め打ちすると、非課税の商品や海外向けの 0% 注文が入ったときに集計から漏れます。上のコードは注文の明細に実際に乗っている税率だけを拾って行を作るので、税率の種類が増えても壊れません。
送料の税率を別に扱っているのは、送料の課税区分が商品と異なる場合があるためです。ここを商品と同じ税率で計算すると、送料が非課税の注文で金額が合わなくなります。
もう 1 つの落とし穴が、注文全体にかかった割引の扱いです。クーポンなどで注文全体から一定額を引いた場合、その割引額は各明細の単価には反映されていません。何もしないと「明細の合計 ≠ 税率区分表の対象金額 ≠ 注文合計」という三者不一致が起こります。これを防ぐには、割引額を各明細に金額の比率で按分してから集計する必要があります。
{% comment %} 注文レベル割引を明細に構成比で按分する {% endcomment %}
{% assign subtotal_ex_tax = 0 %}
{% for li in order.line_items %}
{% assign subtotal_ex_tax = subtotal_ex_tax | plus: li.line_price_ex_tax %}
{% endfor %}
{% for li in order.line_items %}
{% assign share = li.line_price_ex_tax | times: 1.0 | divided_by: subtotal_ex_tax %}
{% assign allocated = order.order_level_discount | times: share %}
{% assign li_net = li.line_price_ex_tax | minus: allocated %}
{% comment %} li_net を税率区分表の対象金額に積む {% endcomment %}
{% endfor %}
按分は端数処理でずれることがあるので、最後の 1 行で差額を吸収する処理を足しておくと、合計がぴったり合います。ここは地味ですが、経理から指摘が来るかどうかの分かれ目です。
7 列の明細表を A4 幅(余白を引いて約 186mm)に収めます。等幅で割ると品名の列が狭すぎて折り返しだらけになるため、明示的に配分します。
@media print {
.doc__items { width: 100%; table-layout: fixed; font-size: 9.5pt; }
.doc__items .c-date { width: 18mm; white-space: nowrap; }
.doc__items .c-name { width: 70mm; }
.doc__items .c-qty { width: 14mm; text-align: right; }
.doc__items .c-unit { width: 12mm; }
.doc__items .c-price { width: 24mm; text-align: right; }
.doc__items .c-rate { width: 14mm; text-align: right; }
.doc__items .c-amount { width: 28mm; text-align: right; }
/* 見出しが語の途中で折れないようにする */
.doc__items thead th { word-break: keep-all; overflow-wrap: normal; }
/* 品名だけは折り返しを許可する */
.doc__items .c-name { word-break: break-all; }
}
table-layout: fixed を指定しないと、ブラウザが中身の長さに応じて勝手に列幅を決めます。同じテンプレートなのに注文によって列幅が変わるのは、これが原因です。固定にしたうえで、各列に mm 単位で幅を与えます。
日付の列に white-space: nowrap を入れているのは、2026/09/01 が 2026/ と 09/01 に折れるのを防ぐためです。逆に品名は折り返さないと収まらないので、こちらだけ break-all を許可します。
見出しの word-break: keep-all も効きます。これがないと「対象金額」が「対象金」「額」のように語の途中で折れます。
動く帳票はできます。ただし運用に載せると、次の作業が継続的に発生します。
注文の取得部分です。 上の HTML/CSS はあくまで表示側で、複数注文を取得する部分は Admin API を使ったサーバー実装が必要です。認証、権限、他店舗のデータを混ぜない仕組み、選択した注文 ID を安全に受け渡す仕組み。ここは帳票の見た目とは別のスキルセットになります。
返金と注文編集の反映です。 一部返金があった注文で「返金前の金額」を出してしまうと、経理で必ず指摘されます。返金額を二重に引いてしまう実装ミスも起こりがちです。注文編集で明細が変わった場合の追随も必要です。
帳票の種類ごとの差分です。 領収書には収入印紙の注記、請求書には振込先と「お支払い済み」表示、納品書には納品場所、見積書には有効期限。それぞれ固有の欄があり、種類が増えるほどテンプレートの分岐が増えます。
多言語です。 上のテンプレートは日本語直書きなので、英語の取引先には使えません。帳票特有の言い回し(御中、但し書き、収入印紙の注記)を全言語分そろえる作業が発生します。
このあたりを毎月保守し続けるコストと、既製アプリの月額を比べる、というのが現実的な判断になります。日本語のみ・帳票 1 種類・返金なしという条件なら自作で十分に回りますが、条件が増えるほど自作の維持コストは跳ね上がります。

Shopify の注文管理画面から注文を選び、領収書・請求書・納品書・見積書をまとめて印刷・PDF 保存できるアプリ。
特徴・機能
この記事で自作した機能が、ほぼそのまま設定項目として用意されています。ステップ3 のページ区切りは「注文ごとにページ区切りする」のチェックボックス 1 つ、ステップ4 の税率集計と割引の按分は最初から実装済み、ステップ5 の列幅配分も 7 列が A4 に収まるよう調整済みです。見出しが語の途中で折れない処理も入っています。
導入して最初にやることは 2 つだけです。設定の「店舗情報」タブで帳票に載せる発行者情報を確認し、「インボイス」タブで登録番号を入れて内訳表示をオンにする。店舗情報は Shopify に登録済みのストア情報から自動で入っているので、正式表記に直したいところだけ書き換えれば済みます。この 2 つが終われば、注文一覧から印刷するだけで帳票が出ます。
運用のコツをいくつか挙げておきます。まず、出す帳票が請求書だけと決まっているストアなら、基本設定で「デフォルト帳票種別」を請求書にして「印刷時に帳票種別を選択させる」をオフにするのがおすすめです。印刷モーダルから選択欄が消えるので、スタッフが誤った種類を選ぶ事故を構造的に防げます。取引先ごとに書類を出し分けるストアは、オンのままにしておきます。
次に、見積書を扱うストアは相対日付モードを活用してください。納期と有効期限を「今月末」「翌月末」「再来月末」「3 か月後の月末」から選んでおくと、発行日を基準に月末が自動計算されます。毎回日付を打ち直す必要がなくなります。案件ごとに日付が変わる場合は「なし」にしておき、印刷モーダルで都度入力する運用に切り替えられます。優先順位は「モーダルの入力 > 設定の相対日付 > 空欄」です。
帯の色を変えるときは注意が必要です。淡い色を背景に使う場合は、帯の文字色も濃い色に変えないと文字が読めなくなります。見た目タブは 2 列レイアウトで右側にプレビューが常に追従表示されるので、変更したその場で確認できます。クリックすると拡大表示されるため、細部まで見たいときはそちらを使います。白黒印刷が前提のストアは、アクセントカラーを濃いグレー(#242424)にしておくとモノクロ出力でも帯がつぶれません。
50 件を超える注文を扱いたい場合は、一覧の絞り込み条件を分けて複数回に分けて実行します。日付・タグ・配送状況などで区切ると、「どこまで印刷したか」を後から追いやすくなります。キャンセル済みの注文が混ざっていた場合は、モーダルに「◯件は発行できません」という警告が出て、発行できる注文だけが対象になります。黙って一部だけ処理されることはありません。
価格設定

B2B 機能と Shopify POS 連携を備えた、PDF 請求書の生成アプリ。
特徴・機能
実店舗を併設しているストア向けの構成です。店頭での販売分もオンライン注文と同じ書式で帳票を出せます。シンプル一括注文帳票印刷がオンラインの注文管理画面からの印刷に絞っているのに対し、Avada は POS 売上まで対象に含めます。 実店舗の売上も同じ帳票で処理したいなら Avada、オンライン注文を日本語の管理画面で完結させたいならシンプル一括注文帳票印刷、という分かれ方になります。管理画面は英語のみです。
価格設定

PDF 請求書の生成と自動送信、顧客ポータルからのダウンロードに対応するアプリ。
特徴・機能
購入者が自分で PDF をダウンロードできる導線を持つのが特徴です。シンプル一括注文帳票印刷は管理者が注文を選んで印刷する方式なので、購入者向けの導線という点で役割が異なります。 購入者に取りに来てもらう運用を作りたい場合は Vify、管理者側でまとめて処理したい場合はシンプル一括注文帳票印刷、と使い分けられます。言語ごとにテンプレートを持てるため、多言語ストアでの書式管理にも向きます。
価格設定

一括生成と ZIP ダウンロード、輸出用インボイスに対応した PDF 請求書アプリ。
特徴・機能
一括処理に振り切った構成です。月末に全注文分の PDF をまとめて落として会計ソフトに渡す運用と噛み合います。シンプル一括注文帳票印刷はサーバーで PDF ファイルを作らない設計なので、「全注文の PDF をアーカイブしたい」という要件がある場合は Fordeer のような生成型が必要になります。 逆に、発行履歴を持ちたくないストアには前者が向きます。海外へ実際に商品を発送していてコマーシャルインボイスが必要な場合も、Fordeer が候補になります。
価格設定

PDF 請求書を注文確認メールに自動添付し、一括印刷にも対応する定番アプリ。
特徴・機能
レビュー件数が多く、定番として選ばれているアプリです。注文が入った時点で PDF をメールに添付して送るため、購入者から問い合わせが来る前に書類が届きます。シンプル一括注文帳票印刷が「必要な注文を選んでその場で出す」方式であるのに対し、Order Printer Pro は「全注文へ機械的に配る」方式です。 定期的に全件配るならメール添付型、月末に選んでまとめて印刷するなら選択型、という使い分けになります。管理画面は英語のみなので、日本語運用が必須のチームは事前確認が必要です。
価格設定
Shopify で注文の帳票を一括印刷する仕組みは、HTML と CSS の組み立てだけなら自作できます。この記事で見たとおり、帳票の骨格を <article> 単位で閉じ、page-break-after で注文ごとに区切り、table-layout: fixed で列幅を配分すれば、A4 に収まる帳票が並びます。
難しくなるのは、その先です。税率を決め打ちせずに集計する、注文レベルの割引を明細に按分して三者の合計を一致させる、返金を二重に引かずに反映する、帳票の種類ごとの固有欄を出し分ける。どれも「間違っていてもエラーが出ない」タイプの実装で、経理から指摘されて初めて気づきます。
月末の帳票作業に毎月 3 時間使っているなら、その 3 時間を減らすことと、この保守を自分で持つこと、どちらのコストが高いかを比べてみてください。答えがはっきりしないうちは、既製のアプリを無料期間中に触って、自分のストアの注文で実際に金額が合うかを確かめるのが一番早い判断材料になります。