Claude CodeでShopify Scriptを書き換える — 廃止後のFunctions移行手順
2026年6月30日に廃止されたShopify ScriptsをFunctionsで作り直す手順です。Rubyの3種類のScriptをAPIに対応づける表と、Claude Codeで棚卸しして入力JSONで挙動を確かめる流れをまとめます。
Claude CodeでShopify Scriptを書き換える — 廃止後のFunctions移行手順
Shopify Scriptsは2026年6月30日に廃止されました。その時点で公開されたままだったScriptは無効化され、動かなくなっています。Script Editorで作ったRubyの割引・配送・支払いのロジックは、Shopify Functionsで作り直すしかありません。
書き換えの難所は、Rubyの1行ごとの意味を失わずにFunctionsのAPIへ振り分けることです。Claude Codeには、Scriptの棚卸しと入力JSONの作成を任せられます。この記事では、対応表、棚卸しの自動化、入力JSONでの挙動の突き合わせを順に扱います。
廃止後のScriptは止まっている — まず何を見るか
Script Editorのヘルプには、廃止日以降は公開中だったScriptが無効化されたと明記されています。移行の入口は、管理画面の「Shopify Scriptsカスタマイズレポート」です。廃止前に動いていたカスタマイズが、Payment gateways、Shipping、Product discountsの3区分で並びます。
レポートにはScriptの名前、説明、ソースファイルへのリンクが載ります。推奨アプリと、Functionsのチュートリアルへのリンクも付きます。CSVでの書き出しにも対応しています。不要になったカスタマイズは、作り直さずに外してかまいません。
作り直す方法は2通りです。Functionsで作られた既存アプリを入れるか、自前でFunctionsを書くか。後者で押さえておく条件が2つあります。
- Functionsを使うアプリはApp Bridgeが前提で、管理画面で作るカスタムアプリは対応しません。自作する場合はDev Dashboardで作ってストアにつなぎます
- Plusプラン以外のストアでは、App Storeで配布される公開アプリに含まれるFunctionsだけが使えます。カスタムアプリのFunctions APIはPlusプランが条件です
自前で書くと決めたところから、Claude Codeの出番です。
Rubyの3種類のScriptはFunctionsのどのAPIに当たるか
Scriptの種類は3つです。行の価格を変えるラインアイテムScript、配送料金を扱うシッピングScript、支払い方法を扱うペイメントScript。FunctionsはこれをAPI単位に分け直しており、1対1ではなく、操作ごとに行き先が違います。
| やりたいこと | Scriptのメソッド | Functions API | ターゲット / 操作 |
|---|---|---|---|
| 商品・注文の値引き | Scriptのメソッドchange_line_price | Functions APIDiscount | ターゲット / 操作cart.lines.discounts.generate.run の productDiscountsAdd / orderDiscountsAdd |
| 送料の値引き | Scriptのメソッドapply_discount | Functions APIDiscount | ターゲット / 操作cart.delivery-options.discounts.generate.run の deliveryDiscountsAdd |
| 配送方法の名称変更・非表示・並べ替え | Scriptのメソッドchange_name / delete_if / sort! | Functions APIDelivery Customization | ターゲット / 操作rename / hide / move |
| 支払い方法の名称変更・非表示・並べ替え | Scriptのメソッド同上(payment_gateways) | Functions APIPayment Customization | ターゲット / 操作rename / hide / move |
| 行の価格の上書き・バンドル化 | Scriptのメソッド(Scriptでは不可) | Functions APICart Transform | ターゲット / 操作lineUpdate など |
この表のうち、メソッドとAPIの組み合わせは、各APIの用途説明から対応づけたものです。Shopifyが「このメソッドはこのAPI」と1対1で定めた表ではありません。
注意点が3つあります。
送料の値引きは、配送のカスタマイズではなく割引APIの担当です。Delivery Customizationが扱うのは名称・順序・表示の3つで、金額には触れません。apply_discount をそのままDelivery Customizationに移そうとすると、行き先がありません。
Cart Transformの価格上書きは、移行の逃げ道にしにくい操作です。 lineUpdate を使うアプリは、開発ストアかPlusプランのストアでしか使えません。Scriptは行の値下げしかできなかったので、同じ挙動の再現なら割引APIで足ります。
各APIとも、ストアで有効にできる関数は25個までです。割引、配送のカスタマイズ、支払いのカスタマイズで、それぞれ上限があります。Cart Transformはアプリごとに1つです。
書き換えで挙動が変わる4点
同じ割引を作ったつもりでも、動く仕組みが変わると結果がずれます。移行前に洗い出しておく差は次のとおりです。
| 論点 | Scripts | Functions |
|---|---|---|
| 同時に動く数 | Scripts種類ごとに1本だけ公開 | FunctionsAPIごとに最大25個 |
| 割引同士の関係 | Scripts1本のScriptの中で直列に処理 | Functions各関数は互いを知らず並行実行 |
| 割引コード | Scriptsdiscount_code は1つだけ返す | FunctionsenteredDiscountCodes で入力できる |
| メタフィールド | Scripts読めない | Functions入力クエリで取得できる |
最も壊れやすいのは2行目です。Scriptでは、1本の中で「VIPなら10%引き、さらに500円引き」と順に書けました。Functionsの割引は互いを知らないまま並行に動き、組み合わせはディスカウント側の併用設定で決まります。ロジックを1つの関数に詰め込むか、複数の割引に分けて併用設定を整えるかは、移行前に決めておく論点です。
3行目も見落としやすい差です。ラインアイテムとシッピングのScriptは、複数の割引コードを併用する注文に対応していませんでした。Functions側は入力で複数のコードを受け取れるので、Rubyの条件分岐をそのまま移すと、コードが複数入った注文でだけ結果が変わることがあります。
Scriptの棚卸しをClaude Codeにやらせる
Scriptが10本以上あるなら、まず台帳を作ります。Claude Codeの非対話モード(claude -p)は、--output-format json と --json-schema を組み合わせると、決めた形のJSONで結果を返します。構造化された出力は structured_output に入るので、jq で取り出せます。
次のスクリプトは、scripts/ に置いたRubyファイルを1本ずつ読ませる例です。読むだけなので Read だけを許可し、--bare で手元のフック類を読み込まない状態にしています。
for f in scripts/*.rb; do
claude --bare -p "$f を読み、種類と操作を分類して" \
--allowedTools "Read" \
--output-format json \
--json-schema '{"type":"object","properties":{
"file":{"type":"string"},
"kind":{"enum":["line_item","shipping","payment"]},
"methods":{"type":"array","items":{"type":"string"}},
"uses_discount_code":{"type":"boolean"},
"risk":{"type":"string"}},
"required":["file","kind","methods"]}' \
| jq -c '.structured_output'
done > inventory.jsonlinventory.jsonl ができたら、先ほどの対応表で行き先を振ります。ここでの狙いは、Rubyのロジックを機械的に写すことではありません。どのScriptが、どのAPIの、何個の関数になるかを先に決めることです。risk の欄に「割引コードに依存」「並び順に依存」といった注記が出ていれば、前節の差に当たるScriptです。
台帳を作る段階で、Claudeの判定を鵜呑みにしないでください。分類を誤ると、後の関数がAPIごと違ってしまいます。methods に挙がったメソッド名が実際のRubyに出てくるか、grep で突き合わせるのが確実です。
CLAUDE.mdに移行のルールを書いておく
Functionsの書き方そのものは、Claude CodeでShopify Functionsを書く手順で扱っています。移行の場合は、そこに「Scriptを正とする」ルールを足します。
# Shopify Scripts から Functions への移行
## 前提
- 元の Ruby は scripts/*.rb。動作の正はこのファイルの挙動とする
- Functions の入出力は shopify app function schema が書き出す schema.graphql に従う
## 手順
- 1 つの Script を移すたびに、test/<script名>/ に入力 JSON を 3 件以上置く
(対象になる / 対象外 / 空のカート)
- 各入力を shopify app function run で実行し、出力を期待値と見比べる
- Ruby にあって schema に対応するフィールドが見つからない条件は、
推測で実装せず「未対応」と報告する
## 禁止
- shopify app deploy は実行しない最後から2つ目の「未対応と報告する」が要です。Rubyの product.gift_card? に当たるフィールドは、割引Function APIのページの入力例に出てきません。Claudeに任せると、それらしい名前のフィールドを書いてしまいがちです。schema.graphql に存在するかを確かめさせ、無ければ止まらせます。
古い言語のスクリプトを読み解く作業そのものは、Claude CodeでPerlの古いスクリプトを読解する記事と同じ考え方で進められます。先に仕様を文章にしてから移すと、取りこぼしが減ります。
入力JSONで挙動の差を確かめる
具体例で流れを見ます。移行対象は、VIPタグの顧客だけ、ギフトカード以外の行を10%引きにするラインアイテムScriptです。
customer = Input.cart.customer
if customer && customer.tags.include?('VIP')
Input.cart.line_items.each do |line_item|
next if line_item.variant.product.gift_card?
line_item.change_line_price(line_item.line_price * 0.9, message: 'VIP 10% OFF')
end
end
Output.cart = Input.cartFunctions側の入力クエリは、顧客のタグ判定とカート行のIDがあれば足ります。割引APIの公式サンプルには、顧客の hasAnyTag(tags: ["VIP"]) を入力で取る例があります。
query Input {
cart {
lines { id }
buyerIdentity { customer { hasAnyTag(tags: ["VIP"]) } }
}
discount { discountClasses }
}ギフトカードの除外はここに入っていません。前節の理由で、フィールドの有無を schema.graphql で確かめてから足します。
入力JSONは、Rubyの分岐を1つずつ踏む形で用意します。
| ケース | 入力の特徴 | 期待する出力 |
|---|---|---|
| A | 入力の特徴VIPの顧客、通常商品1行 | 期待する出力10%の商品割引が1件 |
| B | 入力の特徴VIPではない顧客 | 期待する出力operations が空 |
| C | 入力の特徴カートが空 | 期待する出力operations が空 |
ケースAの入力JSONは次のようになります。
{
"cart": {
"lines": [{ "id": "gid://shopify/CartLine/1" }],
"buyerIdentity": { "customer": { "hasAnyTag": true } }
},
"discount": { "discountClasses": ["PRODUCT"] }
}関数をビルドして、ケースごとに実行します。
shopify app function build
shopify app function run \
--export cart_lines_discounts_generate_run \
--input test/vip-discount/case-a.json --jsonケースAの出力は、次のような形になっていれば期待どおりです(設計上の期待値で、実行結果ではありません)。
{
"operations": [{
"productDiscountsAdd": {
"candidates": [{
"message": "VIP 10% OFF",
"targets": [{ "cartLine": { "id": "gid://shopify/CartLine/1" } }],
"value": { "percentage": { "value": "10.0" } }
}],
"selectionStrategy": "FIRST"
}
}]
}複数行のカートでは、候補を行ごとに分けるか、selectionStrategy をどうするかで結果が変わります。1行のケースで形を固めたあとに、2行以上のケースを足し、スキーマの定義と出力を見比べます。ここはClaudeに推測させず、schema.graphql を読ませたうえで書かせる箇所です。
ケースBで割引が出たときの切り分け
- Q入力JSONのhasAnyTagがfalseになっているか?答えがいいえのとき答えがはい
falseなのに割引が出るなら、関数が入力のタグ判定を見ていません。入力クエリと条件分岐の対応を読み直します。
- QdiscountClassesに対象のクラスが含まれているか?答えがいいえのとき答えがはい
クラスが合わないケースの扱いが、Rubyの挙動と食い違っている可能性があります。含まれないときに空を返す実装かを確かめます。
Rubyとの分岐が一致した
次はVIP以外の割引と、併用する別の割引を加えたケースへ進みます。
金額と併用の差は、本番に近い入力で確かめる
手書きの入力で確かめられるのは、条件分岐の対応までです。金額の丸めや、他の割引との併用は、手書きの1行カートでは現れません。
Rubyでは Money オブジェクトが金額を計算していました。Functionsの割引は割合や固定額を返す形で、最終的な金額はShopify側が計算します。計算の主体が変わるので、複数行・複数個・端数の出る金額のケースを足して、Scriptで実際に請求していた金額と見比べます。手元に過去の注文があれば、その入力を再現したJSONが最も説得力のある期待値になります。
併用の確認は、関数の外側の話です。割引同士の組み合わせは、ディスカウント側の併用設定で決まります。function run では1つの関数の出力しか見えないため、併用の結果はテスト用のストアで実際にカートを組んで確かめる必要があります。
よくある質問
元のRubyのソースはどこから取り出すのか
カスタマイズレポートの各区分に、廃止前にアクティブだったカスタマイズの名前とソースファイルへのリンクが載ります。そこから元のRubyを取り出して、scripts/ に保存します。
既存アプリで済む場合、Claude Codeは要らないのか
要りません。カスタマイズレポートには、各Scriptに合う推奨アプリが出ます。アプリで満たせるものはアプリに任せ、レポートに該当がないロジックだけを自前の関数にする使い分けが現実的です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。