shopify-checkout-extensions について
Shopifyアプリ開発者向けに、チェックアウトUIブロックとShopify Functionsによるバックエンドルールの実装を案内するガイドです。非推奨の`checkout.liquid`カスタマイズに代わる方法として、構築、設定、テスト、デプロイを扱います。
主な機能
- 01Checkout UI拡張の生成
- 02Shopify Functionsによるルール実装
- 03拡張ターゲットと設定の構成
- 04拡張のローカルプレビューとデプロイ
- 05チェックアウトのサンドボックスと実行制限への対応
活用シーン
- 01チェックアウトにギフトメッセージ欄を追加
- 02カート内容に応じて配送方法を絞り込む
- 03注文に独自の割引ルールを適用
- 04チェックアウト完了前に注文内容を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill shopify-checkout-extensionsインストール後、Claude Code で/shopify-checkout-extensionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/checkout-metafield-persistence-with-ui-e/criteria.jsontask.mdevals/configurable-ui-extension-with-settings-/criteria.jsontask.mdevals/shopify-function-configuration-and-wasm-/criteria.jsontask.md詳細
Shopify チェックアウト拡張機能
概要
Shopify チェックアウト拡張機能を使うと、チェックアウトテンプレートをフォークせずに、Shopify のチェックアウト内にアプリのカスタム UI ブロックを表示できます。Shopify Functions を使うと、割引、配送、決済方法、注文の検証といったバックエンドロジックを、Shopify のインフラストラクチャ内で実行されるカスタム WebAssembly モジュールに置き換えられます。これらを組み合わせることで、非推奨となった checkout.liquid によるカスタマイズ方法を置き換え、Shopify Plus とそれ以外のストア(UI 拡張機能)、または Shopify Plus のストアのみ(一部の Functions ターゲット)で利用できます。
このスキルを使用する場面
- チェックアウトにカスタム UI ブロック(アップセル、信頼性バッジ、ギフトメッセージ欄、保証オプションなど)を追加するとき
- Shopify 標準の割引ルールを超えるカスタム割引ロジック(段階的な割引、B2B 価格設定など)を実装するとき
- カートの内容に基づいて、カスタムの配送方法のフィルタリングや名称変更を行うとき
- 特定の顧客に対して決済方法を非表示にしたり、名称を変更したりするカスタム決済機能を構築するとき
- チェックアウト完了前に注文内容を検証するとき(数量制限、地域制限など)
- Shopify Plus ストアで非推奨となった
checkout.liquidのカスタマイズを置き換えるとき
基本的な手順
-
Shopify CLI で拡張機能のひな形を作成する
# Inside an existing Shopify app directory shopify app generate extension # Choose: Checkout UI extension OR Shopify Function # Name: my-checkout-extensionこれにより、
src/index.tsx(UI)またはsrc/index.ts(Function)を含むextensions/my-checkout-extension/ディレクトリが作成されます。 -
チェックアウト UI 拡張機能を構築する
チェックアウト UI 拡張機能では、
@shopify/ui-extensions-react/checkoutの React に似たコンポーネント API を使用します。// extensions/order-upsell/src/index.tsx import { reactExtension, useCartLines, useApplyCartLinesChange, useSettings, BlockStack, Button, Image, Text, InlineStack, Divider, } from "@shopify/ui-extensions-react/checkout"; const CheckoutBlock = reactExtension( "purchase.checkout.block.render", () => <OrderUpsell /> ); export { CheckoutBlock }; function OrderUpsell() { const cartLines = useCartLines(); const applyCartLinesChange = useApplyCartLinesChange(); const { upsell_variant_id: upsellVariantId } = useSettings(); // Only show upsell if a variant is configured and cart doesn't already contain it if (!upsellVariantId) return null; const alreadyInCart = cartLines.some( (line) => line.merchandise.id === upsellVariantId ); if (alreadyInCart) return null; const handleAddUpsell = async () => { await applyCartLinesChange({ type: "addCartLine", merchandiseId: upsellVariantId, quantity: 1, }); }; return ( <BlockStack spacing="base"> <Divider /> <InlineStack blockAlignment="center" spacing="base"> <Image source="https://cdn.shopify.com/s/files/..." aspectRatio={1} /> <BlockStack> <Text emphasis="bold">Add a gift bag for $3.99</Text> <Text appearance="subdued">Beautiful packaging for your order</Text> </BlockStack> <Button onPress={handleAddUpsell}>Add</Button> </InlineStack> </BlockStack> ); } -
shopify.extension.tomlで拡張機能を設定するapi_version = "2025-01" [[extensions]] type = "ui_extension" name = "Order Upsell" handle = "order-upsell" [[extensions.targeting]] module = "./src/index.tsx" target = "purchase.checkout.block.render" [extensions.settings] [[extensions.settings.fields]] key = "upsell_variant_id" type = "variant_reference" name = "Upsell Product Variant" -
カスタム割引用の Shopify Function を構築する
Shopify Functions は WebAssembly にコンパイルされます。Rust または JavaScript を使用します。
// extensions/volume-discount/src/index.ts import type { RunInput, FunctionRunResult, CartLineInput, } from "../generated/api"; const NO_CHANGES: FunctionRunResult = { discounts: [], discountApplicationStrategy: "FIRST" }; export function run(input: RunInput): FunctionRunResult { const { cart } = input; // Calculate total quantity across all lines const totalQuantity = cart.lines.reduce( (sum, line) => sum + line.quantity, 0 ); // Tiered volume discount let discountPercent = 0; if (totalQuantity >= 20) discountPercent = 20; else if (totalQuantity >= 10) discountPercent = 10; else if (totalQuantity >= 5) discountPercent = 5; if (discountPercent === 0) return NO_CHANGES; return { discounts: [ { value: { percentage: { value: discountPercent.toString() }, }, targets: [{ orderSubtotal: { excludedVariantIds: [] } }], message: `${discountPercent}% volume discount (${totalQuantity} items)`, }, ], discountApplicationStrategy: "FIRST", }; }Function の
shopify.extension.toml:api_version = "2025-01" [[extensions]] type = "function" name = "Volume Discount" handle = "volume-discount" runtime = "javascript" [[extensions.input.variables]] name = "cart" type = "Cart" [extensions.build] command = "npm run build" path = "dist/index.wasm" -
拡張機能をテストしてデプロイする
# Run local dev preview (UI extension hot-reloads in checkout) shopify app dev # Open the checkout preview URL shown in terminal # Deploy all extensions to Shopify shopify app deployデプロイ後、管理画面で[チェックアウト]→[カスタマイズ]に移動し、チェックアウトテンプレートに UI 拡張機能のブロックを追加します。Functions は、管理画面の[割引]からその Function を使った割引を作成すると有効になります。
例
useApplyMetafieldsChange を使ったギフトメッセージ欄
import {
reactExtension,
TextField,
useApplyMetafieldsChange,
useMetafield,
BlockStack,
Text,
} from "@shopify/ui-extensions-react/checkout";
export default reactExtension(
"purchase.checkout.shipping-option-list.render-after",
() => <GiftMessage />
);
function GiftMessage() {
const giftMessage = useMetafield({ namespace: "custom", key: "gift_message" });
const applyMetafieldsChange = useApplyMetafieldsChange();
return (
<BlockStack spacing="tight">
<Text emphasis="bold">Gift message (optional)</Text>
<TextField
label="Message"
value={giftMessage?.value ?? ""}
multiline={3}
onChange={(value) =>
applyMetafieldsChange({
type: "updateMetafield",
namespace: "custom",
key: "gift_message",
valueType: "string",
value,
})
}
/>
</BlockStack>
);
}
決済カスタマイズ Function(海外注文で代金引換を非表示にする)
// extensions/payment-customization/src/index.ts
import type { RunInput, FunctionRunResult } from "../generated/api";
export function run(input: RunInput): FunctionRunResult {
const country = input.cart.buyerIdentity?.countryCode;
// Hide "Cash on Delivery" for non-domestic orders
const hideOperations = input.paymentMethods
.filter((pm) => pm.name.toLowerCase().includes("cash on delivery") && country !== "US")
.map((pm) => ({
hide: { paymentMethodId: pm.id },
}));
return { operations: hideOperations };
}
ベストプラクティス
- 配置の柔軟性を最大限に高めるには、
purchase.checkout.block.renderターゲットを使用する。マーチャントはチェックアウトエディターでブロックを任意の位置にドラッグできます。 - Function の実行時間を 5ms 未満に抑える。Shopify は実行時間に厳しい制限を設けています。Functions 内でのネットワーク呼び出しは避け、設定にはメタフィールドまたは Function の入力変数を使用します。
useSettings()フックを使って、拡張機能の設定スキーマでマーチャントが設定した値を読み取る。拡張機能のコードに ID をハードコードせずに済みます。- UI 拡張機能では DOM を読み取ったり、ブラウザー API を使用したりしない。これらは DOM にアクセスできないサンドボックス化された Worker 環境で実行されます。
@shopify/ui-extensions-react/checkoutのコンポーネントのみを使用する。拡張機能のサンドボックスでは、ネイティブ HTML やその他の UI ライブラリは使用できません。- 決済および配送の Functions は、実際のチェックアウトセッションでテストする。ローカルの開発プレビューで動作するのは UI 拡張機能のみです。Functions のテストにはデプロイが必要です。
- 拡張機能の API バージョンを固定する。新しい API を利用しつつ下位互換性を保てるよう、
shopify.extension.tomlのapi_versionを更新します。 - 非同期処理では読み込み状態を表示する。
useApplyCartLinesChangeは非同期処理のため、変更処理中はスピナーを表示します。
よくある問題
| 問題 | 解決策 |
|---|---|
| チェックアウトエディターに拡張機能が表示されない | 拡張機能がデプロイされていること(shopify app deploy)と、Admin → Checkout で正しいチェックアウトテンプレートが選択されていることを確認してください |
Function が FUNCTION_EXECUTION_TIMEOUT を返す | 設定を実行時のロジックから Function の入力メタフィールドに移してください。大規模なカタログに対する複雑なループは避けてください |
カートの更新後に useCartLines が古いデータを返す | カート行を再度読み込む前にチェックアウトの再評価を待つため、applyCartLinesChange が返す promise を使用してください |
| 拡張機能が「Cannot use browser APIs」でクラッシュする | document、window、localStorage の参照を削除してください。拡張機能は Worker サンドボックスで実行されます |
| Discount Function が適用されない | Admin → Discounts で Function ベースのディスカウントが有効になっていることと、顧客が適用資格の条件を満たしていることを確認してください |
| Checkout UI 拡張機能の設定が保存されない | 設定フィールドには、拡張機能のコード内で useSettings() が参照するキーと一致する handle 値が必要です |
関連スキル
- @shopify-app-development
- @shopify-storefront-api
- @shopify-metafields
- @checkout-flow-optimization
- @shopify-admin-api