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

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

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

Shopify ScriptsをFunctionsに移行する方法:完全コードチュートリアル(2026年版)— Revizeブログ記事ヘッダー

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は読み取り専用。新しいScriptsは作成不可。既存Scriptsの編集も不可。

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

2026年6月30日

すべてのShopify Scriptsが実行停止。これで終了です。

この日までにFunctionの代替を本番稼働させる必要があります。

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

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

Functions vs Scripts: 何が本当に変わったのか

観点

Shopify Scripts(非推奨)

Shopify Functions(置き換え先)

言語

Ruby DSL(Shopify固有)

Rust、JavaScript、TypeScript

実行環境

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

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

利用プラン

Plusのみ

すべてのプラン(カスタムアプリはPlusが必要。公開アプリは開放)

エディタ

管理画面内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — ライブ編集

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

テスト

チェックアウト内で手動

shopify app devでのローカル開発、プレビューリンク

デプロイ

管理画面で「Save」をクリック

ターミナルからshopify app deploy

対象

商品行、配送、支払い

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

このアーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを微調整する」ものでした。Functionsは「本物のアプリを書き、バージョン管理し、ローカルでテストし、実際のCIパイプラインでデプロイする」ものです。参入障壁は高くなります。しかし、近い将来にチェックアウトロジックで行う移行はこれが最後になります。Functionsは、Scriptsのようなつなぎではなく、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」を「Free over $50」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを隠す、$500超でCODを隠す、支払い方法を並べ替える

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(稀)

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

Cart Transform API

cart.transform.run

チェックアウトブロックScript

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

Cart & Checkout Validation API

cart.validations.generate.run

10個のScriptがあるなら、実際には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(FunctionをRustで書く場合のみ)

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ダッシュボードにログインして新しい開発用ストアを作成するか、既存のものを使ってください。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'タグのある顧客にはCash on Deliveryを隠す。」以下が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を選び、保存すれば完了です。

この記事を読んでいるあなたへ — Post-Purchaseについて一言

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

移行計画がScripts → Functionsだけを対象にしていて、Post-Purchaseの注文編集を整理したことがないなら、あと3週間ほどで次の「なぜこれがこんなに難しいの?」という壁にぶつかるでしょう。先ほど公開した注文管理ガイドに、チェックアウト後の完全な運用フローが載っています。

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

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

Functionsには、管理画面で切り替えられる「draft mode」はありません。プロ向けのやり方は、新しい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をUnpublishする

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から、partner tokenを生成してください。これで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をよりきれいな分岐で置き換えられることがあります。書き換える前に、Script群をシステムとして監査してください。

2. readスコープを忘れる。 多くの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 configurationのドキュメントを参照してください。

次の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をUnpublish。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を書くかです。Scriptsを使っていた大規模マーチャントの多くは既に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つの行にまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptsの多くは両方を混ぜていました。移行時には、2つのFunctionに分けてください。

開発用ストアなしでFunctionをローカルテストするには?

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

同じ種類のFunctionを複数持てますか?

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

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

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

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

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

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

カスタムプロパティは、GraphQL入力のカート行にあるattributeフィールドから参照できます。 linesセレクション内にattribute(key: "your-key") { value }を追加してください。Functionは、Scriptsが行アイテムプロパティを読むのと同じように読み取れます。Rubyのメソッド呼び出しではなくGraphQL経由になるだけです。

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

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

カスタムFunctionを作らずにインストールできる公開アプリはありますか?

はい。Shopify App Storeには、一般的な用途向けにFunctionsをラップしたアプリが数多くあります。 「discount function」「delivery customization」「payment customization」で検索してください。明快な用途(ボリューム割引、タグ別の支払い方法非表示、X円以上で送料無料)なら、既存アプリで開発日数を節約できるかもしれません。本当にビジネス固有のロジックだけをカスタムFunctionsに任せましょう。

6月30日の締切に間に合わなかったら?

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

Script Editorアプリ自体を削除できますか?

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

