Shopify Scripts を Functions に移行する方法: 完全コードチュートリアル(2026年版)

Shopify Scripts を Functions に移行する方法: 完全コードチュートリアル(2026年版)

Shopify Scripts を Functions に移行する方法: 完全コードチュートリアル(2026年版)

Shopify Scripts から Functions への移行方法 — ブログのヒーローイラスト

2026年4月16日です。昨日——4月15日——は、ShopifyがScript Editorを完全にロックした日でした。新しいScriptを作成したり公開したりすることはもうできません。実行停止は75日後、2026年6月30日に訪れます。

もしあなたがShopify Plusの開発者——あるいはPlusストアを運営する代理店——で、この移行をここ12か月ずっと「次のスプリント」に先送りしてきたなら、問題があります。しかも「まあ直せるなら直す」程度ではありません。「7月1日深夜にチェックアウトが壊れる」レベルの問題です。多くのPlusストアでは、何年にもわたって5〜20個のScriptsが蓄積されており、それぞれが、誰も書いた覚えのない割引ルール、配送方法の非表示、支払い方法の制御を静かに担っています。

このガイドは、1月にあればよかったと思っていた技術的な移行マニュアルです。 単なる戦略ではなく、実際のコードまで扱います。読み終えるころには、Shopify CLIでFunctionのひな形を作成し、割引・配送カスタマイズ・支払いカスタマイズのためのRustまたはJavaScriptのロジックを書き、顧客の一部にタグを付けて安全にテストし、既存のチェックアウトを壊さずに本番へ出す方法がわかるはずです。

あなたのストアからScriptsを外し、Functionsを導入しましょう。


Developer migrating Shopify Scripts to Shopify Functions modules

簡潔な答え: Scripts → Functions を60秒で理解する

1段落でいうと: Shopify Scripts(Script Editor内で書くRubyコード、Plus限定)は、Shopify Functions(RustまたはJavaScriptで書かれたWebAssemblyモジュール、全プランで利用可能)に置き換えられています。shopify app generate extensionでFunctionのひな形を作り、必要なカートデータを取得するrun.graphqlクエリを書き、割引・非表示の配送方法などの操作を返すrun.rsまたはrun.jsファイルを書き、shopify app deployでデプロイしてからAdminまたはGraphQL mutationで有効化します。Functionsはコンパイル済みWASMとして5ms未満のレイテンシで実行され、全プランで動作し、今後Shopifyがサポートする唯一のカスタマイズ手段です。

6月30日に実際に何が変わるのか

コードに触る前に、日付を整理しましょう。2つあり、どちらも重要です。

日付

起こること

あなたの対応

2026年4月15日 (経過済み)

Script Editorが読み取り専用に。新しいScriptの作成不可。既存Scriptの編集不可。

既存Scriptsはまだ実行されます。今すぐ移行するか、ロジックを凍結してください。

2026年6月30日

すべてのShopify Scriptsが実行停止します。以上です。

この日までにFunctionの置き換えを本番稼働させておく必要があります。

移行は二択です。6月30日までにFunctionがデプロイされ、チェックアウトが動き続けるか、そうでないか。そうでなければ、影響を受けるすべてのカートは、標準価格、標準配送レート、そして有効なすべての支払い方法に静かに戻ります。部分点はありません。Scriptは動くか、動かないかだけです。そして6月30日以降は、動きません。

ヒント: Shopify adminでSettings → Checkout → Customizations Reportを開いてください。ストア内のすべての有効なScript、その内容、推奨されるFunctionの置き換えタイプが一覧表示されます。まずはそこから始めましょう。

Functions と Scripts: 実際に何が変わったのか

項目

Shopify Scripts(非推奨)

Shopify Functions(置き換え)

言語

Ruby DSL(Shopify専用)

Rust、JavaScript、TypeScript

ランタイム

Shopifyインフラ上のサンドボックス化されたRuby

WebAssembly(WASM)— 5ms未満で実行

利用プラン

Plusのみ

全プラン(カスタムアプリにはPlusが必要。公開アプリは利用可能)

エディタ

Admin内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — 直接編集

Git向け — 完全なバージョン管理

テスト

チェックアウトで手動テスト

shopify app devを使ったローカル開発、プレビューリンク

デプロイ

Adminで「Save」をクリック

ターミナルからshopify app deploy

対象

Line items、配送、支払い

Discounts、Cart Transform、Validation、Delivery Customization、Payment Customization、Order Routing、Fulfillment Constraints など

アーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを少し調整する」ものでした。Functionsは「実際のアプリを書く、それをバージョン管理する、ローカルでテストする、ちゃんとしたCIパイプラインでデプロイする」ものです。入口はかなり急です。ですが、今後の見通しでチェックアウトロジックに関する最後の移行でもあります。Functionsは、Scriptのようなつなぎではなく、Shopifyが長期的にコミットしている仕組みです。

あなたのScriptsを適切なFunctionタイプに対応付ける

今ある各Scriptは、必ず1つのFunction APIに対応します。モニターに貼っておきたい対応表はこちらです。

旧Scriptタイプ

やっていたこと

新しいFunction API

Functionターゲット

Line Item Script

特定の商品/顧客/カート条件に割引を適用

Cart & Checkout Discounts API

cart.lines.discounts.generate.run

Shipping Script(割引)

カートルールに基づく送料無料/送料割引

Cart & Checkout Discounts API

cart.delivery-options.discounts.generate.run

Shipping Script(非表示/名称変更/並べ替え)

$X以上の配送レートを非表示にする、「Standard」を「$50以上で無料」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを非表示、$500超で代金引換を非表示、支払い方法を並べ替え

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(まれ)

商品のバンドル、Line itemの入れ替え

Cart Transform API

cart.transform.run

チェックアウト停止Script

SKUの組み合わせが無効ならカートを拒否

Cart & Checkout Validation API

cart.validations.generate.run

Scriptが10個あるなら、実際には3〜5個のFunctionになる可能性が高いです。複数のScriptが、よりきれいな分岐ロジックを持つ1つのFunctionにまとまることが多いからです。


Shopify Functions unifying discounts, delivery, and payment customizations

前提条件: ローカル開発環境を整える

Functionのひな形を作る前に、ローカルに3つのものをインストールしておく必要があります。ターミナルで次の確認を行ってください。

1. Node.js 18+

node --version
# Must be >= 18.0.0
node --version
# Must be >= 18.0.0

古い場合は、nvmで入れるか、nodejs.orgからダウンロードしてください。

2. Shopify CLI 3+

npm install -g @shopify/cli@latest
shopify version
# Should output 3.x or higher
npm install -g @shopify/cli@latest
shopify version
# Should output 3.x or higher

3. Rust toolchain(RustでFunctionsを書く場合のみ)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasip1
cargo --version
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasip1
cargo --version

JavaScript FunctionsにはRustは不要です。チームで1つの言語を選び、それに統一してください。両方を混ぜると保守コストが増えます。

4. 開発用ストア

Partnerダッシュボードにログインして新しいdevelopment storeを作成するか、既存のものを使ってください。本番へ昇格する前に、まずこのストアへFunctionsをデプロイします。

最初のFunctionをひな形から作る

CLIが大半の雛形を作ってくれます。任意のディレクトリ内で:

# Create a new Shopify app (skip if you already have one)
shopify app init my-checkout-functions
cd my-checkout-functions

# Generate a Function extension
shopify app generate extension
# Create a new Shopify app (skip if you already have one)
shopify app init my-checkout-functions
cd my-checkout-functions

# Generate a Function extension
shopify app generate extension

CLIがプロンプトに沿って案内してくれます。割引Functionなら、次を選びます:

  • Type: Function

  • Template: discount(または cart_checkout_validationdelivery_customizationpayment_customization など)

  • Language: Rust または JavaScript

  • Name: volume-discount-fn のような名前

これにより、extensions/volume-discount-fn/ が作成され、以下が含まれます:

extensions/volume-discount-fn/
├── shopify.extension.toml      # Function config targets, build, version
├── src/
├── cart_lines_discounts_generate_run.graphql   # Input query
└── cart_lines_discounts_generate_run.rs        # Function logic
├── Cargo.toml                  # Rust dependencies (Rust only)
└── README.md
extensions/volume-discount-fn/
├── shopify.extension.toml      # Function config targets, build, version
├── src/
├── cart_lines_discounts_generate_run.graphql   # Input query
└── cart_lines_discounts_generate_run.rs        # Function logic
├── Cargo.toml                  # Rust dependencies (Rust only)
└── README.md

継続的に編集する3つのファイルは、.toml(設定)、.graphql(入力)、.rs / .js(ロジック)です。それだけです。

チュートリアル1: Line Item Scriptを置き換える(数量割引)

たとえば、旧Scriptが、特定コレクションの合計数量が5個以上なら注文小計を10%オフにしていたとします。これがFunction版です。

