npx skills add https://github.com/wix/skills --skill wix-cli-site-plugin为 Wix CLI 应用程序创建站点插件扩展。站点插件是自定义元素,可集成到 Wix 业务解决方案(如 Wix Stores、Wix Bookings、Wix eCommerce)内的预定义插槽中,从而扩展其功能和用户体验。
站点所有者可以使用 Wix 编辑器中的插件浏览器将站点插件放置到 UI 插槽中。
创建站点插件时,请按顺序执行以下步骤:
src/extensions/site/plugins/<plugin-name>/HTMLElement 并包含 observedAttributes 的 <plugin-name>.tsx 文件widget.getProp/setProp 的 <plugin-name>.panel.tsx 文件extensions.sitePlugin() 和唯一 UUID 的 文件广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<plugin-name>.extension.tssrc/extensions.ts 以导入并使用新的扩展站点插件由三个必需文件组成:
<plugin-name>.tsx)在插槽中渲染的自定义元素组件,使用原生 HTMLElement:
HTMLElement 类observedAttributesconnectedCallback() 和 attributeChangedCallback() 以进行渲染display-name)<plugin-name>.panel.tsx)在 Wix 编辑器侧边栏中显示设置面板:
@wix/design-system) 组件 — 组件参考请参阅 wds-docs@wix/editor widget API 管理插件属性widget.getProp('kebab-case-name') 加载初始值widget.setProp('kebab-case-name', value) 更新属性WixDesignSystemProvider > SidePanel > SidePanel.Content 中<plugin-name>.extension.ts)定义插件的放置配置:
站点插件使用原生 HTMLElement 自定义元素:
// my-site-plugin.tsx
class MyElement extends HTMLElement {
static get observedAttributes() {
return ['display-name'];
}
constructor() {
super();
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
const displayName = this.getAttribute('display-name') || "Your Plugin's Title";
this.innerHTML = `
<div style="font-size: 16px; padding: 16px; border: 1px solid #ccc; border-radius: 8px; margin: 16px;">
<h2>${displayName}</h2>
<hr />
<p>
This is a Site Plugin generated by Wix CLI.<br />
Edit your element's code to change this text.
</p>
</div>
`;
}
}
export default MyElement;
要点:
HTMLElement 类observedAttributes 静态 getter 以列出响应式属性display-name、bg-color)connectedCallback() 进行初始渲染attributeChangedCallback() 以便在属性更改时重新渲染this.getAttribute('attribute-name') 读取属性值define() — 请勿在代码中调用 customElements.define()// my-site-plugin.panel.tsx
import React, { type FC, useState, useEffect, useCallback } from 'react';
import { widget } from '@wix/editor';
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
} from '@wix/design-system';
import '@wix/design-system/styles.global.css';
const Panel: FC = () => {
const [displayName, setDisplayName] = useState<string>('');
useEffect(() => {
widget.getProp('display-name')
.then(displayName => setDisplayName(displayName || "Your Plugin's Title"))
.catch(error => console.error('Failed to fetch display-name:', error));
}, [setDisplayName]);
const handleDisplayNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newDisplayName = event.target.value;
setDisplayName(newDisplayName);
widget.setProp('display-name', newDisplayName);
}, [setDisplayName]);
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Content noPadding stretchVertically>
<SidePanel.Field>
<FormField label="显示名称">
<Input
type="text"
value={displayName}
onChange={handleDisplayNameChange}
aria-label="Display Name"
/>
</FormField>
</SidePanel.Field>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
要点:
widget.getProp() 和 widget.setProp() 中的属性名称使用短横线命名法(例如 "display-name")WixDesignSystemProvider > SidePanel > SidePanel.Content 中@wix/design-system 的 WDS 组件@wix/design-system/styles.global.css 以获取样式aria-label站点插件设置面板可以使用 @wix/editor 中的 inputs.selectColor() 和 inputs.selectFont() 来打开原生的 Wix 编辑器颜色和字体选择器对话框。
打开 Wix 颜色选择器,包含主题颜色、渐变等 — 不是基本的 HTML <input type="color">。
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
打开 Wix 字体选择器,包含字体系列、大小、粗体、斜体和其他排版功能。
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>更改字体</Text>
</Button>
</FormField>
</SidePanel.Field>
);
重要提示:
@wix/editor 的 inputs.selectColor() 和 FillPreview — 请勿使用 <Input type="color">@wix/editor 的 inputs.selectFont() 和回调模式 inputs.selectFont(value, { onChange })@wix/editor 导入 inputs(而不是从 @wix/sdk)站点插件在 HTML 属性中一致使用短横线命名法:
| 文件 | 约定 | 示例 |
|---|---|---|
<plugin>.tsx (getAttribute) | 短横线命名法 | this.getAttribute('display-name') |
<plugin>.tsx (observedAttributes) | 短横线命名法 | ['display-name', 'bg-color'] |
<plugin>.panel.tsx (widget API) | 短横线命名法 | widget.getProp('display-name') |
站点插件位于 src/extensions/site/plugins 下。每个插件都有自己的文件夹,文件以插件名称命名。
src/extensions/site/plugins/
└── {plugin-name}/
├── {plugin-name}.tsx # 主插件组件 (HTMLElement)
├── {plugin-name}.panel.tsx # 设置面板组件
└── {plugin-name}.extension.ts # 扩展注册
public/
└── {plugin-name}-logo.svg # 插件徽标 (可选)
| 主题 | 参考 |
|---|---|
| 完整示例 | EXAMPLES.md |
| 插槽(应用 ID、多位置放置、查找插槽) | SLOTS.md |
| WDS 组件 | wds-docs |
站点插件集成到 Wix 业务解决方案中的预定义插槽。每个插槽由以下标识:
常见的放置区域包括产品页面(Wix Stores)、结账和侧边购物车(Wix eCommerce)、预订页面(Wix Bookings)、服务页面、活动页面和博客文章页面。
有关支持的页面、常见的 Wix 应用 ID 以及如何查找插槽 ID,请参阅 SLOTS.md。
扩展注册是强制性的,并且有两个必需的步骤。
每个站点插件都需要在其文件夹中有一个扩展文件:
// my-site-plugin.extension.ts
import { extensions } from '@wix/astro/builders';
export default extensions.sitePlugin({
id: '{{GENERATE_UUID}}',
name: 'My Site Plugin',
marketData: {
name: 'My Site Plugin',
description: '营销描述',
logoUrl: '{{BASE_URL}}/my-site-plugin-logo.svg',
},
placements: [{
appDefinitionId: 'a0c68605-c2e7-4c8d-9ea1-767f9770e087',
widgetId: '6a25b678-53ec-4b37-a190-65fcd1ca1a63',
slotId: 'product-page-details-6',
}],
installation: { autoAdd: true },
tagName: 'my-site-plugin',
element: './extensions/site/plugins/my-site-plugin/my-site-plugin.tsx',
settings: './extensions/site/plugins/my-site-plugin/my-site-plugin.panel.tsx',
});
关键:UUID 生成
id 必须是一个唯一的、静态的 UUID v4 字符串。为每个扩展生成一个新的 UUID — 请勿使用 randomUUID() 或复制示例中的 UUID。将 {{GENERATE_UUID}} 替换为新生成的 UUID,例如 "95a28afd-7df1-4e09-9ec1-ce710b0389a0"。
| 属性 | 类型 | 描述 |
|---|---|---|
id | string | 唯一的静态 UUID v4(生成新的) |
name | string | 插件的内部名称 |
marketData.name | string | 在插件浏览器和应用仪表板中显示的显示名称 |
marketData.description | string | 在插件浏览器和应用仪表板中显示的描述 |
marketData.logoUrl | string | 徽标文件的路径({{BASE_URL}} 解析为 public 文件夹) |
placements | array | 插件可以添加到的插槽放置位置数组 |
placements.appDefinitionId | string | 包含插槽的 Wix 应用的 ID |
placements.widgetId | string | 包含插槽的页面的 ID |
placements.slotId | string | 特定插槽的 ID |
installation.autoAdd | boolean | 是否在应用安装时自动将插件添加到插槽 |
tagName | string | HTML 自定义元素标签(短横线命名法,必须包含连字符) |
element | string | 插件组件的相对路径 |
settings | string | 设置面板组件的相对路径 |
关键: 创建插件特定的扩展文件后,您必须阅读 wix-cli-extension-registration 并按照“应用注册”部分更新 src/extensions.ts。
如果不完成步骤 2,站点插件将不会在插件浏览器中可用。
如果您正在为结账页面构建插件,它可能不支持安装时自动添加。您必须创建一个仪表板页面,为用户提供将插件添加到其站点的方法。有关仪表板页面模式,请参阅 EXAMPLES.md。
有关包含所有三个必需文件(插件组件、设置面板、扩展配置)的完整示例,请参阅 EXAMPLES.md。
示例用例:
define() - Wix 会自动为您处理 customElements.define()站点插件在编辑器中渲染时会被沙盒化。这意味着它们被视为来自不同的域,这会影响对浏览器存储 API 的访问。
编辑器中受限的 API:
localStorage 和 sessionStorage(Web Storage API)document.cookie(Cookie Store API)如何处理沙盒化:
在访问受限 API 之前,使用 @wix/site-window 中的 viewMode() 函数检查当前模式:
import { window as wixWindow } from '@wix/site-window';
const viewMode = await wixWindow.viewMode();
if (viewMode === 'Site') {
const item = localStorage.getItem('myKey');
} else {
// 模拟存储或修改 API 使用以适配编辑器模式
}
站点插件可以直接导入和使用 Wix SDK 模块 — 您不需要 createClient()。Wix 运行时会自动提供客户端上下文。
// ✅ 正确 — 直接导入 SDK 模块
import { items } from "@wix/data";
import { currentCart } from "@wix/ecom";
import { products } from "@wix/stores";
class MyPlugin extends HTMLElement {
async loadData() {
// 直接调用 SDK 方法 — 不需要 createClient
const result = await items.query("MyCollection").find();
const cart = await currentCart.getCurrentCart();
const productList = await products.queryProducts().limit(10).find();
}
}
// ❌ 错误 — 请勿在站点插件中使用 createClient
import { createClient } from "@wix/sdk";
const wixClient = createClient({ modules: { items, products } });
await wixClient.items.query(...); // 错误 — 通过客户端访问的 API 表面不同
每周安装次数
187
代码仓库
GitHub 星标数
3
首次出现
2026年1月26日
安全审计
安装于
opencode165
cursor92
codex87
gemini-cli85
github-copilot81
amp76
Creates site plugin extensions for Wix CLI applications. Site plugins are custom elements that integrate into predefined slots within Wix business solutions (like Wix Stores, Wix Bookings, Wix eCommerce), extending their functionality and user experience.
Site owners can place site plugins into UI slots using the plugin explorer in Wix editors.
Follow these steps in order when creating a site plugin:
src/extensions/site/plugins/<plugin-name>/<plugin-name>.tsx extending HTMLElement with observedAttributes<plugin-name>.panel.tsx with WDS components and widget.getProp/setProp<plugin-name>.extension.ts with extensions.sitePlugin() and unique UUIDsrc/extensions.ts to import and use the new extensionSite plugins consist of three required files :
<plugin-name>.tsx)Custom element component that renders in the slot using native HTMLElement:
HTMLElement classobservedAttributes for reactive propertiesconnectedCallback() and attributeChangedCallback() for renderingdisplay-name)<plugin-name>.panel.tsx)Settings panel shown in the Wix Editor sidebar:
@wix/design-system) components — see wds-docs for component reference@wix/editor widget APIwidget.getProp('kebab-case-name')widget.setProp('kebab-case-name', value)WixDesignSystemProvider > SidePanel > SidePanel.Content<plugin-name>.extension.ts)Defines the plugin's placement configuration:
Site plugins use native HTMLElement custom elements:
// my-site-plugin.tsx
class MyElement extends HTMLElement {
static get observedAttributes() {
return ['display-name'];
}
constructor() {
super();
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
const displayName = this.getAttribute('display-name') || "Your Plugin's Title";
this.innerHTML = `
<div style="font-size: 16px; padding: 16px; border: 1px solid #ccc; border-radius: 8px; margin: 16px;">
<h2>${displayName}</h2>
<hr />
<p>
This is a Site Plugin generated by Wix CLI.<br />
Edit your element's code to change this text.
</p>
</div>
`;
}
}
export default MyElement;
Key Points:
HTMLElement class directlyobservedAttributes static getter to list reactive attributesdisplay-name, bg-color)connectedCallback() for initial renderattributeChangedCallback() to re-render when attributes changethis.getAttribute('attribute-name') to read attribute valuesdefine() for you — do NOT call customElements.define() in your code// my-site-plugin.panel.tsx
import React, { type FC, useState, useEffect, useCallback } from 'react';
import { widget } from '@wix/editor';
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
} from '@wix/design-system';
import '@wix/design-system/styles.global.css';
const Panel: FC = () => {
const [displayName, setDisplayName] = useState<string>('');
useEffect(() => {
widget.getProp('display-name')
.then(displayName => setDisplayName(displayName || "Your Plugin's Title"))
.catch(error => console.error('Failed to fetch display-name:', error));
}, [setDisplayName]);
const handleDisplayNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newDisplayName = event.target.value;
setDisplayName(newDisplayName);
widget.setProp('display-name', newDisplayName);
}, [setDisplayName]);
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Content noPadding stretchVertically>
<SidePanel.Field>
<FormField label="Display Name">
<Input
type="text"
value={displayName}
onChange={handleDisplayNameChange}
aria-label="Display Name"
/>
</FormField>
</SidePanel.Field>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
Key Points:
widget.getProp() and widget.setProp() use kebab-case (e.g., "display-name")WixDesignSystemProvider > SidePanel > SidePanel.Content@wix/design-system@wix/design-system/styles.global.css for stylesaria-label for accessibilitySite plugin settings panels can use inputs.selectColor() and inputs.selectFont() from @wix/editor to open the native Wix Editor color and font picker dialogs.
Opens the Wix color picker with theme colors, gradients, and more — NOT a basic HTML <input type="color">.
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
Opens the Wix font picker with font family, size, bold, italic, and other typography features.
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>Change Font</Text>
</Button>
</FormField>
</SidePanel.Field>
);
Important:
inputs.selectColor() from @wix/editor with FillPreview — do NOT use <Input type="color">inputs.selectFont() from @wix/editor with the callback pattern inputs.selectFont(value, { onChange })inputs from @wix/editor (not from @wix/sdk)Site plugins use kebab-case consistently for HTML attributes:
| File | Convention | Example |
|---|---|---|
<plugin>.tsx (getAttribute) | kebab-case | this.getAttribute('display-name') |
<plugin>.tsx (observedAttributes) | kebab-case | ['display-name', 'bg-color'] |
<plugin>.panel.tsx (widget API) | kebab-case | widget.getProp('display-name') |
Site plugins live under src/extensions/site/plugins. Each plugin has its own folder with files named after the plugin.
src/extensions/site/plugins/
└── {plugin-name}/
├── {plugin-name}.tsx # Main plugin component (HTMLElement)
├── {plugin-name}.panel.tsx # Settings panel component
└── {plugin-name}.extension.ts # Extension registration
public/
└── {plugin-name}-logo.svg # Plugin logo (optional)
| Topic | Reference |
|---|---|
| Complete Examples | EXAMPLES.md |
| Slots (App IDs, multiple placements, finding slots) | SLOTS.md |
| WDS Components | wds-docs |
Site plugins integrate into predefined slots in Wix business solutions. Each slot is identified by:
Common placement areas include product pages (Wix Stores), checkout and side cart (Wix eCommerce), booking pages (Wix Bookings), service pages, event pages, and blog post pages.
For supported pages, common Wix App IDs, and how to find slot IDs, see SLOTS.md.
Extension registration is MANDATORY and has TWO required steps.
Each site plugin requires an extension file in its folder:
// my-site-plugin.extension.ts
import { extensions } from '@wix/astro/builders';
export default extensions.sitePlugin({
id: '{{GENERATE_UUID}}',
name: 'My Site Plugin',
marketData: {
name: 'My Site Plugin',
description: 'Marketing Description',
logoUrl: '{{BASE_URL}}/my-site-plugin-logo.svg',
},
placements: [{
appDefinitionId: 'a0c68605-c2e7-4c8d-9ea1-767f9770e087',
widgetId: '6a25b678-53ec-4b37-a190-65fcd1ca1a63',
slotId: 'product-page-details-6',
}],
installation: { autoAdd: true },
tagName: 'my-site-plugin',
element: './extensions/site/plugins/my-site-plugin/my-site-plugin.tsx',
settings: './extensions/site/plugins/my-site-plugin/my-site-plugin.panel.tsx',
});
CRITICAL: UUID Generation
The id must be a unique, static UUID v4 string. Generate a fresh UUID for each extension - do NOT use randomUUID() or copy UUIDs from examples. Replace {{GENERATE_UUID}} with a freshly generated UUID like "95a28afd-7df1-4e09-9ec1-ce710b0389a0".
| Property | Type | Description |
|---|---|---|
id | string | Unique static UUID v4 (generate fresh) |
name | string | Internal name for the plugin |
marketData.name | string | Display name in plugin explorer and app dashboard |
marketData.description | string | Description shown in plugin explorer and app dashboard |
marketData.logoUrl | string |
CRITICAL: After creating the plugin-specific extension file, you MUST read wix-cli-extension-registration and follow the "App Registration" section to update src/extensions.ts.
Without completing Step 2, the site plugin will not be available in the plugin explorer.
If you are building a plugin for the checkout page , it may not support automatic addition upon installation. You must create a dashboard page to provide users with a way to add the plugin to their site. See EXAMPLES.md for the dashboard page pattern.
For complete examples with all three required files (plugin component, settings panel, extension configuration), see EXAMPLES.md.
Example use cases:
define() - Wix handles customElements.define() for you automaticallySite plugins are sandboxed when rendered in the editor. This means they're treated as if they come from a different domain, which impacts access to browser storage APIs.
Restricted APIs in the editor:
localStorage and sessionStorage (Web Storage API)document.cookie (Cookie Store API)How to handle sandboxing:
Use the viewMode() function from @wix/site-window to check the current mode before accessing restricted APIs:
import { window as wixWindow } from '@wix/site-window';
const viewMode = await wixWindow.viewMode();
if (viewMode === 'Site') {
const item = localStorage.getItem('myKey');
} else {
// Mock storage or modify API usage for editor mode
}
Site plugins can import and use Wix SDK modules directly — you do NOT need createClient(). The Wix runtime provides the client context automatically.
// ✅ CORRECT — Import SDK modules directly
import { items } from "@wix/data";
import { currentCart } from "@wix/ecom";
import { products } from "@wix/stores";
class MyPlugin extends HTMLElement {
async loadData() {
// Call SDK methods directly — no createClient needed
const result = await items.query("MyCollection").find();
const cart = await currentCart.getCurrentCart();
const productList = await products.queryProducts().limit(10).find();
}
}
// ❌ WRONG — Do NOT use createClient in site plugins
import { createClient } from "@wix/sdk";
const wixClient = createClient({ modules: { items, products } });
await wixClient.items.query(...); // Wrong — API surface differs through client
Weekly Installs
187
Repository
GitHub Stars
3
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode165
cursor92
codex87
gemini-cli85
github-copilot81
amp76
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
116,600 周安装
Path to logo file ({{BASE_URL}} resolves to public folder) |
placements | array | Array of slot placements where plugin can be added |
placements.appDefinitionId | string | ID of the Wix app containing the slot |
placements.widgetId | string | ID of the page containing the slot |
placements.slotId | string | ID of the specific slot |
installation.autoAdd | boolean | Whether to auto-add plugin to slots on app installation |
tagName | string | HTML custom element tag (kebab-case, must contain a hyphen) |
element | string | Relative path to plugin component |
settings | string | Relative path to settings panel component |