Admin UI エクステンション
Admin UI エクステンションを使用すると、Shopify 管理画面にカスタムインターフェースを直接埋め込むことができます。マーチャントにアプリのフルページへの移動を強制する代わりに、商品ページ、注文詳細ビュー、顧客プロフィールなど、マーチャントが必要とする場所に正確に機能を表示できます。これらのエクステンションは、Shopify の UI コンポーネントにアクセスできるサンドボックス環境内で Shopify 管理画面にレンダリングされます。
エクステンションタイプの概要
Shopify は複数のカテゴリの Admin UI エクステンションを提供しています:
| エクステンションタイプ | 表示場所 | ユースケース |
|---|---|---|
| Admin Action | リソースページのアクションメニュー | コンテキストメニューからワークフローをトリガー |
| Admin Block | リソース詳細ページに埋め込み | Shopify データと並んでアプリデータを表示 |
| Product Configuration | 商品作成/編集フォーム | 商品設定にカスタムフィールドを追加 |
| Navigation | Admin サイドバー | アプリセクションへのディープリンク |
| Bulk Action | リソース一覧ページ | 複数のアイテムに一括操作 |
Admin アクションエクステンション
Admin アクションはリソースページの「その他のアクション」ドロップダウンに表示されます。エクステンションがレンダリングされるモーダルオーバーレイが開きます。
Admin アクションの作成
# Generate an admin action extension
shopify app generate extension --template admin_action --name ai-description-generator
これによりエクステンションのスキャフォールドが作成されます:
extensions/ai-description-generator/
├── src/
│ └── ActionExtension.tsx
├── locales/
│ └── en.default.json
├── shopify.extension.toml
└── package.json
エクステンション設定
# extensions/ai-description-generator/shopify.extension.toml
api_version = "2025-01"
[[extensions]]
type = "admin_action"
name = "Generate AI Description"
handle = "ai-description-generator"
[[extensions.targeting]]
module = "./src/ActionExtension.tsx"
target = "admin.product-details.action.render"
target フィールドはエクステンションの表示場所を決定します。一般的なターゲットには以下が含まれます:
admin.product-details.action.render-- 商品詳細ページのアクションadmin.order-details.action.render-- 注文詳細ページのアクションadmin.customer-details.action.render-- 顧客詳細ページのアクションadmin.product-index.action.render-- 商品一覧ページのアクション
アクション UI の構築
Admin エクステンションは Shopify のリモート UI コンポーネント(サンドボックス化されたエクステンション環境向けに設計された Polaris コンポーネントのサブセット)を使用します:
// extensions/ai-description-generator/src/ActionExtension.tsx
import { useEffect, useState } from 'react';
import {
reactExtension,
useApi,
AdminAction,
BlockStack,
Button,
InlineStack,
ProgressIndicator,
Select,
Text,
TextField,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.product-details.action.render';
export default reactExtension(TARGET, () => <GenerateDescriptionAction />);
function GenerateDescriptionAction() {
const { data, close, intents } = useApi(TARGET);
const [tone, setTone] = useState('professional');
const [generatedDescription, setGeneratedDescription] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const productId = data.selected?.[0]?.id;
async function handleGenerate() {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/generate-description', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
productId,
tone,
}),
});
if (!response.ok) {
throw new Error('Failed to generate description');
}
const result = await response.json();
setGeneratedDescription(result.description);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
}
async function handleApply() {
try {
// Use the Admin API directly from the extension
const response = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
mutation updateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
descriptionHtml
}
userErrors {
field
message
}
}
}
`,
variables: {
input: {
id: productId,
descriptionHtml: generatedDescription,
},
},
}),
});
close();
} catch (err) {
setError('Failed to update product');
}
}
return (
<AdminAction
title="Generate AI Description"
primaryAction={
<Button
onPress={handleApply}
disabled={!generatedDescription}
>
Apply Description
</Button>
}
secondaryAction={
<Button onPress={() => close()}>Cancel</Button>
}
>
<BlockStack gap="large">
<Select
label="Tone"
value={tone}
onChange={setTone}
options={[
{ label: 'Professional', value: 'professional' },
{ label: 'Casual & Friendly', value: 'casual' },
{ label: 'Luxurious', value: 'luxurious' },
{ label: 'Technical', value: 'technical' },
{ label: 'Playful', value: 'playful' },
]}
/>
<Button onPress={handleGenerate} disabled={loading}>
{loading ? 'Generating...' : 'Generate Description'}
</Button>
{loading && <ProgressIndicator size="small" />}
{error && (
<Text tone="critical">{error}</Text>
)}
{generatedDescription && (
<BlockStack gap="small">
<Text variant="headingSm">Generated Description</Text>
<TextField
label="Description"
value={generatedDescription}
onChange={setGeneratedDescription}
multiline={6}
autoComplete="off"
/>
</BlockStack>
)}
</BlockStack>
</AdminAction>
);
}
Admin エクステンションはサンドボックス環境で実行されます。window、document へのアクセスや、任意の npm パッケージの使用はできません。Shopify が提供する UI コンポーネントと API のみ利用可能です。バックエンドを呼び出す必要がある場合は、相対 URL で fetch() を使用してください。Shopify がリクエストをアプリサーバーにプロキシします。
Admin ブロックエクステンション
Admin ブロックはリソース詳細ページに表示される永続的な UI パネルです。アクション(モーダルとして開く)とは異なり、ブロックは常に表示され、リアルタイムで更新されます。
// extensions/loyalty-block/src/BlockExtension.tsx
import { useEffect, useState } from 'react';
import {
reactExtension,
useApi,
AdminBlock,
BlockStack,
InlineStack,
Text,
Badge,
Divider,
ProgressIndicator,
Heading,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-details.block.render';
export default reactExtension(TARGET, () => <LoyaltyBlock />);
function LoyaltyBlock() {
const { data } = useApi(TARGET);
const [loyaltyData, setLoyaltyData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const customerId = data.selected?.[0]?.id;
useEffect(() => {
async function fetchLoyaltyData() {
try {
const response = await fetch(
`/api/loyalty/customer?id=${encodeURIComponent(customerId)}`
);
const result = await response.json();
setLoyaltyData(result);
} catch (err) {
console.error('Failed to fetch loyalty data:', err);
} finally {
setLoading(false);
}
}
if (customerId) {
fetchLoyaltyData();
}
}, [customerId]);
if (loading) {
return (
<AdminBlock title="Loyalty Program">
<ProgressIndicator size="small" />
</AdminBlock>
);
}
if (!loyaltyData) {
return (
<AdminBlock title="Loyalty Program">
<Text>Customer is not enrolled in the loyalty program.</Text>
</AdminBlock>
);
}
return (
<AdminBlock title="Loyalty Program">
<BlockStack gap="base">
<InlineStack gap="base" blockAlignment="center">
<Text variant="headingSm">Tier:</Text>
<Badge tone={loyaltyData.tier === 'Gold' ? 'warning' : 'info'}>
{loyaltyData.tier}
</Badge>
</InlineStack>
<Divider />
<InlineStack gap="large">
<BlockStack gap="tight">
<Text variant="bodySm" tone="subdued">Points Balance</Text>
<Heading>{loyaltyData.pointsBalance.toLocaleString()}</Heading>
</BlockStack>
<BlockStack gap="tight">
<Text variant="bodySm" tone="subdued">Lifetime Points</Text>
<Heading>{loyaltyData.lifetimePoints.toLocaleString()}</Heading>
</BlockStack>
<BlockStack gap="tight">
<Text variant="bodySm" tone="subdued">Redemptions</Text>
<Heading>{loyaltyData.totalRedemptions}</Heading>
</BlockStack>
</InlineStack>
<Divider />
<Text variant="bodySm" tone="subdued">
Member since {new Date(loyaltyData.enrolledAt).toLocaleDateString()}
</Text>
</BlockStack>
</AdminBlock>
);
}
商品設定エクステンション
商品設定エクステンションは、商品の作成と編集体験にカスタムフィールドを追加します。追加の商品メタデータが必要なアプリに最適です。
// extensions/product-ai-config/src/ProductConfig.tsx
import { useState } from 'react';
import {
reactExtension,
useApi,
AdminBlock,
BlockStack,
Checkbox,
Select,
Text,
TextField,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.product-details.configuration.render';
export default reactExtension(TARGET, () => <ProductAIConfig />);
function ProductAIConfig() {
const { data, applyMetafieldsChange } = useApi(TARGET);
const [autoDescription, setAutoDescription] = useState(false);
const [targetAudience, setTargetAudience] = useState('general');
const [keywords, setKeywords] = useState('');
async function handleAutoDescriptionToggle(checked: boolean) {
setAutoDescription(checked);
await applyMetafieldsChange({
type: 'updateMetafield',
namespace: 'ai_config',
key: 'auto_description',
value: String(checked),
valueType: 'boolean',
});
}
async function handleAudienceChange(value: string) {
setTargetAudience(value);
await applyMetafieldsChange({
type: 'updateMetafield',
namespace: 'ai_config',
key: 'target_audience',
value,
valueType: 'single_line_text_field',
});
}
async function handleKeywordsChange(value: string) {
setKeywords(value);
await applyMetafieldsChange({
type: 'updateMetafield',
namespace: 'ai_config',
key: 'seo_keywords',
value,
valueType: 'single_line_text_field',
});
}
return (
<AdminBlock title="AI Configuration">
<BlockStack gap="base">
<Text variant="bodySm" tone="subdued">
Configure how AI tools interact with this product.
</Text>
<Checkbox
label="Auto-generate description on save"
checked={autoDescription}
onChange={handleAutoDescriptionToggle}
/>
<Select
label="Target Audience"
value={targetAudience}
onChange={handleAudienceChange}
options={[
{ label: 'General', value: 'general' },
{ label: 'Young Adults (18-25)', value: 'young_adults' },
{ label: 'Professionals', value: 'professionals' },
{ label: 'Parents', value: 'parents' },
{ label: 'Enthusiasts', value: 'enthusiasts' },
]}
/>
<TextField
label="SEO Keywords"
value={keywords}
onChange={handleKeywordsChange}
helpText="Comma-separated keywords to emphasize in AI-generated content"
autoComplete="off"
/>
</BlockStack>
</AdminBlock>
);
}
エクステンションのデプロイ
エクステンションは Shopify CLI を使用してアプリと一緒にデプロイされます:
# Development mode -- live reload
shopify app dev
# Deploy to production
shopify app deploy
# Deploy only extensions (skip app code)
shopify app deploy --reset
エクステンションのバージョニング
各デプロイでエクステンションの新しいバージョンが作成されます。Shopify がロールアウトを管理します:
# Check extension status
shopify app versions list
# Release a specific version
shopify app release --version 1.2.0
開発中、shopify app dev はトンネルを起動し、エクステンションのホットリロードを有効にします。エクステンションコードの変更は再デプロイなしで Shopify Admin にすぐに反映されます。テストには開発ストア(本番ストアではなく)を使用してください。
デプロイ前チェックリスト
Admin UI エクステンションを本番環境にデプロイする前に:
- 複数の画面サイズでテストする -- Admin はデスクトップ、タブレット、モバイルデバイスで使用されます
- ローディング状態を処理する -- データ取得中は常にスピナーまたはスケルトンを表示してください
- エラー状態を処理する -- ネットワーク障害は起こります。有用なメッセージとリトライオプションを表示してください
- レート制限を尊重する -- Admin API にはレート制限があります。可能な限りレスポンスをキャッシュしてください
- 文字列をローカライズする -- すべてのユーザー向けテキストに
locales/ディレクトリを使用してください - アクセシビリティを確認する -- エクステンションがキーボードナビゲーション可能でスクリーンリーダーに対応していることを確認してください
Admin UI エクステンションの最大バンドルサイズは 512 KB(圧縮後)です。依存関係は最小限に保ってください。大きなライブラリをバンドルしないでください。独自のコンポーネントライブラリを持ち込む代わりに、Shopify が提供する UI コンポーネントを使用してください。
チェックアウトエクステンション に進んで、カスタム UI、決済ロジック、配送ルールで Shopify チェックアウト体験をカスタマイズする方法を学びましょう。