ステップ1.1: 設定(shopify.extension.toml

api_version = "2026-01"

[[extensions]]
name = "volume-discount-fn"
handle = "volume-discount-fn"
type = "function"

[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"

[extensions.build]
command = "cargo build --target=wasm32-wasip1 --release"
path = "target/wasm32-wasip1/release/volume-discount-fn.wasm"
watch = ["src/**/*.rs"]
api_version = "2026-01"

[[extensions]]
name = "volume-discount-fn"
handle = "volume-discount-fn"
type = "function"

[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"

[extensions.build]
command = "cargo build --target=wasm32-wasip1 --release"
path = "target/wasm32-wasip1/release/volume-discount-fn.wasm"
watch = ["src/**/*.rs"]

ステップ1.2: 入力クエリ(src/cart_lines_discounts_generate_run.graphql

query Input {
  cart {
    lines {
      id
      quantity
      cost {
        subtotalAmount {
          amount
        }
      }
      merchandise {
        ... on ProductVariant {
          product {
            inAnyCollection(ids: ["gid://shopify/Collection/123456789"])
          }
        }
      }
    }
  }
  discount {
    discountClasses
  }
}
query Input {
  cart {
    lines {
      id
      quantity
      cost {
        subtotalAmount {
          amount
        }
      }
      merchandise {
        ... on ProductVariant {
          product {
            inAnyCollection(ids: ["gid://shopify/Collection/123456789"])
          }
        }
      }
    }
  }
  discount {
    discountClasses
  }
}

ヒント: Functionsが見られるのは、クエリしたデータだけです。GraphQLは最小限に保ってください。省いたフィールドが多いほど、実行は速く、コストも低くなります。

ステップ1.3: ロジック(src/cart_lines_discounts_generate_run.rs

use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function]
fn cart_lines_discounts_generate_run(
    input: schema::cart_lines_discounts_generate_run::Input,
) -> Result<schema::CartLinesDiscountsGenerateRunResult> {
    // Bail if discount class doesn't match
    let has_order_discount = input
        .discount()
        .discount_classes()
        .contains(&schema::DiscountClass::Order);

    if !has_order_discount {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Sum quantities of items in the target collection
    let qualifying_qty: i64 = input
        .cart()
        .lines()
        .iter()
        .filter(|line| {
            if let schema::Merchandise::ProductVariant(v) = line.merchandise() {
                *v.product().in_any_collection()
            } else {
                false
            }
        })
        .map(|line| *line.quantity())
        .sum();

    if qualifying_qty < 5 {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Apply 10% off the order subtotal
    Ok(schema::CartLinesDiscountsGenerateRunResult {
        operations: vec![schema::CartOperation::OrderDiscountsAdd(
            schema::OrderDiscountsAddOperation {
                selection_strategy: schema::OrderDiscountSelectionStrategy::First,
                candidates: vec![schema::OrderDiscountCandidate {
                    targets: vec![schema::OrderDiscountCandidateTarget::OrderSubtotal(
                        schema::OrderSubtotalTarget {
                            excluded_cart_line_ids: vec![],
                        },
                    )],
                    message: Some("Volume discount: 10% off".to_string()),
                    value: schema::OrderDiscountCandidateValue::Percentage(
                        schema::Percentage { value: Decimal(10.0) }
                    ),
                    conditions: None,
                    associated_discount_code: None,
                }],
            },
        )],
    })
}
use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function]
fn cart_lines_discounts_generate_run(
    input: schema::cart_lines_discounts_generate_run::Input,
) -> Result<schema::CartLinesDiscountsGenerateRunResult> {
    // Bail if discount class doesn't match
    let has_order_discount = input
        .discount()
        .discount_classes()
        .contains(&schema::DiscountClass::Order);

    if !has_order_discount {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Sum quantities of items in the target collection
    let qualifying_qty: i64 = input
        .cart()
        .lines()
        .iter()
        .filter(|line| {
            if let schema::Merchandise::ProductVariant(v) = line.merchandise() {
                *v.product().in_any_collection()
            } else {
                false
            }
        })
        .map(|line| *line.quantity())
        .sum();

    if qualifying_qty < 5 {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Apply 10% off the order subtotal
    Ok(schema::CartLinesDiscountsGenerateRunResult {
        operations: vec![schema::CartOperation::OrderDiscountsAdd(
            schema::OrderDiscountsAddOperation {
                selection_strategy: schema::OrderDiscountSelectionStrategy::First,
                candidates: vec![schema::OrderDiscountCandidate {
                    targets: vec![schema::OrderDiscountCandidateTarget::OrderSubtotal(
                        schema::OrderSubtotalTarget {
                            excluded_cart_line_ids: vec![],
                        },
                    )],
                    message: Some("Volume discount: 10% off".to_string()),
                    value: schema::OrderDiscountCandidateValue::Percentage(
                        schema::Percentage { value: Decimal(10.0) }
                    ),
                    conditions: None,
                    associated_discount_code: None,
                }],
            },
        )],
    })
}

ステップ1.4: テスト、デプロイ、有効化

# Local development with hot reload
shopify app dev

# When ready, deploy
shopify app deploy

# In the GraphiQL panel that opens (press `g` in the dev terminal),
# create the automatic discount that uses your Function:
# Local development with hot reload
shopify app dev

# When ready, deploy
shopify app deploy

# In the GraphiQL panel that opens (press `g` in the dev terminal),
# create the automatic discount that uses your Function:
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Volume Discount (5+ collection items)"
      functionHandle: "volume-discount-fn"
      discountClasses: [ORDER]
      startsAt: "2026-04-16T00:00:00Z"
    }
  ) {
    automaticAppDiscount { discountId }
    userErrors { field message }
  }
}
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Volume Discount (5+ collection items)"
      functionHandle: "volume-discount-fn"
      discountClasses: [ORDER]
      startsAt: "2026-04-16T00:00:00Z"
    }
  ) {
    automaticAppDiscount { discountId }
    userErrors { field message }
  }
}

これで完了です。Functionは本番稼働し、バージョン管理され、旧Scriptを完全に置き換えます。

チュートリアル2: Shipping Scriptを置き換える(カートしきい値超過で方法を非表示)

よくあるScript: 「カート小計が$500を超えたら、翌日配送のような高額配送を防ぐためにExpress Shippingを非表示にする」。これがDelivery Customization Function版です。

ステップ2.1: ひな形作成

shopify app generate extension --template delivery_customization --name hide-express-fn
shopify app generate extension --template delivery_customization --name hide-express-fn

ステップ2.2: 入力クエリ(src/run.graphql

query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    deliveryGroups {
      deliveryOptions {
        handle
        title
      }
    }
  }
}
query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    deliveryGroups {
      deliveryOptions {
        handle
        title
      }
    }
  }
}

ステップ2.3: ロジック(src/run.js — JavaScript版)

// @ts-check
/**
 * @typedef {import("../generated/api").RunInput} RunInput
 * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
 */

const NO_CHANGES = { operations: [] };
const THRESHOLD = 500.0;
const HIDE_TITLES = ["Express", "Overnight"];

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
  if (subtotal < THRESHOLD) return NO_CHANGES;

  const operations = input.cart.deliveryGroups.flatMap((group) =>
    group.deliveryOptions
      .filter((opt) => HIDE_TITLES.some((t) => opt.title.includes(t)))
      .map((opt) => ({
        hide: { deliveryOptionHandle: opt.handle },
      }))
  );

  return { operations };
}
// @ts-check
/**
 * @typedef {import("../generated/api").RunInput} RunInput
 * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
 */

const NO_CHANGES = { operations: [] };
const THRESHOLD = 500.0;
const HIDE_TITLES = ["Express", "Overnight"];

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
  if (subtotal < THRESHOLD) return NO_CHANGES;

  const operations = input.cart.deliveryGroups.flatMap((group) =>
    group.deliveryOptions
      .filter((opt) => HIDE_TITLES.some((t) => opt.title.includes(t)))
      .map((opt) => ({
        hide: { deliveryOptionHandle: opt.handle },
      }))
  );

  return { operations };
}

ステップ2.4: Adminから有効化(GraphQL不要)

Delivery Customizationsには、組み込みのAdmin UIがあります。shopify app deployの後に:

  1. Settings → Shipping and deliveryへ移動

  2. 下部のCustomizationsセクションまでスクロール

  3. Add customizationをクリック → あなたのFunctionを選択

  4. 保存

非表示ルールは本番で有効になります。mutationは不要です。


Shopify delivery customization Function hiding shipping option at checkout

チュートリアル3: Payment Scriptを置き換える(B2B向けにCODを非表示)

旧Script: 「'B2B'タグの付いた顧客には代金引換を非表示にする」。これがPayment Customization版です。

ステップ3.1: ひな形作成

shopify app generate extension --template payment_customization --name hide-cod-b2b-fn
shopify app generate extension --template payment_customization --name hide-cod-b2b-fn

ステップ3.2: 入力クエリ

query Input {
  cart {
    buyerIdentity {
      customer {
        hasTags(tags: [{ tag: "B2B" }]) {
          tag
          hasTag
        }
      }
    }
  }
  paymentMethods {
    id
    name
  }
}
query Input {
  cart {
    buyerIdentity {
      customer {
        hasTags(tags: [{ tag: "B2B" }]) {
          tag
          hasTag
        }
      }
    }
  }
  paymentMethods {
    id
    name
  }
}

ステップ3.3: ロジック(src/run.js

const NO_CHANGES = { operations: [] };

export function run(input) {
  const tagCheck = input.cart?.buyerIdentity?.customer?.hasTags?.[0];
  const isB2B = tagCheck?.hasTag === true;
  if (!isB2B) return NO_CHANGES;

  const codMethod = input.paymentMethods.find((pm) =>
    pm.name.toLowerCase().includes("cash on delivery")
  );
  if (!codMethod) return NO_CHANGES;

  return {
    operations: [{ hide: { paymentMethodId: codMethod.id } }],
  };
}
const NO_CHANGES = { operations: [] };

export function run(input) {
  const tagCheck = input.cart?.buyerIdentity?.customer?.hasTags?.[0];
  const isB2B = tagCheck?.hasTag === true;
  if (!isB2B) return NO_CHANGES;

  const codMethod = input.paymentMethods.find((pm) =>
    pm.name.toLowerCase().includes("cash on delivery")
  );
  if (!codMethod) return NO_CHANGES;

  return {
    operations: [{ hide: { paymentMethodId: codMethod.id } }],
  };
}

ステップ3.4: 有効化

Payment Customizationsにも、Settings → Payments → Customizationsの下にAdmin UIがあります。配送と同じ流れです。Functionを選び、保存して完了です。

ここまで読んだあなたへ — ポストパーチェスについて一言

これはRevizeのブログなので、少しだけ補足します。Revizeは、Functionsでは触れられない部分を扱います。注文後に、顧客が商品を追加したい、サイズを交換したい、配送先住所を修正したい、忘れていた割引を適用したい——そんなときです。Functionsはチェックアウト時にあり、Revizeはその後にあります。 Functionsはカート内で何が許可されるかを決め、Revizeは返金して作り直すことなく、その後に顧客とサポートチームが注文を編集できるようにします。これは2種類のストアにとって重要です。手動編集が回らなくなるほど注文量が多いストア(もちろんPlus運用のストア、そして高スループットなAdvancedストアもそうです)と、ブランドの核が顧客体験であるストアです。そこでは「すみません、変更できません」というメールは、再購入を遠ざける原因になります。

移行計画がScripts → Functionsまでしかカバーしておらず、ポストパーチェスの注文編集をまだ整理していないなら、3週間ほどで次の「なんでこんなに難しいの?」の壁にぶつかります。先日公開した注文管理ガイドに、チェックアウト後の完全な運用手順をまとめています。

移行の話に戻りましょう。

テスト戦略: タグ付き顧客パターン

Functionsには、Adminで切り替えられる「ドラフトモード」がありません。プロ向けのやり方は、新しいFunctionを顧客タグで制御し、旧Scriptと新Functionを並行稼働させ、タグ付きユーザーに対して同一の結果を出すことを確認してから、切り替える方法です。

ステップ1: テストユーザーにタグを付ける

Customersで、社内アカウント2〜3件にFN-TESTERタグを追加します。

ステップ2: タグの有無でFunctionを分岐させる

// At the top of your run function
let is_tester = input
    .cart()
    .buyer_identity()
    .and_then(|bi| bi.customer())
    .map(|c| c.has_any_tag())
    .unwrap_or(&false);

if !*is_tester {
    return Ok(default_result);  // Fall through to existing Script
}

// New Function logic only runs for tagged users
// At the top of your run function
let is_tester = input
    .cart()
    .buyer_identity()
    .and_then(|bi| bi.customer())
    .map(|c| c.has_any_tag())
    .unwrap_or(&false);

if !*is_tester {
    return Ok(default_result);  // Fall through to existing Script
}

// New Function logic only runs for tagged users

ステップ3: 入力クエリにhasAnyTagを追加する

cart {
  buyerIdentity {
    customer {
      hasAnyTag(tags: ["FN-TESTER"])
    }
  }
}
cart {
  buyerIdentity {
    customer {
      hasAnyTag(tags: ["FN-TESTER"])
    }
  }
}

ステップ4: チェックアウトで確認する

タグ付きユーザーとしてログインし、チェックアウトを進めてFunctionが実行されることを確認します。タグなしユーザーでログインし、旧Scriptがまだ実行されることを確認します。数日間同等性が保てたら、タグチェックを外してFunctionを全員に適用します。

ステップ5: 旧Scriptを非公開にする

Apps → Script Editor → [Your Script] → Unpublishへ移動します。非公開にしたら、Functionが唯一の正です。

実際にスケールするデプロイワークフロー

開発者のノートPCから永遠にデプロイし続けないでください。1つか2つScriptを移行したら、ちゃんとしたCIパイプラインを組みましょう。

最小実行可能なワークフロー

# .github/workflows/deploy-functions.yml
name: Deploy Shopify Functions
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - uses: dtolnay/rust-toolchain@stable
        with: { targets: wasm32-wasip1 }
      - run: npm install -g @shopify/cli@latest
      - run: shopify app deploy --force
        env:
          SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
# .github/workflows/deploy-functions.yml
name: Deploy Shopify Functions
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - uses: dtolnay/rust-toolchain@stable
        with: { targets: wasm32-wasip1 }
      - run: npm install -g @shopify/cli@latest
      - run: shopify app deploy --force
        env:
          SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}

