Claude CodeでShopifyのアプリブロックを作る — theme app extensionの検証
Claude CodeでShopifyのtheme app extensionを作り、blocks/*.liquidのschemaとassetsの置き方を整え、shopify app buildで直す手順を権限と公開の分け方まで示します。
Claude CodeでShopifyのアプリブロックを作る — theme app extensionの検証
Shopifyアプリの機能をストアのテーマに差し込むには、theme app extensionを使います。Claude Codeに任せるなら、書く場所は extensions/<名前>/blocks/*.liquid と assets/ に絞り、検証は shopify app build に寄せるのが要です。このコマンドはTheme Checkを拡張機能に対して走らせます。
テーマのファイルを直接書き換える方式とは、触る場所も守る基準も違います。テーマ直接編集の流れはClaude CodeでShopifyテーマ開発を進める手順に書きました。ここでは、アプリ側の拡張機能として作るときのschema、assets、権限、公開の分け方を順に扱います。
theme app extensionは何を差し込む仕組みか
theme app extensionは、アプリブロック、アプリ埋め込みブロック、assets、snippetsの束です。マーチャントはテーマのLiquidやコードに触れずに、レビュー表示や価格、評価のような動的な要素をテーマへ足せます。対象はOnline Store 2.0のテーマです。
テーマコードを書き換えないので、テーマを壊すリスクが下がり、アプリの更新を全ストアへ同時に配れます。テーマエディターにも自動で現れるため、位置の調整や設定の画面をアプリ側で作り直す必要がありません。
ブロックには2種類あります。違いは「どこに出すか」と「初期状態」です。
アプリブロックとアプリ埋め込みブロック
アプリブロック
ページ内の好きなセクションに差し込む型です。星評価やレビュー一覧のように、位置をマーチャントが動かしたい要素に向きます。インストール直後のテーマには入っておらず、エディターのAppsから追加します。
アプリ埋め込みブロック
</head> や </body> の前に出力される型です。チャットバブル、タグ、分析のように画面を持たない、または浮かぶ要素に向きます。インストール後は無効で、Theme SettingsのApp embedsで有効にします。
ScriptTag に頼らない理由もここにあります。アプリ埋め込みブロックは、スクリプトを特定のページだけで読み込めます。ScriptTag ではそれができないと説明されています。必要なページだけに限れば、アプリがストアの表示速度に与える影響も抑えられます。
ファイル構成とschemaの決まり
shopify app generate extension でTheme app extensionを選ぶと、次の構成が生成されます。Claudeに触らせる範囲は、この下に限ります。
extensions/
my-theme-app-extension/
assets/ # CSS・JS・画像
blocks/ # アプリブロック・埋め込みブロックのLiquid
snippets/ # ブロックから共有するLiquid部品
locales/ # 翻訳JSON
package.json
shopify.extension.toml # name と type = "theme"shopify.extension.toml は最小で済みます。ブロックの挙動はconfigではなく、各Liquidファイルの末尾にある {% schema %} が決めます。
name = "my-theme-app-extension"
type = "theme"公式の例を基に、商品ページ用のアプリブロックを書くと次の形になります。例示としての形で、app.css と app.js は assets/ に置く前提です。
<div class="my-badge" style="color: {{ block.settings.color }}">
{{ block.settings.label }}
</div>
{% schema %}
{
"name": "Badge",
"target": "content",
"enabled_on": { "templates": ["product"] },
"stylesheet": "app.css",
"javascript": "app.js",
"settings": [
{ "label": "Color", "id": "color", "type": "color", "default": "#000000" },
{ "label": "Label", "id": "label", "type": "text", "default": "New" }
]
}
{% endschema %}schemaで間違えやすい点を、Claudeに渡す前提として表にします。
| 項目 | 決まり |
|---|---|
name | 決まり必須。25文字未満に収め、アプリ名を含めない |
target | 決まり必須。アプリブロックは content、埋め込みは head / compliance_head / body |
stylesheet / javascript | 決まりassets/ 内のファイルを指す。同じファイルを複数ブロックが指しても1回だけ読み込まれる |
enabled_on と disabled_on | 決まりどちらか一方だけ使える。チェックアウトの各ステップには描画できない |
available_if | 決まり値はapp-dataメタフィールドの参照。boolean型のメタフィールドが前提 |
target の旧値 section は今も動き、content と同じ挙動です。新しく作るブロックは content を使います。
制限と、できないこと
ここを知らないと、Claudeが「動くはず」のコードを書いてデプロイで落ちます。公式が挙げる強制の上限は次のとおりです。
デプロイで検証される上限
拡張機能の全ファイル
10 MB
強制
ブロック数
30
強制
Liquid全体のサイズ
100 KB
強制
locale ファイル
15 KB / 本
強制。100本まで
schemaから直接参照するCSSは圧縮後100KB、JSは圧縮後10KBが推奨値です。こちらは強制ではありません。
そのほかの制約は次のとおりです。
- チェックアウトのページ群(連絡先、配送方法、支払い方法、注文状況)には出せません
content_for_header、content_for_index、content_for_layoutのLiquidオブジェクトは使えません- 親セクションのプロパティで使えるのは
idだけです - JSONにコメントや末尾カンマを書けません。テーマのファイルとは違う点です
テーマ側にも条件があります。アプリブロックが動くには、JSONテンプレートと、@app 型のブロックを受けて描画するセクションが必要です。Dawnの商品メインセクションが実装例として挙げられています。古いVintageテーマでは、アプリブロックでなく埋め込みブロックを選ぶことになります。
CLAUDE.mdに書く内容
CLAUDE.mdは公式の目安で1ファイル200行未満です。拡張機能の作業に必要な範囲に絞った例を示します。
# Shopify theme app extension
## 作業範囲
- 編集は extensions/<拡張名>/ の下だけ。テーマのファイルは触らない
- ブロックは blocks/*.liquid、共有部品は snippets/、CSS・JSは assets/
## schema
- アプリブロックは "target": "content"。"section" は使わない
- name はアプリ名を含めず25文字未満
- enabled_on と disabled_on は同時に書かない
- stylesheet / javascript は assets/ に実在するファイル名を指す
## 検証(編集のたびに実行)
- `shopify app build` が成功するまで直す
- 失敗したら出力のチェック名と行を読み、原因を報告してから直す
## 禁止
- `shopify app deploy` は実行しない(公開は人間が行う)
- ScriptTag APIでスクリプトを注入しない「検証が通るまで直す」を完了条件にする点は、テーマ直接編集のときと同じです。違うのは、検証の入口が shopify theme check でなく shopify app build になること。公式の手順では、このコマンドがTheme Checkを拡張機能に対して実行し、上の制限値を超えていないかも確かめます。
buildを回して直させる反復
最初の1回は、生成直後の拡張機能で shopify app build が通ることを確かめます。ここが基準になります。
shopify app generate extension # Theme app extension を選ぶ
shopify app buildそのうえでClaudeに変更を頼みます。たとえば「商品ページにだけ出すバッジのブロックを追加して。ラベルと色を設定にして」と、目的だけを書きます。Claudeはblockを追加し、schemaを書き、shopify app build を実行して、出力を読んで直します。
生成された拡張機能のサンプルには前提があります。商品の評価を表示する例なので、メタフィールド定義(Products、namespace demo、key avg_rating、Integer)を作って値を入れておかないと、画面に何も出ません。ビルドが通っても表示が空という状況は、検証コマンドでは見つかりません。表示の確認は、次のプレビューで人間が行います。
表示されないときの切り分け
- Qアプリブロックをテーマエディターの Apps から追加したか?答えがいいえのとき答えがはい
アプリブロックは、インストールしただけではテーマに入りません。エディターで追加して保存します。
- Q追加したテンプレートは schema の enabled_on に含まれるか?答えがいいえのとき答えがはい
enabled_onかdisabled_onを見直します。外れたテンプレートでは選択肢に出ません。 - Q埋め込みブロックを App embeds で有効にしたか?答えがいいえのとき
インストール後は無効です。Theme SettingsのApp embedsで有効にします。
メタフィールドの値や、セクションが
@appブロックに対応しているかを確かめます。
プレビューは shopify app dev で行います。ホットリロードに対応し、Google Chromeでのみ使えます。テーマを指定しなければ、開発ストアにDawnがアップロードされます。--theme で既存のテーマを指定すると、そのテーマの上で確かめられます。
PostToolUse hookでbuildを固定する
CLAUDE.mdの指示は、守られない回があります。毎回確実に走らせたいなら、PostToolUse hookで固定します。終了コード2で終わると、標準エラーがClaudeに返り、直すきっかけになります。
#!/bin/bash
# .claude/hooks/app-build.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE" in
*/extensions/*) ;;
*) exit 0 ;;
esac
OUT=$(shopify app build 2>&1)
if [ $? -ne 0 ]; then
echo "$OUT" >&2
exit 2
fi
exit 0{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/app-build.sh"
}
]
}
]
}
}extensions/ 配下を編集したときだけ走るので、アプリ本体のコードを直すたびにビルドが走ることはありません。ビルドが重いと感じたら、編集ごとでなく作業の終わりに1回走らせる運用に切り替えます。
権限 — buildは許可、デプロイは確認を挟む
shopify app build はローカルの検証なので、許可に入れて承認の手間を省けます。一方で、deploy は扱いが違います。
{
"permissions": {
"allow": [
"Bash(shopify app build *)"
],
"ask": [
"Bash(shopify app deploy *)",
"Bash(shopify app release *)",
"Bash(shopify app dev *)"
]
}
}shopify app deploy はアプリバージョンを作って、既定ではそのまま公開します。アプリバージョンは、アプリの設定とすべての拡張機能のスナップショットです。公開すると、いま配信されているバージョンを置き換えます。アプリをインストール済みのストアに行き渡るまで、数分かかることがあります。
公開を人間の判断に残したいときは、--no-release を付けてバージョンだけを作る手があります。作ったバージョンは shopify app release かDev Dashboardから公開できます。CLAUDE.mdには「deploy は --no-release を付ける」と書き、権限は ask に置く形が、事故を避けやすい構成です。
公開後に残る作業
コードが通っても、マーチャントが使える状態とは限りません。
- 導入の案内: アプリブロックは自動で入らないため、追加の手順をアプリ内で示す必要があります。ディープリンクを使えば、テーマエディターを開いたところでブロックを足せます。UXガイドラインも、導入手順の用意を求めています
- ディープリンク: アプリブロックは
addAppBlockId={api_key}/{handle}、埋め込みブロックはactivateAppId={api_key}/{handle}で指定します。handleはブロックのLiquidのファイル名(拡張子なし)です - ブロックの検出: アプリ側で、追加済みかどうかを
app.extensions()で確かめられます
ディープリンクのURLは打ち間違いやすく、Claudeに書かせたあとは実機で一度開くのが確実です。テンプレートやセクションが見つからないとマーチャントにエラーが表示されます。
まとめ
theme app extensionをClaude Codeに任せるときの要点は3つです。編集範囲を extensions/ の下に限ること、shopify app build が通ることを完了条件にしてhookで固定すること、deploy を ask に置いて公開は人間が決めることです。ビルドが通っても、アプリブロックの追加やメタフィールドの値といった表示側の条件は検証されないため、プレビューで画面を確かめる工程は残ります。
Shopifyでの割引や配送のロジックを書く場合は、Shopify Functionsの検証手順が参考になります。Admin APIを叩く処理はGraphQLレート制限の守り方にまとめています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- shopify.dev/docs/apps/build/online-store/theme-app-extensions
- shopify.dev/docs/apps/build/online-store/theme-app-extensions/configur…
- shopify.dev/docs/apps/build/online-store/theme-app-extensions/build
- shopify.dev/docs/apps/build/online-store/theme-app-extensions/ux
- code.claude.com/docs/en/hooks-guide
- code.claude.com/docs/en/memory
- code.claude.com/docs/en/permissions