Web Pixel エクステンション
Web Pixel エクステンションは、顧客イベントトラッキングとアナリティクスに対する Shopify のプライバシーファーストアプローチです。ストアフロントに任意の JavaScript を挿入する代わりに(パフォーマンス低下とプライバシー違反のリスクがある)、Web Pixel は顧客データへのアクセスが制御されたサンドボックス環境で実行されます。標準化された EC イベントを受信し、顧客の同意設定を尊重しながらアナリティクスプラットフォームにデータを転送できます。
従来のトラッキングスクリプト(Google Tag Manager、Facebook Pixel など)はページ全体にアクセスでき、ストアフロントを遅くし、適切な同意なしにデータを収集することがあります。Shopify の Web Pixel フレームワークはこれら3つの問題すべてに対処します:パフォーマンスのためのサンドボックス実行、一貫性のための標準化イベント、プライバシーコンプライアンスのための組み込み同意管理。
Web Pixel の仕組み
Web Pixel はサンドボックス化された Web Worker 内で実行されます。DOM にアクセスしたり、Cookie を読み取ったり、ページと直接対話することはできません。代わりに、顧客がブラウズ、カートへのアイテム追加、購入完了を行う際に Shopify が発行する標準化されたカスタマーイベントのストリームをサブスクライブします。
カスタマーイベント
Shopify は標準化された EC イベントの包括的なセットを発行します。ピクセルは必要なイベントをサブスクライブします:
| イベント | トリガー | 主要データ |
|---|---|---|
page_viewed | 顧客が任意のページを閲覧 | URL、ページタイトル、リファラー |
product_viewed | 顧客が商品ページを閲覧 | 商品 ID、タイトル、価格、バリアント |
collection_viewed | 顧客がコレクションを閲覧 | コレクション ID、タイトル |
search_submitted | 顧客が検索を実行 | 検索クエリ、結果件数 |
product_added_to_cart | アイテムがカートに追加 | 商品、バリアント、数量、カート合計 |
product_removed_from_cart | アイテムがカートから削除 | 商品、バリアント、数量 |
cart_viewed | 顧客がカートページを閲覧 | カート内容、合計 |
checkout_started | 顧客がチェックアウトを開始 | カート内容、合計、割引コード |
checkout_address_info_submitted | 住所フォームが完了 | 国、都道府県(PII なし) |
checkout_shipping_info_submitted | 配送方法が選択 | 配送方法、コスト |
payment_info_submitted | 決済情報が入力 | 決済方法タイプ(カード詳細なし) |
checkout_completed | 購入が完了 | 注文 ID、合計、明細、通貨 |
カスタマーイベントにはデフォルトで匿名化されたデータが含まれます。メールアドレスやフルネームなどの個人識別情報(PII)は、顧客が明示的な同意を与えた場合のみ含まれます。ピクセルは PII フィールドにアクセスする前に event.data.customer.consent を確認する必要があります。
Web Pixel エクステンションの作成
# Generate a web pixel extension
shopify app generate extension --template web_pixel --name custom-analytics
extensions/custom-analytics/
├── src/
│ └── index.ts
├── shopify.extension.toml
└── package.json
エクステンション設定
# extensions/custom-analytics/shopify.extension.toml
api_version = "2025-01"
[[extensions]]
type = "web_pixel_extension"
name = "Custom Analytics Pixel"
handle = "custom-analytics"
[extensions.settings]
[[extensions.settings.fields]]
key = "analytics_endpoint"
type = "single_line_text_field"
name = "Analytics Endpoint URL"
description = "The URL where event data will be sent"
[[extensions.settings.fields]]
key = "api_key"
type = "single_line_text_field"
name = "API Key"
description = "Your analytics platform API key"
[[extensions.settings.fields]]
key = "track_search"
type = "boolean"
name = "Track Search Events"
description = "Enable tracking of search queries"
カスタムピクセルの実装
ピクセルのエントリーポイントはイベントをサブスクライブして処理します。主要な EC イベントをトラッキングする完全な実装を以下に示します:
// extensions/custom-analytics/src/index.ts
import { register } from '@shopify/web-pixels-extension';
register(({ analytics, browser, settings, init }) => {
const endpoint = settings.analytics_endpoint;
const apiKey = settings.api_key;
const trackSearch = settings.track_search === 'true';
// Helper to send events to your analytics backend
async function sendEvent(eventName: string, payload: Record<string, any>) {
try {
await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
},
body: JSON.stringify({
event: eventName,
timestamp: new Date().toISOString(),
shopId: init.data.shop.id,
payload,
}),
// Use keepalive to ensure the request completes
// even if the customer navigates away
keepalive: true,
});
} catch (err) {
// Silently fail -- do not disrupt the customer experience
console.error(`Failed to send ${eventName} event:`, err);
}
}
// Track page views
analytics.subscribe('page_viewed', (event) => {
sendEvent('page_view', {
url: event.data.url,
title: event.data.pageTitle,
referrer: event.data.referrer,
});
});
// Track product views
analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendEvent('product_view', {
productId: product?.product?.id,
productTitle: product?.product?.title,
variantId: product?.id,
variantTitle: product?.title,
price: product?.price?.amount,
currency: product?.price?.currencyCode,
});
});
// Track add to cart
analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendEvent('add_to_cart', {
productId: cartLine?.merchandise?.product?.id,
productTitle: cartLine?.merchandise?.product?.title,
variantId: cartLine?.merchandise?.id,
quantity: cartLine?.quantity,
price: cartLine?.merchandise?.price?.amount,
currency: cartLine?.merchandise?.price?.currencyCode,
});
});
// Track cart removals
analytics.subscribe('product_removed_from_cart', (event) => {
const cartLine = event.data.cartLine;
sendEvent('remove_from_cart', {
productId: cartLine?.merchandise?.product?.id,
variantId: cartLine?.merchandise?.id,
quantity: cartLine?.quantity,
});
});
// Track checkout started
analytics.subscribe('checkout_started', (event) => {
const checkout = event.data.checkout;
sendEvent('begin_checkout', {
totalPrice: checkout?.totalPrice?.amount,
currency: checkout?.totalPrice?.currencyCode,
lineItemCount: checkout?.lineItems?.length,
discountCodes: checkout?.discountApplications?.map(
(d: any) => d.title
),
});
});
// Track purchase completed
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendEvent('purchase', {
orderId: checkout?.order?.id,
totalPrice: checkout?.totalPrice?.amount,
subtotalPrice: checkout?.subtotalPrice?.amount,
totalTax: checkout?.totalTax?.amount,
currency: checkout?.totalPrice?.currencyCode,
lineItems: checkout?.lineItems?.map((item: any) => ({
productId: item.variant?.product?.id,
productTitle: item.variant?.product?.title,
variantId: item.variant?.id,
quantity: item.quantity,
price: item.variant?.price?.amount,
})),
shippingRate: {
price: checkout?.shippingLine?.price?.amount,
},
});
});
// Conditionally track search
if (trackSearch) {
analytics.subscribe('search_submitted', (event) => {
sendEvent('search', {
query: event.data.searchResult?.query,
resultCount: event.data.searchResult?.productVariants?.length ?? 0,
});
});
}
// Track collection views
analytics.subscribe('collection_viewed', (event) => {
sendEvent('collection_view', {
collectionId: event.data.collection?.id,
collectionTitle: event.data.collection?.title,
});
});
});
プライバシー準拠のアナリティクス
Shopify の Web Pixel フレームワークは顧客プライバシー同意システムと統合されています。ピクセルは同意ステータスを尊重する必要があります。
同意対応イベントハンドリング
// Enhanced pixel with consent checking
register(({ analytics, browser, settings, init }) => {
const endpoint = settings.analytics_endpoint;
// Check initial consent status
let analyticsConsent = init.customerPrivacy?.analyticsProcessingAllowed ?? false;
let marketingConsent = init.customerPrivacy?.marketingAllowed ?? false;
// Listen for consent changes (customer updates preferences)
analytics.subscribe('visitor_consent_collected', (event) => {
analyticsConsent = event.data.analyticsAllowed;
marketingConsent = event.data.marketingAllowed;
});
async function sendEvent(
eventName: string,
payload: Record<string, any>,
requiresMarketingConsent: boolean = false
) {
// Always check consent before sending data
if (!analyticsConsent) return;
if (requiresMarketingConsent && !marketingConsent) return;
await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: eventName,
timestamp: new Date().toISOString(),
consent: {
analytics: analyticsConsent,
marketing: marketingConsent,
},
payload,
}),
keepalive: true,
});
}
// Basic analytics events (require analytics consent)
analytics.subscribe('page_viewed', (event) => {
sendEvent('page_view', {
url: event.data.url,
title: event.data.pageTitle,
});
});
// Marketing-related events (require marketing consent)
analytics.subscribe('checkout_completed', (event) => {
// Send conversion data to ad platforms only with marketing consent
sendEvent(
'conversion',
{
orderId: event.data.checkout?.order?.id,
value: event.data.checkout?.totalPrice?.amount,
currency: event.data.checkout?.totalPrice?.currencyCode,
},
true // requires marketing consent
);
});
});
Web Pixel は GDPR、CCPA、その他のプライバシー規制に準拠する必要があります。主な要件:
- 同意なしに PII を収集しない -- イベントデータに含まれていても
- オプトアウトリクエストを尊重する -- 顧客が同意を取り消した場合、すぐにデータ送信を停止する
- データ最小化 -- 必要なものだけを収集する。生のイベントペイロードをサードパーティに転送しない
- 保持期限 -- アナリティクスバックエンドがプライバシーポリシーのタイムラインに従ってデータを削除するようにする
違反すると、アプリが Shopify App Store から削除され、法的責任を負う可能性があります。
サードパーティアナリティクスとの統合
Google Analytics 4
// GA4 integration via Measurement Protocol
register(({ analytics, settings }) => {
const measurementId = settings.ga4_measurement_id;
const apiSecret = settings.ga4_api_secret;
const GA4_ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`;
function sendToGA4(eventName: string, params: Record<string, any>) {
fetch(GA4_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
client_id: generateClientId(), // Generate a privacy-safe client ID
events: [{ name: eventName, params }],
}),
keepalive: true,
});
}
analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendToGA4('view_item', {
currency: product?.price?.currencyCode,
value: parseFloat(product?.price?.amount || '0'),
items: [{
item_id: product?.product?.id,
item_name: product?.product?.title,
item_variant: product?.title,
price: parseFloat(product?.price?.amount || '0'),
}],
});
});
analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendToGA4('add_to_cart', {
currency: cartLine?.merchandise?.price?.currencyCode,
value: parseFloat(cartLine?.merchandise?.price?.amount || '0') * (cartLine?.quantity || 1),
items: [{
item_id: cartLine?.merchandise?.product?.id,
item_name: cartLine?.merchandise?.product?.title,
quantity: cartLine?.quantity,
price: parseFloat(cartLine?.merchandise?.price?.amount || '0'),
}],
});
});
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendToGA4('purchase', {
transaction_id: checkout?.order?.id,
currency: checkout?.totalPrice?.currencyCode,
value: parseFloat(checkout?.totalPrice?.amount || '0'),
tax: parseFloat(checkout?.totalTax?.amount || '0'),
shipping: parseFloat(checkout?.shippingLine?.price?.amount || '0'),
items: checkout?.lineItems?.map((item: any) => ({
item_id: item.variant?.product?.id,
item_name: item.variant?.product?.title,
quantity: item.quantity,
price: parseFloat(item.variant?.price?.amount || '0'),
})),
});
});
});
function generateClientId(): string {
// Generate a random client ID that does not contain PII
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
(c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}
Meta Conversions API
// Meta (Facebook) Conversions API integration
register(({ analytics, settings }) => {
const pixelId = settings.meta_pixel_id;
const accessToken = settings.meta_access_token;
const META_ENDPOINT = `https://graph.facebook.com/v18.0/${pixelId}/events`;
function sendToMeta(eventName: string, customData: Record<string, any>) {
fetch(META_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: [{
event_name: eventName,
event_time: Math.floor(Date.now() / 1000),
action_source: 'website',
custom_data: customData,
}],
access_token: accessToken,
}),
keepalive: true,
});
}
analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendToMeta('ViewContent', {
content_ids: [product?.product?.id],
content_type: 'product',
content_name: product?.product?.title,
value: parseFloat(product?.price?.amount || '0'),
currency: product?.price?.currencyCode,
});
});
analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendToMeta('AddToCart', {
content_ids: [cartLine?.merchandise?.product?.id],
content_type: 'product',
value: parseFloat(cartLine?.merchandise?.price?.amount || '0'),
currency: cartLine?.merchandise?.price?.currencyCode,
});
});
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendToMeta('Purchase', {
content_ids: checkout?.lineItems?.map(
(item: any) => item.variant?.product?.id
),
content_type: 'product',
value: parseFloat(checkout?.totalPrice?.amount || '0'),
currency: checkout?.totalPrice?.currencyCode,
num_items: checkout?.lineItems?.length,
});
});
});
テストとデバッグ
# Start development server with pixel debugging
shopify app dev
# The CLI provides a preview URL with pixel debugging enabled
# Open the browser console to see pixel event logs
デバッグモード
register(({ analytics, settings, init }) => {
const debug = init.data.shop.myshopifyDomain.includes('dev');
function log(eventName: string, data: any) {
if (debug) {
console.log(`[Pixel Debug] ${eventName}:`, JSON.stringify(data, null, 2));
}
}
analytics.subscribe('page_viewed', (event) => {
log('page_viewed', event.data);
// ... normal processing
});
});
ブラウザの Network タブを使用して、ピクセルが正しいエンドポイントにリクエストを送信していることを確認してください。Shopify の開発ツールは、ページで発行されたすべてのイベントと、どのピクセルがそれを受信したかを表示する「Customer Events」デバッガーも提供しています。開発ストアで ?debug_pixels=true クエリパラメータを使用してアクセスできます。
パフォーマンスの考慮事項
- すべての fetch リクエストに
keepalive: trueを使用する -- 顧客がリクエスト中にページを離れてもイベントが送信されるようにします - 可能な場合はイベントをバッチ処理する -- イベントごとに1つのリクエストを送信するのではなく、イベントをバッファして数秒ごとにバッチで送信します
- ペイロードサイズを最小化する -- アナリティクスプラットフォームが必要とするデータのみを含めます
- 同期パターンを避ける -- ピクセル内のすべてをノンブロッキングにしてください
- ネットワーク障害をサイレントに処理する -- トラッキングの失敗が顧客体験に影響を与えないようにしてください
// Event batching implementation
class EventBatcher {
private queue: any[] = [];
private timer: ReturnType<typeof setTimeout> | null = null;
private readonly endpoint: string;
private readonly batchSize: number;
private readonly flushInterval: number;
constructor(endpoint: string, batchSize = 10, flushInterval = 3000) {
this.endpoint = endpoint;
this.batchSize = batchSize;
this.flushInterval = flushInterval;
}
add(event: any) {
this.queue.push(event);
if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.flushInterval);
}
}
private flush() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.batchSize);
fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events: batch }),
keepalive: true,
}).catch(() => {
// Re-queue failed events (with a retry limit)
// In production, add exponential backoff
});
}
}
Web Pixel エクステンションは、Shopify ストアフロントで顧客行動をトラッキングするための安全で高性能かつプライバシー準拠の方法を提供します。Shopify の標準化されたイベントシステムを使用することで、DOM スクレイピングや Cookie トラッキングの脆弱性なしに、すべてのマーチャントで一貫したデータを取得できます。同意管理と組み合わせることで、アナリティクス統合はプライバシーファーストの Web に対応できます。