PartnerダッシュボードのSettings → Tokensからパートナートークンを生成します。これでmainへのマージごとにFunctionsが出荷されます。「Mikeがデプロイしたの?」というSlackのやり取りはもう不要です。

バージョン管理とロールバック

shopify app deployはバージョン付きスナップショットを作成します。ロールバックするには:

shopify app versions list
shopify app release --version <previous-version-id

shopify app versions list
shopify app release --version <previous-version-id

Scriptsの場合は「古いコードを覚えておいて、貼り戻す」しかなかったので、これはまるで別世界です。


Shopify Functions deployment pipeline across local staging and production environments

多くのチームが間違えること

この1年、Plusマーチャントの移行を支援してきて、同じ5つのミスが繰り返し出てきました。

1. Functionsを1対1のScript移植として扱う。 そうではありません。1つのFunctionで、3つのScriptをよりきれいな分岐で置き換えられることがあります。書き換える前に、Scriptsをシステムとして棚卸ししてください。

2. 読み取りスコープを忘れる。 多くのFunctionsにはread_customersread_orderswrite_discountsが必要です。shopify.app.tomlscopesに追加し、アプリを再承認してください。そうしないと入力クエリはnullになります。

3. パリティテストなしで、タグなし顧客にFunctionsを実行する。 コードが正しく見えても、エッジケース(空カート、ギフトカード、ストアクレジット、B2Bの下書きなど)で問題が表面化します。タグで段階展開すれば2日で済み、P1障害を回避できます。

4. Customizations Reportを飛ばす。 実際に何が動いているかを知るための、最も優れた棚卸し資料です。記憶を頼りに移行せず、レポートを元に移行してください。

5. コレクションIDや顧客タグをハードコードする。 マーチャントが設定できる値が必要なら、metafields経由でFunction設定を使ってください。CLIはmetafield連携の設定もひな形化できます。ShopifyのFunction設定ドキュメントを参照してください。

今後75日間の移行チェックリスト

6月30日まで落ち着いて到達するための、現実的な週ごとの計画です。

アクション

第1週(今週)

Customizations Reportを取得。すべてのScriptを棚卸し。Function化・公開アプリ利用・削除のどれかを決める。

第2〜3週

ローカル開発環境を整える。最初のFunctionをひな形から作る。最も簡単なScript(たいていは支払い非表示ルール)を移行する。

第4〜6週

割引Scriptを移行する。Discounts APIが最も豊富なので、ここが一番時間がかかります。タグテストを徹底してください。

第7〜8週

配送/配送方法Scriptを移行する。Delivery CustomizationsをAdminから有効化する。

第9〜10週

CIパイプラインを構築する。デプロイを開発者のノートPCから完全に移す。

第11週 (6月中旬)

最終パリティ確認。すべてのScriptを非公開にする。2週間はFunctionsだけでストアを運用する。

6月30日

移行期限当日。早めに終えているので、何も壊れません。

今週始めれば、余裕があります。6月に始めるなら、余裕はありません。


Week-by-week migration roadmap from Shopify Scripts to Functions

よくある質問

Functionsを使うにはShopify Plusが必要ですか?

カスタムFunctionsにはShopify Plusが必要ですが、公開アプリのFunctionsは全プランで使えます。 Plusでない場合は2つの道があります。Shopify App StoreからFunctionを提供する公開アプリを入れるか、Plusにアップグレードして独自のカスタムFunctionsを書くかです。Scriptを使っていた大きなマーチャントの多くはすでにPlusだったため、実務上はあまり変わりません。

FunctionsはTypeScriptで書けますか?

はい — TypeScriptは完全にサポートされており、CLIがひな形も作ってくれます。 shopify app generate extensionを実行して「JavaScript」を選ぶと、生成されたプロジェクトにはimport("../generated/api")からの型定義が含まれます。必要ならファイルを.tsに変えてtsconfig.jsonを追加できます。コンパイル後の出力(WASM)は、ソース言語に関係なく同じです。

FunctionsはScriptsと比べてどれくらい速いですか?

Functionsは通常5ms未満で実行され、Ruby Scriptsよりかなり高速です。 WebAssemblyにコンパイルされ、タイトなランタイムで動作するため、Shopifyは5msの実行予算を課しています。Functionがこれを超えると、その操作は破棄され、Functionは何も返しません。実際には、よく書かれたFunctionなら1〜2msで済みます。パフォーマンスの上限はScriptsよりはるかに高いです。

Functionは外部APIを呼び出せますか?

いいえ — Functionsはネットワークリクエストを行えません。 入力されたカートデータを処理して、操作を返すだけの純粋な計算です。外部データ(CRM参照、リアルタイム在庫チェックなど)が必要なら、あらかじめmetafieldsに保存しておくか、別の面(App Proxy、webhooks、バックエンド参照付きのCart Transform)を使う必要があります。これは、単に移植するのではなく設計を見直す必要がある、最も一般的な理由です。

Cart TransformとDiscountsの違いは何ですか?

Discountsは価格を変え、Cart Transformはカートの中身を変えます。 10%オフ、送料無料、BOGOを適用するならDiscounts APIを使います。2つの商品を1つのLine itemにまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptの多くは両方を混ぜていました。移行時は、2つのFunctionに分けてください。

デベロップメントストアなしで、Functionをローカルテストするには?

cargo test(Rust)またはnpm test(JS)でユニットテストはできますが、完全な統合テストにはデベロップメントストアが必要です。 CLIにはshopify app function runがあり、サンプル入力ファイルに対してFunctionを実行できます。高速な反復には便利です。ただし、チェックアウト動作をエンドツーエンドで確認するには、実際のカートを持つ本物のストアが必要です。

同じタイプのFunctionを複数持てますか?

はい — Shopifyはターゲットごとに複数のFunctionsをサポートしており、決定論的な順序で実行されます。 Discountsでは、順序はShopifyの割引スタッキングルールに従います。Delivery CustomizationsとPayment Customizationsでは、Functionsを連結できますが、各出力が次へ渡されます。多くのチームはシンプルさのために、タイプごとに1つのFunctionを使います。

Functionをデプロイしたら、Scriptはどうなりますか?

Script EditorのApps → Script EditorからScriptを非公開にするまでは、両方並行して動きます。 これは意図的です。並行実行のテスト期間を確保するためです。Functionが正しく動くことを確認したら、手動でScriptを非公開にします。2026年6月30日以降は、非公開にしたかどうかに関係なく、すべてのScriptsが実行停止します。

この移行はSEOやテーマに影響しますか?

いいえ — Functionsはチェックアウト時にサーバー側で実行され、テーマや商品ページには一切触れません。 チェックアウトで割引、配送オプション、支払い方法を変更するだけです。ストアフロント、商品テンプレート、SEOにはまったく影響しません。

Input.line_items をカスタムプロパティ付きで使うScriptはどう移行しますか?

カスタムプロパティは、GraphQL入力内のカートライン上の attribute フィールドから取得できます。 lines の選択内に attribute(key: "your-key") { value } を追加してください。Functionは、ScriptsがLine itemプロパティを読んでいたのと同じように読み取ります。Rubyのメソッド呼び出しではなく、GraphQLを通すだけです。

分析や注文タグはどうなりますか?Functionsはデータを書き込めますか?

Functionsは注文タグを書いたり、自分でwebhookを発火させたりはできません。現在のカートに対する操作を返すだけです。 タグ付けや後続ワークフローには、注文作成イベントでトリガーされるShopify Flowを使ってください。多くのマーチャントは、Function(割引用)とFlow(注文に「VOLUME-DISCOUNT-APPLIED」をタグ付けする用)を組み合わせています。

独自のFunctionを作らずに、インストールできる公開アプリはありますか?

はい — Shopify App Storeには、一般的な用途向けにFunctionsを包んだアプリが多数あります。 「discount function」「delivery customization」「payment customization」で検索してください。単純な用途(数量割引、タグで支払い方法を非表示、X以上で送料無料)なら、既存アプリで数日分の開発工数を節約できるかもしれません。本当に自社固有のロジックだけ、カスタムFunctionにしてください。

6月30日の期限に間に合わなかったら?

Scriptは実行停止します。フォールバックも、猶予期間も、延長もありません。 Scriptがやっていたこと(割引、非表示の配送レート、ブロックされた支払い方法など)は、7月1日0時UTCにデフォルト動作へ戻ります。そのロジックに事業が依存しているなら、かなり前倒しで本番稼働する計画を立ててください。特にパリティテストがあるため、移行には多くのチームが見積もるより時間がかかります。

Script Editorアプリを完全に削除できますか?

2026年6月30日以降ならアンインストールできますが、Shopifyが自動的に削除する可能性が高いです。 Scriptsの実行が止まれば、エディタの役目はありません。移行が完了していれば、今すぐすべてのScriptsを非公開にしてアプリをアンインストールしても構いません。Functionsは独立しています。

今週やること

この記事を読んでタブを閉じて終わりにしないでください。次の7日間で、この4つを実行してください。

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ移動し、エクスポートします。これが移行のバックログです。

2. 開発環境を整える。 Node 18+、Shopify CLI、Rust(必要なら)をインストールします。shopify versionが動くことを確認してください。所要時間: 30分。

3. 小さなFunctionを1つひな形化して、デベロップメントストアに出す。 一番簡単なScriptを選びます。たいていは支払い非表示ルールです。それを最後まで移行してください。本番に出さなくても、ツールチェーンの検証にはなります。

4. 次の8週間のカレンダー時間を確保する。 移行は、スプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、定期枠を確保し、リリース作業として扱ってください。

私たちが見てきた移行チームでは、パターンは一貫しています。2週間の停滞、3週間の実作業、1週間の片付け。つまり6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先送りするためではなく、コードレビューとQAに使ってください。

そしてチェックアウトロジックが整ったら、次に多くのチームが取り組むのがポストパーチェスです。顧客住所の変更、交換、注文後の割引追加などです。そこもロードマップにあるなら(高注文量のPlus運用でも、CX重視のAdvancedストアでも、そうあるべきです)、RevizeはShopify App Storeにあり、ここで作るすべてのFunctionと併用できます。

リソース

関連記事

2026年4月16日です。昨日——4月15日——は、ShopifyがScript Editorを完全にロックした日でした。新しいScriptを作成したり公開したりすることはもうできません。実行停止は75日後、2026年6月30日に訪れます。

