File: C:/Users/fred/.codex/.tmp/plugins/plugins/wix/skills/wix-app/references/SITE_PLUGIN.md
# Wix Site Plugin Builder
Site plugins are custom elements that integrate into predefined **slots** within Wix business solutions (Wix Stores, Wix Bookings, Wix eCommerce, etc.), extending their functionality and user experience. Site owners place site plugins into UI slots using the plugin explorer in Wix editors.
## Scaffold
Use `wix generate --params` with `extensionType: SITE_PLUGIN`. `slotId` is `<componentId>:<slotId>` — the colon-joined widget component ID and slot ID. Run `wix schema generate --type SITE_PLUGIN` to list the available `slotId` values (each `anyOf` entry has the slot's human-readable title). The CLI generates the folder, the plugin `.tsx`, the settings panel `.tsx`, the builder file, the UUID, the logo SVG, and the `src/extensions.ts` registration.
## Architecture
Site plugins consist of **three required files** generated by the CLI:
### 1. Plugin Component (`<plugin-name>.tsx`)
Custom element component that renders in the slot using native HTMLElement:
- Extend `HTMLElement` class
- Define `observedAttributes` for reactive properties
- Implement `connectedCallback()` and `attributeChangedCallback()` for rendering
- Use inline styles via template strings
- Attributes use **kebab-case** (e.g., `display-name`)
### 2. Settings Panel (`<plugin-name>.panel.tsx`)
Settings panel shown in the Wix Editor sidebar:
- Uses Wix Design System (`@wix/design-system`) components — see the `wix-design-system` skill for component reference
- Manages plugin properties via `@wix/editor` widget API
- Loads initial values with `widget.getProp('kebab-case-name')`
- Updates properties with `widget.setProp('kebab-case-name', value)`
- Widget properties are bound to custom element attributes — any property change automatically updates the corresponding attribute
- Wrapped in `WixDesignSystemProvider > SidePanel > SidePanel.Content`
### 3. Extension Builder (`<plugin-name>.extension.ts`)
Generated by the CLI with placements, tagName, element/settings paths, marketData (name/description/logoUrl), and `installation.autoAdd`. Edit `marketData.description` and `installation.autoAdd` directly in this file after scaffolding if defaults don't match your needs.
## Plugin Component Pattern
Site plugins use native `HTMLElement` custom elements:
```typescript
// 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:**
- Extend `HTMLElement` class directly
- Define `observedAttributes` static getter to list reactive attributes
- Attributes use **kebab-case** (e.g., `display-name`, `bg-color`)
- Implement `connectedCallback()` for initial render
- Implement `attributeChangedCallback()` to re-render when attributes change
- Use inline styles via template strings
- Use `this.getAttribute('attribute-name')` to read attribute values
- Wix handles `define()` for you — do NOT call `customElements.define()` in your code
## Settings Panel Pattern
```typescript
// 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:**
- Prop names in `widget.getProp()` and `widget.setProp()` use **kebab-case** (e.g., `"display-name"`)
- Always update both local state AND widget prop in onChange handlers
- Widget properties are bound to custom element attributes — changes automatically update the corresponding attribute
- Wrap content in `WixDesignSystemProvider > SidePanel > SidePanel.Content`
- Use WDS components from `@wix/design-system`
- Import `@wix/design-system/styles.global.css` for styles
- Include `aria-label` for accessibility
## Color & Font Picker Fields
Site plugin settings panels can use `inputs.selectColor()` and `inputs.selectFont()` from `@wix/editor` to open the native Wix Editor color and font picker dialogs.
### ColorPickerField
Opens the Wix color picker with theme colors, gradients, and more — **NOT** a basic HTML `<input type="color">`.
```typescript
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>
);
```
### FontPickerField
Opens the Wix font picker with font family, size, bold, italic, and other typography features.
```typescript
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:**
- Always use `inputs.selectColor()` from `@wix/editor` with `FillPreview` — do NOT use `<Input type="color">`
- Always use `inputs.selectFont()` from `@wix/editor` with the callback pattern `inputs.selectFont(value, { onChange })`
- Import `inputs` from `@wix/editor` (not from `@wix/sdk`)
## Attribute Naming Convention
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')` |
## References
| Topic | Reference |
| --- | --- |
| Complete Examples | [EXAMPLES.md](site-plugin/EXAMPLES.md) |
| Slots (App IDs, runtime APIs, design guidelines, multiple placements) | [SLOTS.md](site-plugin/SLOTS.md) — run `wix schema generate --type SITE_PLUGIN` for the authoritative `slotId` enum |
| WDS Components | the `wix-design-system` skill |
## Available Slots
Site plugins integrate into predefined slots in Wix business solutions. Each slot is identified by:
- **appDefinitionId**: The ID of the Wix app (e.g., Stores, Bookings)
- **widgetId**: The ID of the page containing the slot
- **slotId**: The specific slot identifier
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 App Definition IDs, per-slot runtime APIs, design guidelines, and placement constraints, see [SLOTS.md](site-plugin/SLOTS.md). Run `wix schema generate --type SITE_PLUGIN` for the authoritative `slotId` enum.
## Checkout Plugins
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](site-plugin/EXAMPLES.md) for the dashboard page pattern.
## Examples
For complete examples with all three required files (plugin component, settings panel, extension configuration), see [EXAMPLES.md](site-plugin/EXAMPLES.md).
**Example use cases:**
- **Best Seller Badge** - Customizable badge on product pages with text and color settings
- **Booking Confirmation** - Custom confirmation message for booking pages
- **Product Reviews Summary** - Star rating and review count display
- **Data-Driven Plugin** - Plugin with Wix Data API integration and editor environment handling
## Best Practices
### Implementation Guidelines
- **Use inline styles** - CSS imports are not supported in custom elements
- **Handle editor environment** - Show placeholders when in editor mode for data-dependent plugins
- **Do not call `define()`** - Wix handles `customElements.define()` for you automatically
- **Validate all input** - Check required props are present
- **Follow naming conventions** - kebab-case for all attributes and widget API
- **Keep plugins focused** - Each plugin should do one thing well
- **Test in multiple slots** - If supporting multiple placements, test each one
- **Support both Stores versions** - Include placements for both old and new Wix Stores product pages for maximum compatibility
### Editor Sandboxing
Site 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)
- IndexedDB API
- Cache API
**How to handle sandboxing:**
Use the `viewMode()` function from `@wix/site-window` to check the current mode before accessing restricted APIs:
```typescript
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
}
```
### Using Wix SDK Modules in Site Plugins
Site plugins can import and use Wix SDK modules directly — you do NOT need `createClient()`. The Wix runtime provides the client context automatically.
```typescript
// ✅ 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();
}
}
```
```typescript
// ❌ 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
```
### Performance Considerations
- Keep bundle size small - plugins load on user-facing pages
- Avoid heavy computations on initial render
- Lazy load data when possible
- Use efficient re-rendering patterns