From wix-cli
Use when building interactive components for predefined slots in Wix business solutions. Triggers include site plugin, slot, Wix app integration, plugin explorer, business solution extension.
npx claudepluginhub wix-playground/skills-architecture-test --plugin wix-cliThis skill uses the workspace's default tool permissions.
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.
Builds and reviews Wix CLI app extensions: dashboard pages, modals, plugins, Editor React components, backend APIs, events, service plugins, data collections. Prepares apps for App Market review.
Guides discovery of Wix eCommerce APIs via Playwright UI automation, official docs, and user guidance to build commands/skills for wix-ecom-cowork plugin.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Share bugs, ideas, or general feedback.
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 | 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 |
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