もしあなたがShopify Plusの開発者——あるいはPlusストアを運営する代理店——で、この移行をここ12か月ずっと「次のスプリント」に先送りしてきたなら、問題があります。しかも「まあ直せるなら直す」程度ではありません。「7月1日深夜にチェックアウトが壊れる」レベルの問題です。多くのPlusストアでは、何年にもわたって5〜20個のScriptsが蓄積されており、それぞれが、誰も書いた覚えのない割引ルール、配送方法の非表示、支払い方法の制御を静かに担っています。

このガイドは、1月にあればよかったと思っていた技術的な移行マニュアルです。 単なる戦略ではなく、実際のコードまで扱います。読み終えるころには、Shopify CLIでFunctionのひな形を作成し、割引・配送カスタマイズ・支払いカスタマイズのためのRustまたはJavaScriptのロジックを書き、顧客の一部にタグを付けて安全にテストし、既存のチェックアウトを壊さずに本番へ出す方法がわかるはずです。

あなたのストアからScriptsを外し、Functionsを導入しましょう。


Developer migrating Shopify Scripts to Shopify Functions modules

簡潔な答え: Scripts → Functions を60秒で理解する

1段落でいうと: Shopify Scripts(Script Editor内で書くRubyコード、Plus限定)は、Shopify Functions(RustまたはJavaScriptで書かれたWebAssemblyモジュール、全プランで利用可能)に置き換えられています。shopify app generate extensionでFunctionのひな形を作り、必要なカートデータを取得するrun.graphqlクエリを書き、割引・非表示の配送方法などの操作を返すrun.rsまたはrun.jsファイルを書き、shopify app deployでデプロイしてからAdminまたはGraphQL mutationで有効化します。Functionsはコンパイル済みWASMとして5ms未満のレイテンシで実行され、全プランで動作し、今後Shopifyがサポートする唯一のカスタマイズ手段です。

6月30日に実際に何が変わるのか

コードに触る前に、日付を整理しましょう。2つあり、どちらも重要です。

日付

起こること

あなたの対応

2026年4月15日 (経過済み)

Script Editorが読み取り専用に。新しいScriptの作成不可。既存Scriptの編集不可。

既存Scriptsはまだ実行されます。今すぐ移行するか、ロジックを凍結してください。

2026年6月30日

すべてのShopify Scriptsが実行停止します。以上です。

この日までにFunctionの置き換えを本番稼働させておく必要があります。

移行は二択です。6月30日までにFunctionがデプロイされ、チェックアウトが動き続けるか、そうでないか。そうでなければ、影響を受けるすべてのカートは、標準価格、標準配送レート、そして有効なすべての支払い方法に静かに戻ります。部分点はありません。Scriptは動くか、動かないかだけです。そして6月30日以降は、動きません。

ヒント: Shopify adminでSettings → Checkout → Customizations Reportを開いてください。ストア内のすべての有効なScript、その内容、推奨されるFunctionの置き換えタイプが一覧表示されます。まずはそこから始めましょう。

Functions と Scripts: 実際に何が変わったのか

項目

Shopify Scripts(非推奨)

Shopify Functions(置き換え)

言語

Ruby DSL(Shopify専用)

Rust、JavaScript、TypeScript

ランタイム

Shopifyインフラ上のサンドボックス化されたRuby

WebAssembly(WASM)— 5ms未満で実行

利用プラン

Plusのみ

全プラン(カスタムアプリにはPlusが必要。公開アプリは利用可能)

エディタ

Admin内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — 直接編集

Git向け — 完全なバージョン管理

テスト

チェックアウトで手動テスト

shopify app devを使ったローカル開発、プレビューリンク

デプロイ

Adminで「Save」をクリック

ターミナルからshopify app deploy

対象

Line items、配送、支払い

Discounts、Cart Transform、Validation、Delivery Customization、Payment Customization、Order Routing、Fulfillment Constraints など

アーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを少し調整する」ものでした。Functionsは「実際のアプリを書く、それをバージョン管理する、ローカルでテストする、ちゃんとしたCIパイプラインでデプロイする」ものです。入口はかなり急です。ですが、今後の見通しでチェックアウトロジックに関する最後の移行でもあります。Functionsは、Scriptのようなつなぎではなく、Shopifyが長期的にコミットしている仕組みです。

あなたのScriptsを適切なFunctionタイプに対応付ける

今ある各Scriptは、必ず1つのFunction APIに対応します。モニターに貼っておきたい対応表はこちらです。

旧Scriptタイプ

やっていたこと

新しいFunction API

Functionターゲット

Line Item Script

特定の商品/顧客/カート条件に割引を適用

Cart & Checkout Discounts API

cart.lines.discounts.generate.run

Shipping Script(割引)

カートルールに基づく送料無料/送料割引

Cart & Checkout Discounts API

cart.delivery-options.discounts.generate.run

Shipping Script(非表示/名称変更/並べ替え)

$X以上の配送レートを非表示にする、「Standard」を「$50以上で無料」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを非表示、$500超で代金引換を非表示、支払い方法を並べ替え

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(まれ)

商品のバンドル、Line itemの入れ替え

Cart Transform API

cart.transform.run

チェックアウト停止Script

SKUの組み合わせが無効ならカートを拒否

Cart & Checkout Validation API

cart.validations.generate.run

Scriptが10個あるなら、実際には3〜5個のFunctionになる可能性が高いです。複数のScriptが、よりきれいな分岐ロジックを持つ1つのFunctionにまとまることが多いからです。


Shopify Functions unifying discounts, delivery, and payment customizations

前提条件: ローカル開発環境を整える

Functionのひな形を作る前に、ローカルに3つのものをインストールしておく必要があります。ターミナルで次の確認を行ってください。

1. Node.js 18+

node --version
# Must be >= 18.0.0

古い場合は、nvmで入れるか、nodejs.orgからダウンロードしてください。

2. Shopify CLI 3+

npm install -g @shopify/cli@latest
shopify version
# Should output 3.x or higher

3. Rust toolchain(RustでFunctionsを書く場合のみ)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasip1
cargo --version

JavaScript FunctionsにはRustは不要です。チームで1つの言語を選び、それに統一してください。両方を混ぜると保守コストが増えます。

4. 開発用ストア

Partnerダッシュボードにログインして新しいdevelopment storeを作成するか、既存のものを使ってください。本番へ昇格する前に、まずこのストアへFunctionsをデプロイします。

最初のFunctionをひな形から作る

CLIが大半の雛形を作ってくれます。任意のディレクトリ内で:

# Create a new Shopify app (skip if you already have one)
shopify app init my-checkout-functions
cd my-checkout-functions

# Generate a Function extension
shopify app generate extension

CLIがプロンプトに沿って案内してくれます。割引Functionなら、次を選びます:

  • Type: Function

  • Template: discount(または cart_checkout_validationdelivery_customizationpayment_customization など)

  • Language: Rust または JavaScript

  • Name: volume-discount-fn のような名前

これにより、extensions/volume-discount-fn/ が作成され、以下が含まれます:

extensions/volume-discount-fn/
├── shopify.extension.toml      # Function config targets, build, version
├── src/
├── cart_lines_discounts_generate_run.graphql   # Input query
└── cart_lines_discounts_generate_run.rs        # Function logic
├── Cargo.toml                  # Rust dependencies (Rust only)
└── README.md

継続的に編集する3つのファイルは、.toml(設定)、.graphql(入力)、.rs / .js(ロジック)です。それだけです。

チュートリアル1: Line Item Scriptを置き換える(数量割引)

たとえば、旧Scriptが、特定コレクションの合計数量が5個以上なら注文小計を10%オフにしていたとします。これがFunction版です。

ステップ1.1: 設定(shopify.extension.toml

api_version = "2026-01"

[[extensions]]
name = "volume-discount-fn"
handle = "volume-discount-fn"
type = "function"

[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"

[extensions.build]
command = "cargo build --target=wasm32-wasip1 --release"
path = "target/wasm32-wasip1/release/volume-discount-fn.wasm"
watch = ["src/**/*.rs"]

ステップ1.2: 入力クエリ(src/cart_lines_discounts_generate_run.graphql

query Input {
  cart {
    lines {
      id
      quantity
      cost {
        subtotalAmount {
          amount
        }
      }
      merchandise {
        ... on ProductVariant {
          product {
            inAnyCollection(ids: ["gid://shopify/Collection/123456789"])
          }
        }
      }
    }
  }
  discount {
    discountClasses
  }
}

ヒント: Functionsが見られるのは、クエリしたデータだけです。GraphQLは最小限に保ってください。省いたフィールドが多いほど、実行は速く、コストも低くなります。

ステップ1.3: ロジック(src/cart_lines_discounts_generate_run.rs

use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function]
fn cart_lines_discounts_generate_run(
    input: schema::cart_lines_discounts_generate_run::Input,
) -> Result<schema::CartLinesDiscountsGenerateRunResult> {
    // Bail if discount class doesn't match
    let has_order_discount = input
        .discount()
        .discount_classes()
        .contains(&schema::DiscountClass::Order);

    if !has_order_discount {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Sum quantities of items in the target collection
    let qualifying_qty: i64 = input
        .cart()
        .lines()
        .iter()
        .filter(|line| {
            if let schema::Merchandise::ProductVariant(v) = line.merchandise() {
                *v.product().in_any_collection()
            } else {
                false
            }
        })
        .map(|line| *line.quantity())
        .sum();

    if qualifying_qty < 5 {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Apply 10% off the order subtotal
    Ok(schema::CartLinesDiscountsGenerateRunResult {
        operations: vec![schema::CartOperation::OrderDiscountsAdd(
            schema::OrderDiscountsAddOperation {
                selection_strategy: schema::OrderDiscountSelectionStrategy::First,
                candidates: vec![schema::OrderDiscountCandidate {
                    targets: vec![schema::OrderDiscountCandidateTarget::OrderSubtotal(
                        schema::OrderSubtotalTarget {
                            excluded_cart_line_ids: vec![],
                        },
                    )],
                    message: Some("Volume discount: 10% off".to_string()),
                    value: schema::OrderDiscountCandidateValue::Percentage(
                        schema::Percentage { value: Decimal(10.0) }
                    ),
                    conditions: None,
                    associated_discount_code: None,
                }],
            },
        )],
    })
}

ステップ1.4: テスト、デプロイ、有効化

# Local development with hot reload
shopify app dev

# When ready, deploy
shopify app deploy

# In the GraphiQL panel that opens (press `g` in the dev terminal),
# create the automatic discount that uses your Function:
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Volume Discount (5+ collection items)"
      functionHandle: "volume-discount-fn"
      discountClasses: [ORDER]
      startsAt: "2026-04-16T00:00:00Z"
    }
  ) {
    automaticAppDiscount { discountId }
    userErrors { field message }
  }
}

これで完了です。Functionは本番稼働し、バージョン管理され、旧Scriptを完全に置き換えます。

チュートリアル2: Shipping Scriptを置き換える(カートしきい値超過で方法を非表示)

よくあるScript: 「カート小計が$500を超えたら、翌日配送のような高額配送を防ぐためにExpress Shippingを非表示にする」。これがDelivery Customization Function版です。

ステップ2.1: ひな形作成

shopify app generate extension --template delivery_customization --name hide-express-fn

ステップ2.2: 入力クエリ(src/run.graphql

query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    deliveryGroups {
      deliveryOptions {
        handle
        title
      }
    }
  }
}

ステップ2.3: ロジック(src/run.js — JavaScript版)

// @ts-check
/**
 * @typedef {import("../generated/api").RunInput} RunInput
 * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
 */

const NO_CHANGES = { operations: [] };
const THRESHOLD = 500.0;
const HIDE_TITLES = ["Express", "Overnight"];

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
  if (subtotal < THRESHOLD) return NO_CHANGES;

  const operations = input.cart.deliveryGroups.flatMap((group) =>
    group.deliveryOptions
      .filter((opt) => HIDE_TITLES.some((t) => opt.title.includes(t)))
      .map((opt) => ({
        hide: { deliveryOptionHandle: opt.handle },
      }))
  );

  return { operations };
}

