アプリ標準のボタンを使わず、テーマ側で作った独自のハートボタンから同じお気に入り状態を操作したい場合の手順です。DOMの直接操作や標準ボタンの裏側クリックは不要で、ここに書いた方法だけがサポート対象です。
はじめる前に
- アプリ埋め込み「WISHLIST MANAGER」が有効になっている必要があります(設置手順の手順1)
- 連携の対象は商品単位です。バリアント(色・サイズ)単位の保存には対応していません
- ここで説明する属性名・API名は公開仕様として固定します。変更が必要になった場合は互換を保ちます
マークアップ連携(全プラン)
任意の要素に次のdata属性(data-wishlist-button と data-product-handle が必須、data-product-id は推奨)を付けるだけで、その要素がお気に入りボタンになります。
<button
type="button"
data-wishlist-button
data-product-id="{{ product.id }}"
data-product-handle="{{ product.handle }}"
aria-pressed="false"
aria-label="お気に入りに追加"
>
<svg><!-- 任意のハートアイコン --></svg>
</button>
| 属性 | 必須 | 説明 |
|---|---|---|
data-wishlist-button | 必須 | この要素をお気に入りボタンとして扱う印です。値は空でかまいません |
data-product-id | 推奨 | 商品の数値ID({{ product.id }})。あると初回タップが1リクエスト早くなります |
data-product-handle | 必須 | 商品ハンドル({{ product.handle }})。お気に入り一覧ページの表示に使います |
動作
- クリックで追加/削除がトグルされます。クリック処理はdocumentレベルで委譲しているので、設置場所は問いません。商品詳細ページ、モバイル下部に固定するStickyボタン、トップページ・コレクション・検索結果・関連商品の商品カードのどこでも同じ書き方で動きます
- お気に入り済みの要素には class
is-activeとaria-pressed="true"が付きます(未登録では外れます)。見た目はこの2つをフックにCSSで作ってください。アプリのCSSは独自ボタンには一切当たりません - 同じ商品を指すボタンがページ内に複数あっても、すべて同時に状態が更新されます
- 「もっと見る」や無限スクロール、クイックビューなどで後から挿入されたボタンも自動で状態が反映されます(アプリ側で監視しています)
ラベルの切り替え(任意)
テキスト付きのボタンにする場合、次の属性を持つ子要素を入れると、状態に応じて表示が切り替わります。
<span data-wishlist-label-add>お気に入りに追加</span>
<span data-wishlist-label-remove hidden>お気に入り済み</span>
CSSの例
.my-heart[aria-pressed="true"] svg path {
fill: currentColor;
}
JavaScript API(Standardプラン)
Standardプランでは、状態の取得や追加・削除をコードから行う window.WishlistManager と、変更を知らせるイベントが使えます。Freeプランでもオブジェクトは存在しますが enabled が false で、各メソッドはエラーになります(黙って何も起きない、という状態を避けるためです)。
アプリのスクリプトは defer で読み込まれるため、DOMContentLoaded 以降に参照してください。
document.addEventListener("DOMContentLoaded", () => {
const wl = window.WishlistManager;
if (!wl || !wl.enabled) return;
wl.has("8528950001234"); // => true / false(ローカルキャッシュから同期的に返します)
});
| メソッド | 戻り値 | 説明 |
|---|---|---|
enabled | boolean | APIが使えるプランかどうか |
has(productId) | boolean | お気に入り済みか。ローカルキャッシュから同期的に返します |
items() | Promise<{ productId, productHandle }[]> | サーバーから最新の一覧を取得します |
add(productId, productHandle?) | Promise<{ status }> | 追加。status は added / exists / limit_reached / error |
remove(productId) | Promise<{ status }> | 削除。status は removed / error |
toggle(productId, productHandle?) | Promise<{ status }> | 状態に応じて追加または削除 |
refresh() | Promise<void> | ページ内の全ボタンの表示を再描画します |
productIdは数値のShopify商品IDです(文字列でも数値でもかまいません)productHandleは、その商品のdata-wishlist-buttonがページ内に無い場合に必要です。ページ内にあればそこから補完されます- 追加・削除は呼び出し順に直列で処理されるので、連続して呼んでもすべて結果が返ります
変更イベント
お気に入りが変わるたびに document へ wishlist-manager:change が発火します。独自UIのカウンターやバッジを更新する用途に使えます。
document.addEventListener("wishlist-manager:change", (event) => {
const { type, productId, productHandle, items } = event.detail;
// type: "added" | "removed" | "sync"
// items: 変更後の全件 [{ productId, productHandle }, ...]
document.querySelector(".my-wishlist-count").textContent = items.length;
});
sync は、サーバーとの同期(セッション初回・ログイン直後の引き継ぎ・items() 呼び出し)で一覧が更新されたときに発火します。
プラン変更の反映
Standardへの切り替え後、ストアフロント側のAPIは管理画面でアプリを開いた時点で有効になります(プラン承認後は自動的にアプリの画面へ戻るので、通常はそのまま有効になります)。有効になっているかは window.WishlistManager.enabled で確認できます。
仕様上の制限と挙動
| 項目 | 挙動 |
|---|---|
| 保存単位 | 商品単位(バリアント単位ではありません) |
| ゲストの引き継ぎ | 同じブラウザでログインすると、初回アクセス時にアカウント側へ自動統合されます。別端末・別ブラウザで保存したゲスト分は対象外です |
| 上限(Free) | ストア全体で追加500件/月(毎月リセット)。上限到達時は limit_reached が返り、閲覧・削除・既存の保存には影響しません |
| 上限(Standard) | 追加件数の月間上限なし。不正なスクリプト対策として、お客様1人あたりの保存件数に250件の上限があります |
| 売り切れ商品 | お気に入り一覧にそのまま表示されます(在庫復活時の購入導線になります) |
| 非公開・削除された商品 | お気に入り一覧には表示されません(エラーは出ません)。非公開の商品は再公開すると再び表示されます |
| リクエスト制限 | 追加・削除は1分あたり30回/IPまでです。超えた場合は error が返ります |
うまくいかないとき
独自ボタンを押しても反応しない
アプリ埋め込みが無効か、data-wishlist-button 属性が付いていません。ブラウザの開発者ツールで document.querySelector("[data-wishlist-config]") が存在するか確認してください。無ければ埋め込みが無効です。
window.WishlistManager が undefined
アプリのスクリプトの読み込み前に参照しています。DOMContentLoaded 以降で参照してください。
メソッドを呼ぶと「requires the Standard plan」のエラーになる
Freeプランです。Standardプランに切り替えたあと、管理画面でアプリを一度開いてください。
このページは役に立ちましたか?
Shopify App Store のレビューは、同じ課題を持つ他のストア運営者がアプリを見つける手がかりになります。良かった点も、足りない点も、そのまま書いてください。
レビューはShopify App Store上に公開されます。書き換え・削除はご自身で行えます。