Customer Account UI Extensions開発の始め方:scaffoldから実ストアでの検証まで【実践チュートリアル】
検証環境は2026年8月、Shopify CLI 3.90、api_version 2026-04です。Customer Account UI Extensionsは変化の速い領域のため、本文ではリンク付きの公式仕様と、公式に記載のない2026年8月時点の実測を区別して書いています。
はじめに:extensionの生成から実ストアでの検証までの範囲
新しいお客様アカウントに切り替えると、マイページはShopifyがホストするUIに変わります。アプリが独自のUIをそこへ差し込む仕組みはCustomer Account UI Extensions(以下UIE)に一本化されていて、マイページに何かを足したいなら、このextensionを作ることになります。
公式チュートリアルをたどると、extensionを生成してdevelopment storeのマイページに自作のUIを出すところまでは一通り進めます。そこまで表示できた後は、実ストア(本番相当のデータを持つストア)のデータで動きを確かめたくなりますが、公式の手順はすべてdevelopment storeを前提にしているため、実ストアで検証するにはdeployベースの開発サイクルへ切り替えることになります。
切り替えた後の開発では、バンドルサイズの上限(圧縮後64KB)の把握と内訳の分析が要ります。環境まわりでは、検証ストアで公式ドキュメントに記載のないトラブルが3種起きたので、症状から切り分けて対処するところまで扱います。
必要なもの
| もの | 補足 | 根拠 |
|---|---|---|
| Shopify Partnerアカウント | アプリの作成、管理に必要 | 公式ドキュメント |
| development store |
shopify app devのプレビュー先として必要 |
公式ドキュメント、検証ストアでも確認 |
| Shopify CLI(最新版) |
npm install -g @shopify/cliなどで導入。Node.js前提 |
公式ドキュメント |
作成したUIEはアプリの一部として配布され、テーマのコードとは別の開発ワークフローで管理します。Shopify CLIを使ったアプリ開発の流れに乗るため、上の3点が要ります。shopify app devの接続先にdevelopment storeが要る理由は2章で、実ストアではdev previewを使えない事情とあわせて書きます。
独自のバックエンドは、必ずしも要りません。Shopifyが提供するデータとUIE内の処理だけで完結する構成なら、独自バックエンドを持たない選択もできます。extensionのコード自体はShopifyがホストするためで、この点は公式ドキュメントに書かれています。外部APIや秘匿情報を扱う場合にどう構成するかは、Customer Account UI Extensionsから外部APIを呼ぶ:network_access・CORS・実データでの検証手法にまとめました。
差し込み位置は、targetという単位で決まります。注文一覧、注文詳細、プロフィール、フルページなどで使うtargetが異なるので、実装前に公式のtarget一覧でどこに出すかを決めておくと、後からextensionを分割し直す事態を減らせます。
1. extensionを生成する:shopify app generate extension
既存のアプリがなければ雛形を作り、そのディレクトリへ移動してextensionを生成します。
# アプリの雛形を作成(既存アプリがあればスキップ)
shopify app init
# アプリのディレクトリで extension を生成
cd my-app
shopify app generate extension
shopify app initは既存のアプリに追加する場合には飛ばします。shopify app generate extensionを実行するとextensionの種別を聞かれるので、「Customer account UI extension」を選ぶと、TOMLの設定ファイルとPreactベースのコンポーネントファイルが生成されます。手順は公式チュートリアルのとおりで、検証ストアでも同じ生成物を確認しました。
shopify.extension.tomlの最小構成:api_version、targeting、capabilities
生成される設定ファイルはextensions/<name>/shopify.extension.tomlにあります。最初はapi_version、targeting、capabilitiesの3要素に分けて読むと、extensionがどのAPIを使い、どこへ表示され、何を許可されているかを追えます。
api_version = "2026-04"
[[extensions]]
type = "ui_extension"
name = "My customer account extension"
handle = "customer-account-ui"
[[extensions.targeting]]
target = "customer-account.order-status.block.render"
module = "./src/OrderStatusBlock.jsx"
[extensions.capabilities]
api_access = true
api_versionはextensionが使うAPIバージョンで、この記事は2026-04を前提にしています。targetingは差し込み位置(target)と、そこに描画するモジュールの組で、サンプルではcustomer-account.order-status.block.renderに./src/OrderStatusBlock.jsxを割り当てた形です。capabilitiesはプラットフォームレベルの許可で、各capabilityが何を許可するかは公式ドキュメント(capabilities)に整理されています。外部APIを直接呼ぶときに要るnetwork_accessは、Customer Account UI Extensionsから外部APIを呼ぶ:network_access・CORS・実データでの検証手法で詳しく書いています。
コンポーネントの書き方は、2025-10以降でPreactとshopify.*グローバルに変わっています。reactExtensionやuseApiを使う旧来のReact式は使いません(APIリファレンス)。検証中に検索で見つけた古いコード例を混ぜたところ、世代の違う書き方が1つのextensionに同居し、原因を追いにくいエラーになりました。
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
export default async () => {
render(<Extension />, document.body);
};
function Extension() {
const order = shopify.order?.value;
return (
<s-text>
{order ? `注文 ${order.name} を表示中です。` : 'マイページに最初のブロックが出ました。'}
</s-text>
);
}
このコンポーネントでは、shopify.order?.valueから注文名を取得し、注文が無い場合にもブロック自体は表示します。骨格は、@shopify/ui-extensions/preactを読み込み、default exportのasync関数の中でrender(<Extension />, document.body)を呼ぶ形で、<s-text>の中身だけが注文の有無で切り替わります。
shopify.*グローバルで取れる値は、targetごとに異なります。使いたいデータがそのtargetで提供されるかは、書く前にAPIリファレンスで確認します。データアクセスの詳しい挙動はCustomer Account UI Extensionsで過去注文のデータを読む・書く:signal・GraphQL・メタフィールドの使い分けのほうに置きました。
targetの組み合わせには制約があります。customer-account.page.renderなどのフルページ用targetを使うextensionには、他のtargetを同居させられません。2024年10月の公式変更以降、同居させた構成はdeploy時に構成エラーとして弾かれます。フルページを含む構成では、extensionをどう分割するかを先に決めておく必要があります。
2. 手元で動かす:shopify app devとdevelopment store
生成したextensionは、次のコマンドでdevelopment storeにつないで確認します。
shopify app dev
実行すると、接続先としてdevelopment storeの選択を求められます。起動後はCLIの案内に従ってDev Consoleを開き(公式チュートリアルではpキー)、extensionのプレビューリンクからマイページに自作のブロックが出ていることを確かめます。コードを変えると自動でリロードされるため、変更のたびにプレビュー画面で表示を確認できます。公式チュートリアルの手順どおりで、development storeでも同じ流れで表示まで進むことを2026年8月に確認しました。
機能を増やす前に確かめておく項目は4つです。
- extensionが指定したtargetに表示される
- コードの変更がプレビューへ反映される
-
shopify.*から、そのtargetで必要なデータを取得できる - 値が無いケースでもextension全体が消えない
確認の順序は、表示、データ、外部通信の順です。この順なら、設定で詰まったのか、データで詰まったのか、通信で詰まったのかを混ぜずに切り分けられます。
3. 実ストアのデータで検証する:deployベース開発への切り替え
開発が進むと、実データで確かめたい場面が出てきます。過去の注文履歴、実際のラインアイテムプロパティ、本物のメタフィールドは、development storeに入れたテストデータではキー体系、空値、キャンセル状態まで再現しきれないことが多いためです。検証ストアでも、そこで足りなくなりました。
実ストアにshopify app devを向けたときのエラー:Shop is not configured for app development
公式チュートリアルの手順は、すべてdevelopment storeを前提にしています(公式チュートリアル)。shopify app devを実ストア(Shopify Plusのテスト用ストアを含む)に向けると、次のエラーで拒否されます。
Shop is not configured for app development
このメッセージが出たときは、再ログインやextensionの作り直しに進む前に、接続先のストア種別を確認します。実ストアに向けていたなら想定内の挙動で、対処はdeployベースの開発への切り替えです。2026年8月の検証ストアでも、このエラーで止まりました。
dev previewまわりの不具合はCLIリポジトリのissueにも複数報告されていますが、そのissueを追う前に、接続先がdevelopment storeかどうかを見ておくほうが切り分けは早く済みます。
deployベースの開発サイクル:shopify app deploy --force
実ストアで検証する間は、変更のたびに次のコマンドでdeployする開発スタイルに切り替えます。
shopify app deploy --force
--forceは確認プロンプトを省くフラグで、これを付けると対話なしに回せます。コードの変更、静的チェック、deploy、実機確認の4段階を繰り返します。ホットリロードは失われますが、実データで判断できる価値がそれを上回る場面は多く、検証ストアでもCIに近い感覚で回せました。
回す前に、deployの影響範囲を確認します。deployするとアプリの新バージョンが公開され、そのアプリを入れている全ストアに影響が及びます。検証は検証専用のアプリか、検証専用のストアにだけ入れたアプリで行い、利用者のいるアプリでは繰り返さないようにします。
deploy前には静的チェックを挟みます。ラウンドトリップが長い分、ランタイムエラーの手戻りが高くつくためです。何を挟むかは、5章のesbuildの項で書きます。
反映の確認は画面で行います。deployの成功ログが出ても、マイページでextensionが動いているとは限らないので、ログと画面に出た実物は別のものとして扱います。
4. バンドルサイズを管理する:64KB上限と内訳の分析方法
UIEのバンドルには、圧縮後64KBの上限があります。公式ドキュメントに「strict 64 KB compressed size limit」と明記されていて、超過したバンドルはdeploy時に弾かれます。
内訳の分析方法も、同じドキュメントにあります。CLI 3.92.0以降でshopify app buildを実行すると、各extensionのdist/にesbuildのmetafile(.metafile.json)が出力され、これをesbuildのbundle analyzerに読み込ませると、どのファイルや依存がバンドルのサイズを押し上げているかが分かります。この記事の検証環境はCLI 3.90なので、サイズ分析には3.92以降への更新が要る、という但し書き付きです。
サイズを抑える方針は5つあります。
- UIライブラリを追加せず、Polaris Web Componentsを使う
- 日付処理などのユーティリティは、
Intl.DateTimeFormatなどの組み込みで置き換える - 大きな静的データはバンドルに埋め込まず、メタフィールド側に持たせる
- 翻訳はローカライズAPIを使い、i18nライブラリを同梱しない
- 依存を追加した直後に、サイズの差分を確認する
メタフィールドをUIEから読むには、宣言とaccess設定が必要です。その手順と実際のデータアクセスはCustomer Account UI Extensionsで過去注文のデータを読む・書く:signal・GraphQL・メタフィールドの使い分けで詳しく書いています。
依存を増やさない構成では64KBを下回っていても、ライブラリを1つ追加しただけで上限との差が大きく縮まることがあります。依存を追加するたびに64KBとの差を確認すれば、終盤にまとめてバンドルを削る作業を減らせます。
5. 環境まわりで起きたトラブル3種と対処
環境まわりで止まった原因は3つありました。いずれも検証ストアで実際に起きた挙動で、公式ドキュメントには記載がありません。
deployは成功したのに何も描画されないとき:esbuildの検出範囲
deployが成功しても、画面にエラーを出さないままマイページからextensionが消えることがあります。原因を示す手がかりは画面上にはなく、ブラウザの開発者ツールでコンソールを開くとReferenceErrorが出ています。検証中に実際に起きたのは、リファクタで関数定義を巻き込んで削除し、参照だけが残ったケースでした。名前を変えたのに呼び出し側を変え忘れるのも、同じ類の未定義参照です。
Shopify CLIのビルドはesbuildで行われ、型チェックも未定義識別子の検出も行いません。ビルドが通っても動くとは限らないのはそのためで、検証中の未定義参照はそのままビルドを通過し、初期描画時のReferenceErrorでextensionが部分的にではなく丸ごと消えました。deploy成功のログだけを見ていても気づけない事象で、deployベースの開発では往復が長く、この手戻りが特に大きくなります。
deploy前にtsc --noEmitやESLint(no-undef)を実行すると、esbuildが検出しない未定義参照を見つけやすくなります。
[events]が突然必須になったとき:エラーの出どころと暫定回避
前日まで通っていたdeployが、ある日[events]: Requiredで弾かれました。Events機能を使っていないアプリで、検証中に起きた事象です。アプリ側のコードや設定を変えていないなら、失敗しているのはextensionではなく、アプリ設定に対するサーバー側のバリデーションのほうです。公式開発者フォーラムにも同じ事象の報告がありますが、2026年8月24日時点でスタッフからの回答はありません。
検証では、Eventsを使わないアプリでもshopify.app.tomlにダミー宣言を置くとdeployが通りました。ただし宣言の形には条件があり、[[events.subscription]]まで要求される、api_versionはunstableしか受理されない、uriは相対パスが不可でHTTPSの絶対URLが要る、の3点を満たす必要がありました。次のダミー宣言では、topicsとuriの山括弧の部分を、受理されるトピックと自アプリ管理下のHTTPS絶対URLに置き換えて使います。
[events]
api_version = "unstable"
[[events.subscription]]
topics = ["<受理されるトピック>"]
uri = "<自アプリ管理下の HTTPS 絶対 URL>"
あくまで公式回答のない時点での暫定回避で、ダミーとはいえ購読は実際に登録されます。変更した理由を記録しておき、公式の案内が出たら最新の設定形式へ戻す前提の対処です。
プラットフォームのサーバー側の要件は、手元のCLIが知らないうちに変わることがあります。コードや設定を変えていないのにdeployが通らなくなった場合は、エラーメッセージで公式フォーラムを検索すると、サーバー側の要件変更かどうかを切り分けやすくなります。
複数の組織を扱うときの403:CLI認証の単位
前日まで動いていたshopify app系のコマンドが、突然403を返すようになりました。直近で別の組織に対してshopify auth loginを実行していると、接続先の切り替わりが403の原因になります。Shopify CLIのPartner認証はマシン全体で1セッションで、別の組織へログインするとその時点で接続先が移るためで、検証ストアでも確認した挙動です。
403には権限不足など別の原因もあります。いまどの組織に入っているかと、対象アプリへの権限をあわせて見て、組織が違っていたなら再ログインして正しい組織を選び直すのが対処です。
theme系のコマンドなら、Theme Accessアプリのトークン(SHOPIFY_CLI_THEME_TOKEN)でコマンド単位に接続先を分けられます。app系には同様の分離手段が見当たりませんでした。複数の組織を並行して扱うなら、app系の作業の前に接続先の組織を見ておく習慣が現実的な対策になります。
6. 最新の手順とテンプレートの入手先
この記事の手順は2026年8月時点のものです。CLIの対話フローや生成される雛形は変わっていくので、始める時点の公式資料を起点にします。
Start building for customer accounts(公式チュートリアル)には、target種別ごとのチュートリアルが揃っています。Shopify/customer-account-tutorials(公式サンプルリポジトリ)には、ウィッシュリストやロイヤルティなど完成形のサンプルコードがあり、cloneして使えます。
公式テンプレートを取得したら、生成物のimport、render()、shopify.*の使い方を基準にして、必要なtargetとcapabilityだけを足していきます。
次に読む
extensionを表示できた後は、targetごとに取得できるデータの違いを確認する必要があります。Customer Account UI Extensionsで過去注文のデータを読む・書く:signal・GraphQL・メタフィールドの使い分けでは、過去注文のラインアイテムプロパティはそのまま読めるのか、メタフィールドはなぜ読めないことがあるのか、APIリファレンスだけでは分からないデータアクセスの挙動を実測ベースで整理しています。