ステップ2.4: Adminから有効化(GraphQL不要)

Delivery Customizationsには、組み込みのAdmin UIがあります。shopify app deployの後に:

  1. Settings → Shipping and deliveryへ移動

  2. 下部のCustomizationsセクションまでスクロール

  3. Add customizationをクリック → あなたのFunctionを選択

  4. 保存

非表示ルールは本番で有効になります。mutationは不要です。


Shopify delivery customization Function hiding shipping option at checkout

チュートリアル3: Payment Scriptを置き換える(B2B向けにCODを非表示)

旧Script: 「'B2B'タグの付いた顧客には代金引換を非表示にする」。これがPayment Customization版です。

ステップ3.1: ひな形作成

shopify app generate extension --template payment_customization --name hide-cod-b2b-fn

ステップ3.2: 入力クエリ

query Input {
  cart {
    buyerIdentity {
      customer {
        hasTags(tags: [{ tag: "B2B" }]) {
          tag
          hasTag
        }
      }
    }
  }
  paymentMethods {
    id
    name
  }
}

ステップ3.3: ロジック(src/run.js

const NO_CHANGES = { operations: [] };

export function run(input) {
  const tagCheck = input.cart?.buyerIdentity?.customer?.hasTags?.[0];
  const isB2B = tagCheck?.hasTag === true;
  if (!isB2B) return NO_CHANGES;

  const codMethod = input.paymentMethods.find((pm) =>
    pm.name.toLowerCase().includes("cash on delivery")
  );
  if (!codMethod) return NO_CHANGES;

  return {
    operations: [{ hide: { paymentMethodId: codMethod.id } }],
  };
}

ステップ3.4: 有効化

Payment Customizationsにも、Settings → Payments → Customizationsの下にAdmin UIがあります。配送と同じ流れです。Functionを選び、保存して完了です。

ここまで読んだあなたへ — ポストパーチェスについて一言

これはRevizeのブログなので、少しだけ補足します。Revizeは、Functionsでは触れられない部分を扱います。注文後に、顧客が商品を追加したい、サイズを交換したい、配送先住所を修正したい、忘れていた割引を適用したい——そんなときです。Functionsはチェックアウト時にあり、Revizeはその後にあります。 Functionsはカート内で何が許可されるかを決め、Revizeは返金して作り直すことなく、その後に顧客とサポートチームが注文を編集できるようにします。これは2種類のストアにとって重要です。手動編集が回らなくなるほど注文量が多いストア(もちろんPlus運用のストア、そして高スループットなAdvancedストアもそうです)と、ブランドの核が顧客体験であるストアです。そこでは「すみません、変更できません」というメールは、再購入を遠ざける原因になります。

移行計画がScripts → Functionsまでしかカバーしておらず、ポストパーチェスの注文編集をまだ整理していないなら、3週間ほどで次の「なんでこんなに難しいの?」の壁にぶつかります。先日公開した注文管理ガイドに、チェックアウト後の完全な運用手順をまとめています。

移行の話に戻りましょう。

テスト戦略: タグ付き顧客パターン

Functionsには、Adminで切り替えられる「ドラフトモード」がありません。プロ向けのやり方は、新しいFunctionを顧客タグで制御し、旧Scriptと新Functionを並行稼働させ、タグ付きユーザーに対して同一の結果を出すことを確認してから、切り替える方法です。

ステップ1: テストユーザーにタグを付ける

Customersで、社内アカウント2〜3件にFN-TESTERタグを追加します。

ステップ2: タグの有無でFunctionを分岐させる

// At the top of your run function
let is_tester = input
    .cart()
    .buyer_identity()
    .and_then(|bi| bi.customer())
    .map(|c| c.has_any_tag())
    .unwrap_or(&false);

if !*is_tester {
    return Ok(default_result);  // Fall through to existing Script
}

// New Function logic only runs for tagged users

ステップ3: 入力クエリにhasAnyTagを追加する

cart {
  buyerIdentity {
    customer {
      hasAnyTag(tags: ["FN-TESTER"])
    }
  }
}

ステップ4: チェックアウトで確認する

タグ付きユーザーとしてログインし、チェックアウトを進めてFunctionが実行されることを確認します。タグなしユーザーでログインし、旧Scriptがまだ実行されることを確認します。数日間同等性が保てたら、タグチェックを外してFunctionを全員に適用します。

ステップ5: 旧Scriptを非公開にする

Apps → Script Editor → [Your Script] → Unpublishへ移動します。非公開にしたら、Functionが唯一の正です。

実際にスケールするデプロイワークフロー

開発者のノートPCから永遠にデプロイし続けないでください。1つか2つScriptを移行したら、ちゃんとしたCIパイプラインを組みましょう。

最小実行可能なワークフロー

# .github/workflows/deploy-functions.yml
name: Deploy Shopify Functions
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - uses: dtolnay/rust-toolchain@stable
        with: { targets: wasm32-wasip1 }
      - run: npm install -g @shopify/cli@latest
      - run: shopify app deploy --force
        env:
          SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}

PartnerダッシュボードのSettings → Tokensからパートナートークンを生成します。これでmainへのマージごとにFunctionsが出荷されます。「Mikeがデプロイしたの?」というSlackのやり取りはもう不要です。

バージョン管理とロールバック

shopify app deployはバージョン付きスナップショットを作成します。ロールバックするには:

shopify app versions list
shopify app release --version <previous-version-id

Scriptsの場合は「古いコードを覚えておいて、貼り戻す」しかなかったので、これはまるで別世界です。


Shopify Functions deployment pipeline across local staging and production environments

多くのチームが間違えること

この1年、Plusマーチャントの移行を支援してきて、同じ5つのミスが繰り返し出てきました。

1. Functionsを1対1のScript移植として扱う。 そうではありません。1つのFunctionで、3つのScriptをよりきれいな分岐で置き換えられることがあります。書き換える前に、Scriptsをシステムとして棚卸ししてください。

2. 読み取りスコープを忘れる。 多くのFunctionsにはread_customersread_orderswrite_discountsが必要です。shopify.app.tomlscopesに追加し、アプリを再承認してください。そうしないと入力クエリはnullになります。

3. パリティテストなしで、タグなし顧客にFunctionsを実行する。 コードが正しく見えても、エッジケース(空カート、ギフトカード、ストアクレジット、B2Bの下書きなど)で問題が表面化します。タグで段階展開すれば2日で済み、P1障害を回避できます。

4. Customizations Reportを飛ばす。 実際に何が動いているかを知るための、最も優れた棚卸し資料です。記憶を頼りに移行せず、レポートを元に移行してください。

5. コレクションIDや顧客タグをハードコードする。 マーチャントが設定できる値が必要なら、metafields経由でFunction設定を使ってください。CLIはmetafield連携の設定もひな形化できます。ShopifyのFunction設定ドキュメントを参照してください。

今後75日間の移行チェックリスト

6月30日まで落ち着いて到達するための、現実的な週ごとの計画です。

アクション

第1週(今週)

Customizations Reportを取得。すべてのScriptを棚卸し。Function化・公開アプリ利用・削除のどれかを決める。

第2〜3週

ローカル開発環境を整える。最初のFunctionをひな形から作る。最も簡単なScript(たいていは支払い非表示ルール)を移行する。

第4〜6週

割引Scriptを移行する。Discounts APIが最も豊富なので、ここが一番時間がかかります。タグテストを徹底してください。

第7〜8週

配送/配送方法Scriptを移行する。Delivery CustomizationsをAdminから有効化する。

第9〜10週

CIパイプラインを構築する。デプロイを開発者のノートPCから完全に移す。

第11週 (6月中旬)

最終パリティ確認。すべてのScriptを非公開にする。2週間はFunctionsだけでストアを運用する。

6月30日

移行期限当日。早めに終えているので、何も壊れません。

今週始めれば、余裕があります。6月に始めるなら、余裕はありません。


Week-by-week migration roadmap from Shopify Scripts to Functions

よくある質問

Functionsを使うにはShopify Plusが必要ですか?

カスタムFunctionsにはShopify Plusが必要ですが、公開アプリのFunctionsは全プランで使えます。 Plusでない場合は2つの道があります。Shopify App StoreからFunctionを提供する公開アプリを入れるか、Plusにアップグレードして独自のカスタムFunctionsを書くかです。Scriptを使っていた大きなマーチャントの多くはすでにPlusだったため、実務上はあまり変わりません。

FunctionsはTypeScriptで書けますか?

はい — TypeScriptは完全にサポートされており、CLIがひな形も作ってくれます。 shopify app generate extensionを実行して「JavaScript」を選ぶと、生成されたプロジェクトにはimport("../generated/api")からの型定義が含まれます。必要ならファイルを.tsに変えてtsconfig.jsonを追加できます。コンパイル後の出力(WASM)は、ソース言語に関係なく同じです。

FunctionsはScriptsと比べてどれくらい速いですか?

Functionsは通常5ms未満で実行され、Ruby Scriptsよりかなり高速です。 WebAssemblyにコンパイルされ、タイトなランタイムで動作するため、Shopifyは5msの実行予算を課しています。Functionがこれを超えると、その操作は破棄され、Functionは何も返しません。実際には、よく書かれたFunctionなら1〜2msで済みます。パフォーマンスの上限はScriptsよりはるかに高いです。

Functionは外部APIを呼び出せますか?

いいえ — Functionsはネットワークリクエストを行えません。 入力されたカートデータを処理して、操作を返すだけの純粋な計算です。外部データ(CRM参照、リアルタイム在庫チェックなど)が必要なら、あらかじめmetafieldsに保存しておくか、別の面(App Proxy、webhooks、バックエンド参照付きのCart Transform)を使う必要があります。これは、単に移植するのではなく設計を見直す必要がある、最も一般的な理由です。

Cart TransformとDiscountsの違いは何ですか?

Discountsは価格を変え、Cart Transformはカートの中身を変えます。 10%オフ、送料無料、BOGOを適用するならDiscounts APIを使います。2つの商品を1つのLine itemにまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptの多くは両方を混ぜていました。移行時は、2つのFunctionに分けてください。

デベロップメントストアなしで、Functionをローカルテストするには?

cargo test(Rust)またはnpm test(JS)でユニットテストはできますが、完全な統合テストにはデベロップメントストアが必要です。 CLIにはshopify app function runがあり、サンプル入力ファイルに対してFunctionを実行できます。高速な反復には便利です。ただし、チェックアウト動作をエンドツーエンドで確認するには、実際のカートを持つ本物のストアが必要です。

