Claude CodeでShopify Functionsを書く — function runで出力を検証する手順
Claude Codeに割引・配送のShopify Functionsを書かせるとき、入力クエリとschemaを先に渡し、shopify app function runで出力JSONを確かめる反復と権限設定をまとめます。
Claude CodeでShopify Functionsを書く — function runで出力を検証する手順
Claude Codeに割引や配送のShopify Functionsを書かせるなら、検証の軸は shopify app function run です。Shopify Functionsは、入力JSONを受け取って出力JSONを返す関数です。入力を固定すれば出力も確かめられるので、Claudeは書いた直後に自分の結果を読んで直せます。
ただしその前に、Claudeへ渡す材料を選ぶ必要があります。この記事では、入力クエリとschemaの渡し方、function run での反復、権限の線引きを順に扱います。
Shopify Functionsは何を受け取り、何を返すのか
Functionsはターゲットに差し込まれ、Shopifyのバックエンドの処理を書き換えます。呼び出しはShopifyが必要なときに行い、URLで直接叩くことはできません。
関数は3つの部分でできています。
関数を構成する3要素
入力
自分で定義するGraphQLクエリの結果が、JSONオブジェクトとして渡されます。カート行の商品データやメタフィールドなど、必要な項目だけを選びます。
ロジック
WebAssemblyにコンパイルできる言語で書きます。テンプレートとクライアントライブラリはRustとJavaScriptに用意されています。
出力
Shopifyに実行してほしい操作を記述したJSONです。ターゲットごとの入力と出力は、Shopifyが提供するGraphQLスキーマで決まります。
Claudeに書かせる場合、ロジックの部分は得意な作業です。外れやすいのは入出力の形で、入力クエリに無いフィールドを読んだり、スキーマに無い操作名を出力したりします。そこで、形を先に固定してから書かせます。
割引Functionのファイル構成を把握する
割引Functionのチュートリアルでは、次のコマンドで雛形を作ります。
shopify app generate extension \
--template discount --name discount-function-rs言語はRustを選ぶ流れです。生成される shopify.extension.toml では、ターゲットごとに target、input_query、export の3つを指定します。
[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"見出しに input.graphql と書く記事が多いものの、ファイル名は固定ではありません。input_query に書いたパスが入力クエリの場所で、割引テンプレートでは cart_lines_discounts_generate_run.graphql という名前です。Claudeへの指示では、ファイル名ではなく「input_query が指すファイル」と伝えると取り違えません。
割引APIの run ターゲットは2つあります。
| ターゲット | 割引できる対象 |
|---|---|
cart.lines.discounts.generate.run | 割引できる対象カート行と注文の小計 |
cart.delivery-options.discounts.generate.run | 割引できる対象配送料金 |
配送割引を書きたいときは、後者のターゲットと、専用の入力クエリ・出力型を使います。テンプレートの入力クエリは、配送側では cart.deliveryGroups の id と discount.discountClasses を取る最小の形です。
入力クエリとschemaを先にClaudeへ渡す
書かせる順序は、入力クエリが先、ロジックが後です。ロジックから頼むと、Claudeは必要そうなフィールドを想像して読みにいきます。
最新のGraphQLスキーマは、関数のディレクトリで shopify app function schema を実行すると schema.graphql に書き出されます。APIの種類とバージョンは、拡張機能のTOMLに書かれた値が使われます。ファイルを作りたくない場合は --stdout で標準出力にもできます。
cd extensions/discount-function
shopify app function schemaJavaScriptの関数なら、入力クエリからTypeScriptの型を作る shopify app function typegen もあります。型が付くと、存在しないフィールドを読むコードがエディターと tsc の段階で落とせます。Rustの場合、生成された schema モジュールの型が同じ役割を担います。
プロンプトの例を挙げます。
extensions/discount-function の cart.lines.discounts.generate.run を実装して。
- 入力は shopify.extension.toml の input_query が指す .graphql だけを使う
- 読みたいフィールドが足りなければ、先にそのクエリへ追加してから実装する
- 出力の型は schema.graphql の CartLinesDiscountsGenerateRunResult に従う
- 実装後は test/input.json を使って shopify app function run で確かめるポイントは3つ目の「型に従う」です。出力はスキーマで決まるため、Claudeが推測で組み立てた形ではなく、schema.graphql を読んだ結果を使わせます。
function runで出力を確かめる反復
shopify app function run は、現在のディレクトリの関数を1回だけ実行します。入力JSONは --input(-i)で渡し、省略すると標準入力から読みます。--export(-e)で呼び出すWebAssemblyのエクスポート名を指定し、--json(-j)を付けると結果をJSONで出力します。
まず入力JSONを用意します。ここではクエリが cart.lines の id、cost.subtotalAmount.amount、discount.discountClasses を取る前提の、手書きの例です。実際の値の型や形は、schema.graphql と見比べて直してください。
{
"cart": {
"lines": [
{ "id": "gid://shopify/CartLine/1",
"cost": { "subtotalAmount": { "amount": "5000.0" } } }
]
},
"discount": { "discountClasses": ["PRODUCT"] }
}実行します。
shopify app function build
shopify app function run \
--export cart_lines_discounts_generate_run \
--input test/input.json --jsonbuild を先に置くのは、run の説明に「現在のディレクトリの関数を実行する」としか書かれておらず、ソース変更が自動で反映されるとは読み取れないためです。コンパイルを挟めば、古いWasmを実行して「直したのに変わらない」状態を避けられます。
出力は、スキーマに沿った操作の配列です。商品割引なら productDiscountsAdd のような操作に、selectionStrategy と candidates が入る構造になります。公式のRustサンプルは、discount.discountClasses に Product が含まれるときだけ商品割引を返し、含まれなければ空の operations を返します。
出力が期待と違うときの切り分け
- Qoperations が空配列か?答えがいいえのとき答えがはい
入力JSONの
discountClassesに、実装が見るクラスが含まれているかを見ます。クラスが合わないと、関数は空の結果を返す作りです。 - Qスキーマに無い名前の操作が出ているか?答えがいいえのとき答えがはい
schema.graphqlと出力のキー名を突き合わせます。違いがあれば、Claudeに該当の型定義を読ませて直させます。
出力の形が合った
次は金額や対象のカート行IDなど、値の中身を確認します。
この反復をCLAUDE.mdで固定しておくと、毎回プロンプトに書かずに済みます。
# Shopify Functions
## 検証(実装のたびに実行)
- 関数ディレクトリで `shopify app function build` を実行する
- `test/*.json` の入力ごとに
`shopify app function run -e <export> -i <file> --json` を実行し、
出力が期待どおりか確かめてから完了を報告する
## 規約
- 入力クエリは shopify.extension.toml の input_query が指すファイルだけ
- 読む項目を増やすときは、先にそのクエリへ追加する
- 型は schema.graphql に従い、推測でフィールド名を書かない
## 禁止
- shopify app deploy は実行しない(人間が実行する)本番の入力でも再現できる
手書きの入力だけでは、実際のカートとのずれに気づけません。公式の手順では、shopify app dev を動かすと関数の実行ログが端末に流れ、入力・出力などの詳細が .shopify/logs ディレクトリのファイルにも書かれます。このファイルは、そのままテストの入力として使えます。
実行済みの入力で動作を見直すには、shopify app function replay が使えます。ログの識別子(6文字)を --log で指定するか、省略して一覧から選びます。run が1回の実行であるのに対し、replay は記録済みの実行を、コードを直すたびに再実行します。
JavaScriptの関数テンプレートには tests/ ディレクトリと fixtures/log.json が付き、npm test でWasmをビルドしてフィクスチャーの入出力を照合できます。フィクスチャーには export、target、input、output を書きます。Claudeに「新しい割引パターンを足すたびに、フィクスチャーも1つ追加する」と頼めば、回帰テストが育ちます。
権限は「検証は許可、反映は確認」に分ける
function build と function run は手元のファイルを読んで実行するだけです。許可リストに入れて承認の手間を省けます。一方、app dev は開発ストアへ接続し、app deploy はアプリのバージョンを作って配信するため、確認を挟みます。
{
"permissions": {
"allow": [
"Bash(shopify app function build *)",
"Bash(shopify app function run *)",
"Bash(shopify app function schema *)"
],
"ask": [
"Bash(shopify app dev *)",
"Bash(shopify app deploy *)"
]
}
}shopify app deploy は、アプリをビルドし、設定と拡張機能のスナップショットであるアプリバージョンを作って、ユーザーへリリースします。非対話の環境では --allow-updates、--allow-deletes、--no-release のいずれかが要るため、無人実行でこれらを付けるとリリースまで進んでしまいます。確認なしで通らないよう、ask に置いておく設計です。
allow と ask の優先関係は、ルールの書き方として覚えておくと便利です。同じ呼び出しに両方が当てはまると、ask が先に働き、確認が出ます。ルールの詳細はAzure Functionsの権限設計の記事にもまとめています。
利用条件と、書く前に知っておく制限
- プラン: どのプランのストアでも、App Storeで配布される公開アプリの関数は使えます。カスタムアプリに含まれるShopify Function APIを使えるのはShopify Plusのストアだけです
- 言語: 関数はRustかJavaScriptのテンプレートから始められます。公式は、カートが大きいと関数が失敗しないよう、最も高速なRustを強く推奨しています
- ログ: 関数のログは1 kBで切り捨てられます。デバッグ用の
console.logやlog!は、デプロイ前に消し、機密データは書かないよう注意書きがあります - 削除: 関数を消すには
extensionsディレクトリから関連ファイルを削除して再デプロイします。関連する関数のオーナー(割引など)も完全に削除され、関数はインストール済みの全ストアで動かなくなります
Claudeに「不要な拡張機能を片付けて」と頼むと、この削除に当たる変更が入る場合があります。extensions 配下の削除を伴う差分は、デプロイ前に人間が確認する運用が安全です。
つまずきやすい点
落とし穴
Rustのビルドで時間を使う
Rustの関数は cargo build --target=wasm32-unknown-unknown --release でコンパイルされます。チュートリアルは、最新のRustとの互換性のために wasm32-unknown-unknown ターゲットのインストールを前提にしています。ビルドが通らないときは、実装を疑う前にこのターゲットの有無を確認させます。
- 入力クエリを増やしたのに値が来ない: アプリのアクセススコープが、クエリの読む項目に足りていない可能性があります。割引のチュートリアルは
write_discountsとread_productsを前提にしています - 出力が通るのに割引が付かない: 関数が正しくても、ストア側で自動割引の作成と有効化が必要です。ここから先は
app devと開発ストアでの確認になり、function runの守備範囲を超えます - Wasmとユニットテストの差: Vitestなどのユニットテストは、WebAssemblyの上では動きません。まれに本番と違う結果になるため、仕上げの確認は
function runで行います
まとめ
Shopify Functionsは入力JSONと出力JSONの契約がはっきりしているため、Claude Codeと相性のよい題材です。入力クエリとschemaを先に固定し、function run の出力で完了を判定する流れにすれば、「それらしいが動かない」コードを減らせます。
shopify app deploy と開発ストアへの接続は人間の確認に回し、読み取りと検証だけを許可します。Shopify向けのスキルやMCPの入れ方はShopify AI Toolkitの記事、Liquidテーマの検証はテーマ開発の記事が扱っています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- shopify.dev/docs/apps/build/functions
- shopify.dev/docs/apps/build/discounts/build-discount-function
- shopify.dev/docs/apps/build/functions/test-debug-functions
- shopify.dev/docs/api/shopify-cli/app/app-function-run
- shopify.dev/docs/api/shopify-cli/app/app-function-schema
- shopify.dev/docs/api/shopify-cli/app/app-deploy
- code.claude.com/docs/en/permissions