今週やること

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

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ行き、エクスポートしてください。これが移行バックログです。

2. 開発環境をセットアップする。 Node 18+、Shopify CLI、そして必要ならRustをインストールします。shopify versionが動くことを確認してください。所要時間は30分です。

3. ひとつの小さなFunctionをスキャフォールドして、開発用ストアに出す。 いちばん簡単なScriptを選んでください。たいていは支払い非表示ルールです。エンドツーエンドで移行します。本番に行かなくても、ツールチェーンの検証になります。

4. 今後8週間のカレンダーを押さえる。 移行はスプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、繰り返し枠を確保し、それをリリース扱いにしてください。

これまで見てきた移行チームでは、傾向は一貫しています。2週間の停滞、3週間の実作業、1週間の整理。合計6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先延ばしにするのではなく、コードレビューとQAに使ってください。

そして、チェックアウトロジックが片付いたら、多くのチームが次に取り組むのはPost-Purchaseです。注文後の住所変更、商品の交換、注文後の割引追加などです。そこがロードマップにあるなら(高注文量の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は読み取り専用。新しいScriptsは作成不可。既存Scriptsの編集も不可。

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

2026年6月30日

すべてのShopify Scriptsが実行停止。これで終了です。

この日までにFunctionの代替を本番稼働させる必要があります。

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

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

Functions vs Scripts: 何が本当に変わったのか

観点

Shopify Scripts(非推奨)

Shopify Functions(置き換え先)

言語

Ruby DSL(Shopify固有)

Rust、JavaScript、TypeScript

実行環境

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

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

利用プラン

Plusのみ

すべてのプラン(カスタムアプリはPlusが必要。公開アプリは開放)

エディタ

管理画面内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — ライブ編集

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

テスト

チェックアウト内で手動

shopify app devでのローカル開発、プレビューリンク

デプロイ

管理画面で「Save」をクリック

ターミナルからshopify app deploy

対象

商品行、配送、支払い

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

このアーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを微調整する」ものでした。Functionsは「本物のアプリを書き、バージョン管理し、ローカルでテストし、実際のCIパイプラインでデプロイする」ものです。参入障壁は高くなります。しかし、近い将来にチェックアウトロジックで行う移行はこれが最後になります。Functionsは、Scriptsのようなつなぎではなく、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」を「Free over $50」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを隠す、$500超でCODを隠す、支払い方法を並べ替える

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(稀)

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

Cart Transform API

cart.transform.run

チェックアウトブロックScript

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

Cart & Checkout Validation API

cart.validations.generate.run

10個のScriptがあるなら、実際には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(FunctionをRustで書く場合のみ)

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

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

4. 開発用ストア

Partnerダッシュボードにログインして新しい開発用ストアを作成するか、既存のものを使ってください。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'タグのある顧客にはCash on Deliveryを隠す。」以下が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を選び、保存すれば完了です。

この記事を読んでいるあなたへ — Post-Purchaseについて一言

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

移行計画がScripts → Functionsだけを対象にしていて、Post-Purchaseの注文編集を整理したことがないなら、あと3週間ほどで次の「なぜこれがこんなに難しいの?」という壁にぶつかるでしょう。先ほど公開した注文管理ガイドに、チェックアウト後の完全な運用フローが載っています。

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

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

Functionsには、管理画面で切り替えられる「draft mode」はありません。プロ向けのやり方は、新しい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をUnpublishする

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から、partner tokenを生成してください。これで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をよりきれいな分岐で置き換えられることがあります。書き換える前に、Script群をシステムとして監査してください。

2. readスコープを忘れる。 多くの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 configurationのドキュメントを参照してください。

次の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をUnpublish。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を書くかです。Scriptsを使っていた大規模マーチャントの多くは既に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つの行にまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptsの多くは両方を混ぜていました。移行時には、2つのFunctionに分けてください。

開発用ストアなしでFunctionをローカルテストするには?

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

同じ種類のFunctionを複数持てますか?

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

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

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

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

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

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

カスタムプロパティは、GraphQL入力のカート行にあるattributeフィールドから参照できます。 linesセレクション内にattribute(key: "your-key") { value }を追加してください。Functionは、Scriptsが行アイテムプロパティを読むのと同じように読み取れます。Rubyのメソッド呼び出しではなくGraphQL経由になるだけです。

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

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

カスタムFunctionを作らずにインストールできる公開アプリはありますか?

はい。Shopify App Storeには、一般的な用途向けにFunctionsをラップしたアプリが数多くあります。 「discount function」「delivery customization」「payment customization」で検索してください。明快な用途(ボリューム割引、タグ別の支払い方法非表示、X円以上で送料無料)なら、既存アプリで開発日数を節約できるかもしれません。本当にビジネス固有のロジックだけをカスタムFunctionsに任せましょう。

6月30日の締切に間に合わなかったら?

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

Script Editorアプリ自体を削除できますか?

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

今週やること

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

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ行き、エクスポートしてください。これが移行バックログです。

2. 開発環境をセットアップする。 Node 18+、Shopify CLI、そして必要ならRustをインストールします。shopify versionが動くことを確認してください。所要時間は30分です。

3. ひとつの小さなFunctionをスキャフォールドして、開発用ストアに出す。 いちばん簡単なScriptを選んでください。たいていは支払い非表示ルールです。エンドツーエンドで移行します。本番に行かなくても、ツールチェーンの検証になります。

4. 今後8週間のカレンダーを押さえる。 移行はスプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、繰り返し枠を確保し、それをリリース扱いにしてください。

これまで見てきた移行チームでは、傾向は一貫しています。2週間の停滞、3週間の実作業、1週間の整理。合計6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先延ばしにするのではなく、コードレビューとQAに使ってください。

そして、チェックアウトロジックが片付いたら、多くのチームが次に取り組むのはPost-Purchaseです。注文後の住所変更、商品の交換、注文後の割引追加などです。そこがロードマップにあるなら(高注文量の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は読み取り専用。新しいScriptsは作成不可。既存Scriptsの編集も不可。

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

2026年6月30日

すべてのShopify Scriptsが実行停止。これで終了です。

この日までにFunctionの代替を本番稼働させる必要があります。

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

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

Functions vs Scripts: 何が本当に変わったのか

観点

Shopify Scripts(非推奨)

Shopify Functions(置き換え先)

言語

Ruby DSL(Shopify固有)

Rust、JavaScript、TypeScript

実行環境

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

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

利用プラン

Plusのみ

すべてのプラン(カスタムアプリはPlusが必要。公開アプリは開放)

エディタ

管理画面内のScript Editor

ローカルIDE + Shopify CLI

バージョン管理

なし — ライブ編集

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

テスト

チェックアウト内で手動

shopify app devでのローカル開発、プレビューリンク

デプロイ

管理画面で「Save」をクリック

ターミナルからshopify app deploy

対象

商品行、配送、支払い

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

このアーキテクチャの変化は重要です。Scriptsは「テキストエリアでRubyを微調整する」ものでした。Functionsは「本物のアプリを書き、バージョン管理し、ローカルでテストし、実際のCIパイプラインでデプロイする」ものです。参入障壁は高くなります。しかし、近い将来にチェックアウトロジックで行う移行はこれが最後になります。Functionsは、Scriptsのようなつなぎではなく、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」を「Free over $50」に変更する

Delivery Customization API

cart.delivery-options.transform.run

Payment Script

B2B向けにPayPalを隠す、$500超でCODを隠す、支払い方法を並べ替える

Payment Customization API

cart.payment-methods.transform.run

カート変更Script(稀)

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

Cart Transform API

cart.transform.run

チェックアウトブロックScript

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

Cart & Checkout Validation API

cart.validations.generate.run

10個のScriptがあるなら、実際には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(FunctionをRustで書く場合のみ)

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

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

4. 開発用ストア

Partnerダッシュボードにログインして新しい開発用ストアを作成するか、既存のものを使ってください。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'タグのある顧客にはCash on Deliveryを隠す。」以下が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を選び、保存すれば完了です。

この記事を読んでいるあなたへ — Post-Purchaseについて一言

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

移行計画がScripts → Functionsだけを対象にしていて、Post-Purchaseの注文編集を整理したことがないなら、あと3週間ほどで次の「なぜこれがこんなに難しいの?」という壁にぶつかるでしょう。先ほど公開した注文管理ガイドに、チェックアウト後の完全な運用フローが載っています。

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

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

Functionsには、管理画面で切り替えられる「draft mode」はありません。プロ向けのやり方は、新しい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をUnpublishする

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から、partner tokenを生成してください。これで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をよりきれいな分岐で置き換えられることがあります。書き換える前に、Script群をシステムとして監査してください。

2. readスコープを忘れる。 多くの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 configurationのドキュメントを参照してください。

次の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をUnpublish。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を書くかです。Scriptsを使っていた大規模マーチャントの多くは既に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つの行にまとめたり、1つのバリアントを複数に分割したりするならCart Transformを使います。古いScriptsの多くは両方を混ぜていました。移行時には、2つのFunctionに分けてください。

開発用ストアなしでFunctionをローカルテストするには?

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

同じ種類のFunctionを複数持てますか?

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

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

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

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

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

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

カスタムプロパティは、GraphQL入力のカート行にあるattributeフィールドから参照できます。 linesセレクション内にattribute(key: "your-key") { value }を追加してください。Functionは、Scriptsが行アイテムプロパティを読むのと同じように読み取れます。Rubyのメソッド呼び出しではなくGraphQL経由になるだけです。

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

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

カスタムFunctionを作らずにインストールできる公開アプリはありますか?

はい。Shopify App Storeには、一般的な用途向けにFunctionsをラップしたアプリが数多くあります。 「discount function」「delivery customization」「payment customization」で検索してください。明快な用途(ボリューム割引、タグ別の支払い方法非表示、X円以上で送料無料)なら、既存アプリで開発日数を節約できるかもしれません。本当にビジネス固有のロジックだけをカスタムFunctionsに任せましょう。

6月30日の締切に間に合わなかったら?

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

Script Editorアプリ自体を削除できますか?

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

今週やること

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

1. Customizations Reportを取得する。 Settings → Checkout → Customizations Reportへ行き、エクスポートしてください。これが移行バックログです。

2. 開発環境をセットアップする。 Node 18+、Shopify CLI、そして必要ならRustをインストールします。shopify versionが動くことを確認してください。所要時間は30分です。

3. ひとつの小さなFunctionをスキャフォールドして、開発用ストアに出す。 いちばん簡単なScriptを選んでください。たいていは支払い非表示ルールです。エンドツーエンドで移行します。本番に行かなくても、ツールチェーンの検証になります。

4. 今後8週間のカレンダーを押さえる。 移行はスプリントの合間の空き時間では進みません。たとえば毎週火曜と木曜の午後のように、繰り返し枠を確保し、それをリリース扱いにしてください。

これまで見てきた移行チームでは、傾向は一貫しています。2週間の停滞、3週間の実作業、1週間の整理。合計6週間です。あなたには10週間あります。余裕はあります。その余裕は、開始を先延ばしにするのではなく、コードレビューとQAに使ってください。

そして、チェックアウトロジックが片付いたら、多くのチームが次に取り組むのはPost-Purchaseです。注文後の住所変更、商品の交換、注文後の割引追加などです。そこがロードマップにあるなら(高注文量の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: Deadline, Migration, and What's Broken How to Let Customers Cancel Orders on Shopify Shopify Legacy Customer Accounts Are Deprecated 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 Shopify AI Toolkit Guide 2026: Agents, MCP, and UCP Explained Shopify Sidekick vs Your Agency: The 2026 Scorecard for Plus Stores Shopify B2B 2026 Complete Guide Shopify Order Management Guide 2026 Shopify Advanced to Plus 2026 Migration Playbook