同じタイプのFunctionを複数持てますか?

はい — Shopifyはターゲットごとに複数のFunctionsをサポートしており、決定論的な順序で実行されます。 Discountsでは、順序はShopifyの割引スタッキングルールに従います。Delivery CustomizationsとPayment Customizationsでは、Functionsを連結できますが、各出力が次へ渡されます。多くのチームはシンプルさのために、タイプごとに1つのFunctionを使います。

Functionをデプロイしたら、Scriptはどうなりますか?

Script EditorのApps → Script EditorからScriptを非公開にするまでは、両方並行して動きます。 これは意図的です。並行実行のテスト期間を確保するためです。Functionが正しく動くことを確認したら、手動でScriptを非公開にします。2026年6月30日以降は、非公開にしたかどうかに関係なく、すべてのScriptsが実行停止します。

この移行はSEOやテーマに影響しますか?

いいえ — Functionsはチェックアウト時にサーバー側で実行され、テーマや商品ページには一切触れません。 チェックアウトで割引、配送オプション、支払い方法を変更するだけです。ストアフロント、商品テンプレート、SEOにはまったく影響しません。

Input.line_items をカスタムプロパティ付きで使うScriptはどう移行しますか?

カスタムプロパティは、GraphQL入力内のカートライン上の attribute フィールドから取得できます。 lines の選択内に attribute(key: "your-key") { value } を追加してください。Functionは、ScriptsがLine itemプロパティを読んでいたのと同じように読み取ります。Rubyのメソッド呼び出しではなく、GraphQLを通すだけです。

分析や注文タグはどうなりますか?Functionsはデータを書き込めますか?

Functionsは注文タグを書いたり、自分でwebhookを発火させたりはできません。現在のカートに対する操作を返すだけです。 タグ付けや後続ワークフローには、注文作成イベントでトリガーされるShopify Flowを使ってください。多くのマーチャントは、Function(割引用)とFlow(注文に「VOLUME-DISCOUNT-APPLIED」をタグ付けする用)を組み合わせています。

独自のFunctionを作らずに、インストールできる公開アプリはありますか?

はい — Shopify App Storeには、一般的な用途向けにFunctionsを包んだアプリが多数あります。 「discount function」「delivery customization」「payment customization」で検索してください。単純な用途(数量割引、タグで支払い方法を非表示、X以上で送料無料)なら、既存アプリで数日分の開発工数を節約できるかもしれません。本当に自社固有のロジックだけ、カスタムFunctionにしてください。

6月30日の期限に間に合わなかったら?

Scriptは実行停止します。フォールバックも、猶予期間も、延長もありません。 Scriptがやっていたこと(割引、非表示の配送レート、ブロックされた支払い方法など)は、7月1日0時UTCにデフォルト動作へ戻ります。そのロジックに事業が依存しているなら、かなり前倒しで本番稼働する計画を立ててください。特にパリティテストがあるため、移行には多くのチームが見積もるより時間がかかります。

Script Editorアプリを完全に削除できますか?

2026年6月30日以降ならアンインストールできますが、Shopifyが自動的に削除する可能性が高いです。 Scriptsの実行が止まれば、エディタの役目はありません。移行が完了していれば、今すぐすべてのScriptsを非公開にしてアプリをアンインストールしても構いません。Functionsは独立しています。

今週やること

この記事を読んでタブを閉じて終わりにしないでください。次の7日間で、この4つを実行してください。

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ移動し、エクスポートします。これが移行のバックログです。

2. 開発環境を整える。 Node 18+、Shopify CLI、Rust(必要なら)をインストールします。shopify versionが動くことを確認してください。所要時間: 30分。

3. 小さなFunctionを1つひな形化して、デベロップメントストアに出す。 一番簡単なScriptを選びます。たいていは支払い非表示ルールです。それを最後まで移行してください。本番に出さなくても、ツールチェーンの検証にはなります。

4. 次の8週間のカレンダー時間を確保する。 移行は、スプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、定期枠を確保し、リリース作業として扱ってください。

私たちが見てきた移行チームでは、パターンは一貫しています。2週間の停滞、3週間の実作業、1週間の片付け。つまり6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先送りするためではなく、コードレビューとQAに使ってください。

そしてチェックアウトロジックが整ったら、次に多くのチームが取り組むのがポストパーチェスです。顧客住所の変更、交換、注文後の割引追加などです。そこもロードマップにあるなら(高注文量のPlus運用でも、CX重視のAdvancedストアでも、そうあるべきです)、RevizeはShopify App Storeにあり、ここで作るすべてのFunctionと併用できます。

リソース

関連記事

2026年4月16日です。昨日——4月15日——は、ShopifyがScript Editorを完全にロックした日でした。新しいScriptを作成したり公開したりすることはもうできません。実行停止は75日後、2026年6月30日に訪れます。

もしあなたがShopify Plusの開発者——あるいはPlusストアを運営する代理店——で、この移行をここ12か月ずっと「次のスプリント」に先送りしてきたなら、問題があります。しかも「まあ直せるなら直す」程度ではありません。「7月1日深夜にチェックアウトが壊れる」レベルの問題です。多くのPlusストアでは、何年にもわたって5〜20個のScriptsが蓄積されており、それぞれが、誰も書いた覚えのない割引ルール、配送方法の非表示、支払い方法の制御を静かに担っています。

このガイドは、1月にあればよかったと思っていた技術的な移行マニュアルです。 単なる戦略ではなく、実際のコードまで扱います。読み終えるころには、Shopify CLIでFunctionのひな形を作成し、割引・配送カスタマイズ・支払いカスタマイズのためのRustまたはJavaScriptのロジックを書き、顧客の一部にタグを付けて安全にテストし、既存のチェックアウトを壊さずに本番へ出す方法がわかるはずです。

あなたのストアからScriptsを外し、Functionsを導入しましょう。


Developer migrating Shopify Scripts to Shopify Functions modules

簡潔な答え: Scripts → Functions を60秒で理解する

1段落でいうと: Shopify Scripts(Script Editor内で書くRubyコード、Plus限定)は、Shopify Functions(RustまたはJavaScriptで書かれたWebAssemblyモジュール、全プランで利用可能)に置き換えられています。shopify app generate extensionでFunctionのひな形を作り、必要なカートデータを取得するrun.graphqlクエリを書き、割引・非表示の配送方法などの操作を返すrun.rsまたはrun.jsファイルを書き、shopify app deployでデプロイしてからAdminまたはGraphQL mutationで有効化します。Functionsはコンパイル済みWASMとして5ms未満のレイテンシで実行され、全プランで動作し、今後Shopifyがサポートする唯一のカスタマイズ手段です。

6月30日に実際に何が変わるのか

コードに触る前に、日付を整理しましょう。2つあり、どちらも重要です。

日付

起こること

あなたの対応

2026年4月15日 (経過済み)

Script Editorが読み取り専用に。新しいScriptの作成不可。既存Scriptの編集不可。

既存Scriptsはまだ実行されます。今すぐ移行するか、ロジックを凍結してください。

2026年6月30日

すべてのShopify Scriptsが実行停止します。以上です。

この日までにFunctionの置き換えを本番稼働させておく必要があります。

移行は二択です。6月30日までにFunctionがデプロイされ、チェックアウトが動き続けるか、そうでないか。そうでなければ、影響を受けるすべてのカートは、標準価格、標準配送レート、そして有効なすべての支払い方法に静かに戻ります。部分点はありません。Scriptは動くか、動かないかだけです。そして6月30日以降は、動きません。

ヒント: Shopify adminでSettings → Checkout → Customizations Reportを開いてください。ストア内のすべての有効なScript、その内容、推奨されるFunctionの置き換えタイプが一覧表示されます。まずはそこから始めましょう。

Functions と Scripts: 実際に何が変わったのか

項目

Shopify Scripts(非推奨)

Shopify Functions(置き換え)

言語

Ruby DSL(Shopify専用)

Rust、JavaScript、TypeScript

ランタイム

Shopifyインフラ上のサンドボックス化されたRuby

WebAssembly(WASM)— 5ms未満で実行

利用プラン

Plusのみ

全プラン(カスタムアプリにはPlusが必要。公開アプリは利用可能)

エディタ

Admin内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — 直接編集

Git向け — 完全なバージョン管理

テスト

チェックアウトで手動テスト

shopify app devを使ったローカル開発、プレビューリンク

デプロイ

Adminで「Save」をクリック

ターミナルからshopify app deploy

対象

Line items、配送、支払い

Discounts、Cart Transform、Validation、Delivery Customization、Payment Customization、Order Routing、Fulfillment Constraints など

アーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを少し調整する」ものでした。Functionsは「実際のアプリを書く、それをバージョン管理する、ローカルでテストする、ちゃんとしたCIパイプラインでデプロイする」ものです。入口はかなり急です。ですが、今後の見通しでチェックアウトロジックに関する最後の移行でもあります。Functionsは、Scriptのようなつなぎではなく、Shopifyが長期的にコミットしている仕組みです。

あなたのScriptsを適切なFunctionタイプに対応付ける

今ある各Scriptは、必ず1つのFunction APIに対応します。モニターに貼っておきたい対応表はこちらです。

旧Scriptタイプ

やっていたこと

新しいFunction API

Functionターゲット

Line Item Script

特定の商品/顧客/カート条件に割引を適用

Cart & Checkout Discounts API

cart.lines.discounts.generate.run

Shipping Script(割引)

カートルールに基づく送料無料/送料割引

Cart & Checkout Discounts API

cart.delivery-options.discounts.generate.run

Shipping Script(非表示/名称変更/並べ替え)

$X以上の配送レートを非表示にする、「Standard」を「$50以上で無料」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを非表示、$500超で代金引換を非表示、支払い方法を並べ替え

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(まれ)

商品のバンドル、Line itemの入れ替え

Cart Transform API

cart.transform.run

チェックアウト停止Script

SKUの組み合わせが無効ならカートを拒否

Cart & Checkout Validation API

cart.validations.generate.run

Scriptが10個あるなら、実際には3〜5個のFunctionになる可能性が高いです。複数のScriptが、よりきれいな分岐ロジックを持つ1つのFunctionにまとまることが多いからです。


Shopify Functions unifying discounts, delivery, and payment customizations

前提条件: ローカル開発環境を整える

Functionのひな形を作る前に、ローカルに3つのものをインストールしておく必要があります。ターミナルで次の確認を行ってください。

1. Node.js 18+

node --version
# Must be >= 18.0.0

古い場合は、nvmで入れるか、nodejs.orgからダウンロードしてください。

2. Shopify CLI 3+

npm install -g @shopify/cli@latest
shopify version
# Should output 3.x or higher

3. Rust toolchain(RustでFunctionsを書く場合のみ)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasip1
cargo --version

JavaScript FunctionsにはRustは不要です。チームで1つの言語を選び、それに統一してください。両方を混ぜると保守コストが増えます。

4. 開発用ストア

Partnerダッシュボードにログインして新しいdevelopment storeを作成するか、既存のものを使ってください。本番へ昇格する前に、まずこのストアへFunctionsをデプロイします。

