← Files WixARCHIVED FILE
skills/wix-app/references/SITE_PLUGIN.md
9.64 KB · Oct 8, 2026 · 12:02 UTC
# 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. 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. `marketData.description` must stay under 130 characters.
## 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']; }
connectedCallback() { this.render(); }
attributeChangedCallback() { this.render(); }
render() {
const displayName = this.getAttribute('display-name') || "Your Plugin's Title";
this.innerHTML = `<div style="padding: 16px;"><h2>${displayName}</h2></div>`;
}
}
export default MyElement;
```
**Key Points** (beyond the structure already listed under [Architecture](#architecture)):
- 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 { type FC, useState, useEffect, useCallback, type ChangeEvent } 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('');
useEffect(() => {
widget.getProp('display-name').then(v => setDisplayName(v || "Your Plugin's Title"));
}, []);
const handleDisplayNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setDisplayName(e.target.value);
widget.setProp('display-name', e.target.value);
}, []);
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** (beyond the structure already listed under [Architecture](#architecture)):
- Always update both local state AND widget prop in the same onChange handler
- 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 { 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) => val && onChange(val) })} />
</Box>
</FormField>
</SidePanel.Field>
);
```
### FontPickerField
Opens the Wix font picker (family, size, bold, italic). Same shape as `ColorPickerField` above, with a `Button`+`Text` trigger instead of `FillPreview`, calling `inputs.selectFont(value, { onChange })` instead of `selectColor`. Value type: `{ font: string; textDecoration: string }`.
**Important:** always go through `inputs` from `@wix/editor` (never `<Input type="color">`, never `@wix/sdk`) with the `{ onChange }` callback pattern shown above.
## 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) |
| WDS Components | the `wix-design-system` skill |
## Available Slots
Site plugins integrate into predefined slots — product pages (Wix Stores), checkout and side cart (Wix eCommerce), booking pages (Wix Bookings), service pages, event pages, and blog post pages. See [SLOTS.md](site-plugin/SLOTS.md) for the App Definition IDs, per-slot runtime APIs, and design guidelines.
## 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) — including a product-page badge, a booking confirmation message, a review-count display, and a Wix Data-driven plugin — see [EXAMPLES.md](site-plugin/EXAMPLES.md).
## Best Practices
### Implementation Guidelines
- **Handle the editor environment** - show placeholders when in editor mode for data-dependent plugins (see [Editor Sandboxing](#editor-sandboxing) below)
- **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 — treated as if from a different domain, which blocks `localStorage`/`sessionStorage`, `document.cookie`, IndexedDB, and the Cache API.
Check the mode with `viewMode()` from `@wix/site-window` before touching any of those. That package isn't part of the CLI's default scaffolded `package.json` — install it first (`npm install @wix/site-window`), or the build fails with `Rollup failed to resolve import "@wix/site-window"`:
```typescript
import { window as wixWindow } from '@wix/site-window';
if ((await wixWindow.viewMode()) === 'Site') {
const item = localStorage.getItem('myKey');
} // else: mock storage or skip, in 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 directly, no createClient needed
import { items } from "@wix/data";
class MyPlugin extends HTMLElement {
async loadData() {
const result = await items.query("MyCollection").find();
}
}
```
The same applies to any other Wix SDK module (`@wix/ecom`, `@wix/stores`, etc. — see the table below for three specific calls). Do **not** wrap them in `createClient({ modules: {...} })` from `@wix/sdk` — that API surface differs from the direct-import one shown above, and site plugins don't need it.
### Identity of SDK Calls in Site Plugins
A plugin runs as the **site visitor or member**, never as the app — see [Identity and Elevation Requirement](../SKILL.md#identity-and-elevation-requirement) before routing any SDK call out to a backend endpoint.
Each of these three calls is correct to call directly, exactly as written, and each for a different reason:
| Call | Why it works directly |
| --- | --- |
| `items.query(...)` | Wix Data enforces the collection's `dataPermissions` per caller — the scaffolded default reads as `ANYONE`. See [Permissions](DATA_COLLECTION.md#permissions) |
| `currentCartV2.getCurrentCart()` | Resolves the cart from the caller's session. Elevating it would return the app's cart, not the visitor's |
| `products.queryProducts()` | Catalog base fields are public. `MERCHANT_DATA` and non-visible products are withheld unless the app holds `SCOPE.STORES.PRODUCT_READ_ADMIN` — don't request them from a plugin |
SHA-256: ef7a35dd3caa0777861827fa2a11d4a21e2c99bcaa34d9332f22c50cf44a0373