最初のFunctionをひな形から作る

CLIが大半の雛形を作ってくれます。任意のディレクトリ内で:

# Create a new Shopify app (skip if you already have one)
shopify app init my-checkout-functions
cd my-checkout-functions

# Generate a Function extension
shopify app generate extension

CLIがプロンプトに沿って案内してくれます。割引Functionなら、次を選びます:

  • Type: Function

  • Template: discount(または cart_checkout_validationdelivery_customizationpayment_customization など)

  • Language: Rust または JavaScript

  • Name: volume-discount-fn のような名前

これにより、extensions/volume-discount-fn/ が作成され、以下が含まれます:

extensions/volume-discount-fn/
├── shopify.extension.toml      # Function config targets, build, version
├── src/
├── cart_lines_discounts_generate_run.graphql   # Input query
└── cart_lines_discounts_generate_run.rs        # Function logic
├── Cargo.toml                  # Rust dependencies (Rust only)
└── README.md

継続的に編集する3つのファイルは、.toml(設定)、.graphql(入力)、.rs / .js(ロジック)です。それだけです。

チュートリアル1: Line Item Scriptを置き換える(数量割引)

たとえば、旧Scriptが、特定コレクションの合計数量が5個以上なら注文小計を10%オフにしていたとします。これがFunction版です。

ステップ1.1: 設定(shopify.extension.toml

api_version = "2026-01"

[[extensions]]
name = "volume-discount-fn"
handle = "volume-discount-fn"
type = "function"

[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"

[extensions.build]
command = "cargo build --target=wasm32-wasip1 --release"
path = "target/wasm32-wasip1/release/volume-discount-fn.wasm"
watch = ["src/**/*.rs"]

ステップ1.2: 入力クエリ(src/cart_lines_discounts_generate_run.graphql

query Input {
  cart {
    lines {
      id
      quantity
      cost {
        subtotalAmount {
          amount
        }
      }
      merchandise {
        ... on ProductVariant {
          product {
            inAnyCollection(ids: ["gid://shopify/Collection/123456789"])
          }
        }
      }
    }
  }
  discount {
    discountClasses
  }
}

ヒント: Functionsが見られるのは、クエリしたデータだけです。GraphQLは最小限に保ってください。省いたフィールドが多いほど、実行は速く、コストも低くなります。

ステップ1.3: ロジック(src/cart_lines_discounts_generate_run.rs

use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function]
fn cart_lines_discounts_generate_run(
    input: schema::cart_lines_discounts_generate_run::Input,
) -> Result<schema::CartLinesDiscountsGenerateRunResult> {
    // Bail if discount class doesn't match
    let has_order_discount = input
        .discount()
        .discount_classes()
        .contains(&schema::DiscountClass::Order);

    if !has_order_discount {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Sum quantities of items in the target collection
    let qualifying_qty: i64 = input
        .cart()
        .lines()
        .iter()
        .filter(|line| {
            if let schema::Merchandise::ProductVariant(v) = line.merchandise() {
                *v.product().in_any_collection()
            } else {
                false
            }
        })
        .map(|line| *line.quantity())
        .sum();

    if qualifying_qty < 5 {
        return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
    }

    // Apply 10% off the order subtotal
    Ok(schema::CartLinesDiscountsGenerateRunResult {
        operations: vec![schema::CartOperation::OrderDiscountsAdd(
            schema::OrderDiscountsAddOperation {
                selection_strategy: schema::OrderDiscountSelectionStrategy::First,
                candidates: vec![schema::OrderDiscountCandidate {
                    targets: vec![schema::OrderDiscountCandidateTarget::OrderSubtotal(
                        schema::OrderSubtotalTarget {
                            excluded_cart_line_ids: vec![],
                        },
                    )],
                    message: Some("Volume discount: 10% off".to_string()),
                    value: schema::OrderDiscountCandidateValue::Percentage(
                        schema::Percentage { value: Decimal(10.0) }
                    ),
                    conditions: None,
                    associated_discount_code: None,
                }],
            },
        )],
    })
}

ステップ1.4: テスト、デプロイ、有効化

# Local development with hot reload
shopify app dev

# When ready, deploy
shopify app deploy

# In the GraphiQL panel that opens (press `g` in the dev terminal),
# create the automatic discount that uses your Function:
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Volume Discount (5+ collection items)"
      functionHandle: "volume-discount-fn"
      discountClasses: [ORDER]
      startsAt: "2026-04-16T00:00:00Z"
    }
  ) {
    automaticAppDiscount { discountId }
    userErrors { field message }
  }
}

これで完了です。Functionは本番稼働し、バージョン管理され、旧Scriptを完全に置き換えます。

チュートリアル2: Shipping Scriptを置き換える(カートしきい値超過で方法を非表示)

よくあるScript: 「カート小計が$500を超えたら、翌日配送のような高額配送を防ぐためにExpress Shippingを非表示にする」。これがDelivery Customization Function版です。

ステップ2.1: ひな形作成

shopify app generate extension --template delivery_customization --name hide-express-fn

ステップ2.2: 入力クエリ(src/run.graphql

query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    deliveryGroups {
      deliveryOptions {
        handle
        title
      }
    }
  }
}

ステップ2.3: ロジック(src/run.js — JavaScript版)

// @ts-check
/**
 * @typedef {import("../generated/api").RunInput} RunInput
 * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
 */

const NO_CHANGES = { operations: [] };
const THRESHOLD = 500.0;
const HIDE_TITLES = ["Express", "Overnight"];

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
  if (subtotal < THRESHOLD) return NO_CHANGES;

  const operations = input.cart.deliveryGroups.flatMap((group) =>
    group.deliveryOptions
      .filter((opt) => HIDE_TITLES.some((t) => opt.title.includes(t)))
      .map((opt) => ({
        hide: { deliveryOptionHandle: opt.handle },
      }))
  );

  return { operations };
}

ステップ2.4: Adminから有効化(GraphQL不要)

Delivery Customizationsには、組み込みのAdmin UIがあります。shopify app deployの後に:

  1. Settings → Shipping and deliveryへ移動

  2. 下部のCustomizationsセクションまでスクロール

  3. Add customizationをクリック → あなたのFunctionを選択

  4. 保存

非表示ルールは本番で有効になります。mutationは不要です。


Shopify delivery customization Function hiding shipping option at checkout

チュートリアル3: Payment Scriptを置き換える(B2B向けにCODを非表示)

旧Script: 「'B2B'タグの付いた顧客には代金引換を非表示にする」。これがPayment Customization版です。

ステップ3.1: ひな形作成

shopify app generate extension --template payment_customization --name hide-cod-b2b-fn

ステップ3.2: 入力クエリ

query Input {
  cart {
    buyerIdentity {
      customer {
        hasTags(tags: [{ tag: "B2B" }]) {
          tag
          hasTag
        }
      }
    }
  }
  paymentMethods {
    id
    name
  }
}

ステップ3.3: ロジック(src/run.js

const NO_CHANGES = { operations: [] };

export function run(input) {
  const tagCheck = input.cart?.buyerIdentity?.customer?.hasTags?.[0];
  const isB2B = tagCheck?.hasTag === true;
  if (!isB2B) return NO_CHANGES;

  const codMethod = input.paymentMethods.find((pm) =>
    pm.name.toLowerCase().includes("cash on delivery")
  );
  if (!codMethod) return NO_CHANGES;

  return {
    operations: [{ hide: { paymentMethodId: codMethod.id } }],
  };
}

ステップ3.4: 有効化

Payment Customizationsにも、Settings → Payments → Customizationsの下にAdmin UIがあります。配送と同じ流れです。Functionを選び、保存して完了です。

ここまで読んだあなたへ — ポストパーチェスについて一言

これはRevizeのブログなので、少しだけ補足します。Revizeは、Functionsでは触れられない部分を扱います。注文後に、顧客が商品を追加したい、サイズを交換したい、配送先住所を修正したい、忘れていた割引を適用したい——そんなときです。Functionsはチェックアウト時にあり、Revizeはその後にあります。 Functionsはカート内で何が許可されるかを決め、Revizeは返金して作り直すことなく、その後に顧客とサポートチームが注文を編集できるようにします。これは2種類のストアにとって重要です。手動編集が回らなくなるほど注文量が多いストア(もちろんPlus運用のストア、そして高スループットなAdvancedストアもそうです)と、ブランドの核が顧客体験であるストアです。そこでは「すみません、変更できません」というメールは、再購入を遠ざける原因になります。

移行計画がScripts → Functionsまでしかカバーしておらず、ポストパーチェスの注文編集をまだ整理していないなら、3週間ほどで次の「なんでこんなに難しいの?」の壁にぶつかります。先日公開した注文管理ガイドに、チェックアウト後の完全な運用手順をまとめています。

移行の話に戻りましょう。

テスト戦略: タグ付き顧客パターン

Functionsには、Adminで切り替えられる「ドラフトモード」がありません。プロ向けのやり方は、新しいFunctionを顧客タグで制御し、旧Scriptと新Functionを並行稼働させ、タグ付きユーザーに対して同一の結果を出すことを確認してから、切り替える方法です。

ステップ1: テストユーザーにタグを付ける

Customersで、社内アカウント2〜3件にFN-TESTERタグを追加します。

ステップ2: タグの有無でFunctionを分岐させる

// At the top of your run function
let is_tester = input
    .cart()
    .buyer_identity()
    .and_then(|bi| bi.customer())
    .map(|c| c.has_any_tag())
    .unwrap_or(&false);

if !*is_tester {
    return Ok(default_result);  // Fall through to existing Script
}

// New Function logic only runs for tagged users

ステップ3: 入力クエリにhasAnyTagを追加する

cart {
  buyerIdentity {
    customer {
      hasAnyTag(tags: ["FN-TESTER"])
    }
  }
}

ステップ4: チェックアウトで確認する

タグ付きユーザーとしてログインし、チェックアウトを進めてFunctionが実行されることを確認します。タグなしユーザーでログインし、旧Scriptがまだ実行されることを確認します。数日間同等性が保てたら、タグチェックを外してFunctionを全員に適用します。

ステップ5: 旧Scriptを非公開にする

Apps → Script Editor → [Your Script] → Unpublishへ移動します。非公開にしたら、Functionが唯一の正です。

実際にスケールするデプロイワークフロー

開発者のノートPCから永遠にデプロイし続けないでください。1つか2つScriptを移行したら、ちゃんとしたCIパイプラインを組みましょう。

最小実行可能なワークフロー

# .github/workflows/deploy-functions.yml
name: Deploy Shopify Functions
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - uses: dtolnay/rust-toolchain@stable
        with: { targets: wasm32-wasip1 }
      - run: npm install -g @shopify/cli@latest
      - run: shopify app deploy --force
        env:
          SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}

PartnerダッシュボードのSettings → Tokensからパートナートークンを生成します。これでmainへのマージごとにFunctionsが出荷されます。「Mikeがデプロイしたの?」というSlackのやり取りはもう不要です。

バージョン管理とロールバック

shopify app deployはバージョン付きスナップショットを作成します。ロールバックするには:

shopify app versions list
shopify app release --version <previous-version-id

Scriptsの場合は「古いコードを覚えておいて、貼り戻す」しかなかったので、これはまるで別世界です。


Shopify Functions deployment pipeline across local staging and production environments

多くのチームが間違えること

この1年、Plusマーチャントの移行を支援してきて、同じ5つのミスが繰り返し出てきました。

1. Functionsを1対1のScript移植として扱う。 そうではありません。1つのFunctionで、3つのScriptをよりきれいな分岐で置き換えられることがあります。書き換える前に、Scriptsをシステムとして棚卸ししてください。

2. 読み取りスコープを忘れる。 多くのFunctionsにはread_customersread_orderswrite_discountsが必要です。shopify.app.tomlscopesに追加し、アプリを再承認してください。そうしないと入力クエリはnullになります。

3. パリティテストなしで、タグなし顧客にFunctionsを実行する。 コードが正しく見えても、エッジケース(空カート、ギフトカード、ストアクレジット、B2Bの下書きなど)で問題が表面化します。タグで段階展開すれば2日で済み、P1障害を回避できます。

4. Customizations Reportを飛ばす。 実際に何が動いているかを知るための、最も優れた棚卸し資料です。記憶を頼りに移行せず、レポートを元に移行してください。

5. コレクションIDや顧客タグをハードコードする。 マーチャントが設定できる値が必要なら、metafields経由でFunction設定を使ってください。CLIはmetafield連携の設定もひな形化できます。ShopifyのFunction設定ドキュメントを参照してください。

今後75日間の移行チェックリスト

6月30日まで落ち着いて到達するための、現実的な週ごとの計画です。

アクション

第1週(今週)

Customizations Reportを取得。すべてのScriptを棚卸し。Function化・公開アプリ利用・削除のどれかを決める。

第2〜3週

ローカル開発環境を整える。最初のFunctionをひな形から作る。最も簡単なScript(たいていは支払い非表示ルール)を移行する。

第4〜6週

割引Scriptを移行する。Discounts APIが最も豊富なので、ここが一番時間がかかります。タグテストを徹底してください。

第7〜8週

配送/配送方法Scriptを移行する。Delivery CustomizationsをAdminから有効化する。

第9〜10週

CIパイプラインを構築する。デプロイを開発者のノートPCから完全に移す。

第11週 (6月中旬)

最終パリティ確認。すべてのScriptを非公開にする。2週間はFunctionsだけでストアを運用する。

6月30日

移行期限当日。早めに終えているので、何も壊れません。

今週始めれば、余裕があります。6月に始めるなら、余裕はありません。


Week-by-week migration roadmap from Shopify Scripts to Functions

よくある質問

Functionsを使うにはShopify Plusが必要ですか?

カスタムFunctionsにはShopify Plusが必要ですが、公開アプリのFunctionsは全プランで使えます。 Plusでない場合は2つの道があります。Shopify App StoreからFunctionを提供する公開アプリを入れるか、Plusにアップグレードして独自のカスタムFunctionsを書くかです。Scriptを使っていた大きなマーチャントの多くはすでにPlusだったため、実務上はあまり変わりません。

FunctionsはTypeScriptで書けますか?

はい — TypeScriptは完全にサポートされており、CLIがひな形も作ってくれます。 shopify app generate extensionを実行して「JavaScript」を選ぶと、生成されたプロジェクトにはimport("../generated/api")からの型定義が含まれます。必要ならファイルを.tsに変えてtsconfig.jsonを追加できます。コンパイル後の出力(WASM)は、ソース言語に関係なく同じです。

FunctionsはScriptsと比べてどれくらい速いですか?

Functionsは通常5ms未満で実行され、Ruby Scriptsよりかなり高速です。 WebAssemblyにコンパイルされ、タイトなランタイムで動作するため、Shopifyは5msの実行予算を課しています。Functionがこれを超えると、その操作は破棄され、Functionは何も返しません。実際には、よく書かれたFunctionなら1〜2msで済みます。パフォーマンスの上限はScriptsよりはるかに高いです。

Functionは外部APIを呼び出せますか?

いいえ — Functionsはネットワークリクエストを行えません。 入力されたカートデータを処理して、操作を返すだけの純粋な計算です。外部データ(CRM参照、リアルタイム在庫チェックなど)が必要なら、あらかじめmetafieldsに保存しておくか、別の面(App Proxy、webhooks、バックエンド参照付きのCart Transform)を使う必要があります。これは、単に移植するのではなく設計を見直す必要がある、最も一般的な理由です。

Cart TransformとDiscountsの違いは何ですか?

Discountsは価格を変え、Cart Transformはカートの中身を変えます。 10%オフ、送料無料、BOGOを適用するならDiscounts APIを使います。2つの商品を1つのLine itemにまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptの多くは両方を混ぜていました。移行時は、2つのFunctionに分けてください。

デベロップメントストアなしで、Functionをローカルテストするには?

cargo test(Rust)またはnpm test(JS)でユニットテストはできますが、完全な統合テストにはデベロップメントストアが必要です。 CLIにはshopify app function runがあり、サンプル入力ファイルに対してFunctionを実行できます。高速な反復には便利です。ただし、チェックアウト動作をエンドツーエンドで確認するには、実際のカートを持つ本物のストアが必要です。

同じタイプのFunctionを複数持てますか?

はい — Shopifyはターゲットごとに複数のFunctionsをサポートしており、決定論的な順序で実行されます。 Discountsでは、順序はShopifyの割引スタッキングルールに従います。Delivery CustomizationsとPayment Customizationsでは、Functionsを連結できますが、各出力が次へ渡されます。多くのチームはシンプルさのために、タイプごとに1つのFunctionを使います。

Functionをデプロイしたら、Scriptはどうなりますか?

Script EditorのApps → Script EditorからScriptを非公開にするまでは、両方並行して動きます。 これは意図的です。並行実行のテスト期間を確保するためです。Functionが正しく動くことを確認したら、手動でScriptを非公開にします。2026年6月30日以降は、非公開にしたかどうかに関係なく、すべてのScriptsが実行停止します。

この移行はSEOやテーマに影響しますか?

いいえ — Functionsはチェックアウト時にサーバー側で実行され、テーマや商品ページには一切触れません。 チェックアウトで割引、配送オプション、支払い方法を変更するだけです。ストアフロント、商品テンプレート、SEOにはまったく影響しません。

Input.line_items をカスタムプロパティ付きで使うScriptはどう移行しますか?

カスタムプロパティは、GraphQL入力内のカートライン上の attribute フィールドから取得できます。 lines の選択内に attribute(key: "your-key") { value } を追加してください。Functionは、ScriptsがLine itemプロパティを読んでいたのと同じように読み取ります。Rubyのメソッド呼び出しではなく、GraphQLを通すだけです。

分析や注文タグはどうなりますか?Functionsはデータを書き込めますか?

Functionsは注文タグを書いたり、自分でwebhookを発火させたりはできません。現在のカートに対する操作を返すだけです。 タグ付けや後続ワークフローには、注文作成イベントでトリガーされるShopify Flowを使ってください。多くのマーチャントは、Function(割引用)とFlow(注文に「VOLUME-DISCOUNT-APPLIED」をタグ付けする用)を組み合わせています。

独自のFunctionを作らずに、インストールできる公開アプリはありますか?

はい — Shopify App Storeには、一般的な用途向けにFunctionsを包んだアプリが多数あります。 「discount function」「delivery customization」「payment customization」で検索してください。単純な用途(数量割引、タグで支払い方法を非表示、X以上で送料無料)なら、既存アプリで数日分の開発工数を節約できるかもしれません。本当に自社固有のロジックだけ、カスタムFunctionにしてください。

6月30日の期限に間に合わなかったら?

Scriptは実行停止します。フォールバックも、猶予期間も、延長もありません。 Scriptがやっていたこと(割引、非表示の配送レート、ブロックされた支払い方法など)は、7月1日0時UTCにデフォルト動作へ戻ります。そのロジックに事業が依存しているなら、かなり前倒しで本番稼働する計画を立ててください。特にパリティテストがあるため、移行には多くのチームが見積もるより時間がかかります。

Script Editorアプリを完全に削除できますか?

2026年6月30日以降ならアンインストールできますが、Shopifyが自動的に削除する可能性が高いです。 Scriptsの実行が止まれば、エディタの役目はありません。移行が完了していれば、今すぐすべてのScriptsを非公開にしてアプリをアンインストールしても構いません。Functionsは独立しています。

今週やること

この記事を読んでタブを閉じて終わりにしないでください。次の7日間で、この4つを実行してください。

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ移動し、エクスポートします。これが移行のバックログです。

2. 開発環境を整える。 Node 18+、Shopify CLI、Rust(必要なら)をインストールします。shopify versionが動くことを確認してください。所要時間: 30分。

3. 小さなFunctionを1つひな形化して、デベロップメントストアに出す。 一番簡単なScriptを選びます。たいていは支払い非表示ルールです。それを最後まで移行してください。本番に出さなくても、ツールチェーンの検証にはなります。

4. 次の8週間のカレンダー時間を確保する。 移行は、スプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、定期枠を確保し、リリース作業として扱ってください。

私たちが見てきた移行チームでは、パターンは一貫しています。2週間の停滞、3週間の実作業、1週間の片付け。つまり6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先送りするためではなく、コードレビューとQAに使ってください。

そしてチェックアウトロジックが整ったら、次に多くのチームが取り組むのがポストパーチェスです。顧客住所の変更、交換、注文後の割引追加などです。そこもロードマップにあるなら(高注文量のPlus運用でも、CX重視のAdvancedストアでも、そうあるべきです)、RevizeはShopify App Storeにあり、ここで作るすべてのFunctionと併用できます。

リソース

関連記事

RevizeでShopifyストアを刷新し、
顧客体験でリードしましょう

© 著作権 2024、無断転載を禁じます

RevizeでShopifyストアを刷新し、
顧客体験でリードしましょう

© 著作権 2024、無断転載を禁じます

RevizeでShopifyストアを刷新し、
顧客体験でリードしましょう

© 著作権 2024、無断転載を禁じます

RevizeでShopifyストアを刷新し、
顧客体験でリードしましょう

© 著作権 2024、無断転載を禁じます

The Universal Commerce Protocol UCP Guide How to Start a Shopify Store in 2026 The True Cost of Returns Guide How to Change Shipping Address on Shopify Best Shopify Customer Service Apps 10 Advanced Shopify Flow Workflows How to Add Discount on Shopify After Checkout How to Edit an Order on Shopify Shopify Draft Orders Complete Guide How to Do a Partial Refund on Shopify Shopify Social Login Complete Guide Post Purchase Email Marketing Automating E-commerce with Shopify Flow Customize Shopify Login Redirect Shopify New Customer Accounts Migration Guide How Poor Customer Support Can Sabotage Your Business How Refunds Work on Shopify In-House Warranty Management vs Shopify Apps Shopify Checkout Extensibility 2026: You Missed the Deadline. Now What? How to Let Customers Cancel Orders on Shopify Shopify Legacy Customer Accounts Are Deprecated: What Every Merchant Needs to Do Right Now How to Edit Your Shopify Order Confirmation Email How to Do an Exchange on Shopify How to Sell on ChatGPT with Shopify Agentic Storefronts Shopify Functions Migration Tutorial