> Portal Navigation:
> 
> - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version.
> - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages).
> - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`).
> - If a reference page seems to be missing its schema (parameters, return types, or type definitions), append `?schema=true` to its `.md` URL (e.g. `https://dev.wix.com/docs/contexts/contexts/stores/category-info/use-category-info-context.md?schema=true`).
> - Top-level index of all portals: https://dev.wix.com/docs/llms.txt
> - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt

## Resource: Build a Site Plugin in Studio Two

## Article: Build a Site Plugin in Studio Two

## Article Link: https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/build-a-site-plugin-in-studio-two.md

## Article Content:

# Build a Site Plugin in Studio Two

A [site plugin](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/about-site-plugins.md) is a custom element, a settings panel, and a configuration file. You generate the files first, then spend most of your time in the custom element: calling Wix APIs and rendering the result. The settings panel lets a Wix user customize the plugin in the editor.

This article walks through that loop for an example plugin in the Side Cart. The plugin tells site visitors how much more to add to their cart to get free shipping, and a Wix user sets the free shipping threshold in its settings panel. Plugins in other slots use the same three files. The data and APIs each plugin uses depend on its slot.

Follow the instructions below to:

1. Scaffold the plugin.
2. Review the configuration.
3. Read the cart.
4. Add a setting.
5. Render again when the cart changes.
6. Place and check the plugin.

## Before you begin

To follow the example, install Wix Stores, which adds the Side Cart to your site.

## Step 1 | Scaffold the plugin

Start from a generated site plugin rather than an empty folder, so the file structure and registration are already in place. In **What would you like to extend?**, choose the slot your plugin fills. For the example plugin, choose the **SideCart** option for `side-cart:header:after-1`, and name the plugin `Free Shipping Note`.

For the full procedure, see [Add a Site Plugin Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/add-a-site-plugin-extension-in-studio-two.md#step-1--add-a-site-plugin).

You can find the new files under `src/extensions/site/plugins/<plugin-folder>/`. For the example plugin, the folder is `free-shipping-note`. You spend most of this article in `<plugin-folder>.tsx`, the custom element. For what every file in the folder is for, see [Site Plugin Extension Files and Code](https://dev.wix.com/docs/site-extensions/site-plugins/site-plugin-extension-files-and-code.md).

## Step 2 | Review the configuration

`<plugin-folder>.extension.ts` is the configuration file. For the example plugin it looks like this:

```ts
import { extensions } from '@wix/custom-extensions'

export default extensions.sitePlugin({
  id: 'f00db6d5-dcb3-46fa-8dd4-d9ddc276abb1',
  name: 'Free Shipping Note',
  marketData: {
    name: 'Free Shipping Note',
    description: 'Marketing Description',
    logoUrl: '{{BASE_URL}}/free-shipping-note-logo.svg',
  },
  placements: [{
    appDefinitionId: '1380b703-ce81-ff05-f115-39571d94dfcd',
    widgetId: '49dbb2d9-d9e5-4605-a147-e926605bf164',
    slotId: 'side-cart:header:after-1',
  }],
  installation: { autoAdd: true },
  tagName: 'free-shipping-note',
  element: './extensions/site/plugins/free-shipping-note/free-shipping-note.tsx',
  settings: './extensions/site/plugins/free-shipping-note/free-shipping-note.panel.tsx',
});
```

`placements` holds the slot you chose in **What would you like to extend?**. `tagName` is the custom element's tag name. `element` and `settings` are the paths to the custom element and the settings panel, relative to `src/`. Keep `id` stable.

`marketData` sets the name and description that the **Plugin Market** shows for the plugin.

Studio Two doesn't place the plugin in its slot, even though the configuration sets `installation.autoAdd`. You place it from the editor in Step 6.

For every field, see [Site Plugin Extension Files and Code](https://dev.wix.com/docs/site-extensions/site-plugins/site-plugin-extension-files-and-code.md#configuration-fields).

## Step 3 | Read the cart

The custom element can call Wix APIs. On the live site, it calls them as the site visitor.

A plugin in the Side Cart can read the site visitor's cart with [`getCurrentCart()`](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart?apiView=SDK.md) from the `currentCartV2` module of `@wix/ecom`. The cart's `subtotal.amount` is the sum of the line items' final prices, as a string. `businessInfo.currencyCode` is the site's currency.

The example plugin reads both values in its `render()` method. `getCurrentCart()` returns a promise, so make `render()` `async`:

```ts
import { currentCartV2 } from '@wix/ecom';

// In the element class:
async render() {
  const { cart } = await currentCartV2.getCurrentCart();
  const subtotal = Number(cart?.subtotal?.amount ?? 0);
  const currency = cart?.businessInfo?.currencyCode ?? '';
  // ...
}
```

## Step 4 | Add a setting

The settings panel sets the plugin's properties with `widget.setProp()` from `@wix/editor`. Each property is bound to an attribute of the custom element with the same name. The element reads the attribute with `getAttribute()`, and renders again in `attributeChangedCallback()` when the Wix user changes it.

A generated plugin has one setting, **Display Name**. The example plugin replaces it with a free shipping threshold, in the `threshold` property.

In the custom element, list `threshold` in `observedAttributes`, and read it in `render()`. Use 100 when the attribute is missing or isn't a number. A threshold of `0` means every order ships free.

```ts
static get observedAttributes() {
  return ['threshold'];
}

// Inside render():
const value = this.getAttribute('threshold');
const threshold = value && Number.isFinite(Number(value)) ? Number(value) : 100;
```

In `<plugin-folder>.panel.tsx`, replace the **Display Name** field with a field for the threshold:

```tsx
import React, { type FC, useState, useEffect } 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 [threshold, setThreshold] = useState<string>('');

  useEffect(() => {
    widget.getProp('threshold')
      .then((value) => setThreshold(value || '100'))
      .catch((error) => console.error('Failed to fetch threshold:', error));
  }, []);

  const handleThresholdChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setThreshold(event.target.value);
    widget.setProp('threshold', event.target.value);
  };

  return (
    <WixDesignSystemProvider>
      <SidePanel width="300" height="100vh">
        <SidePanel.Content noPadding stretchVertically>
          <SidePanel.Field>
            <FormField label="Free shipping threshold">
              <Input
                type="number"
                value={threshold}
                onChange={handleThresholdChange}
                aria-label="Free shipping threshold"
              />
            </FormField>
          </SidePanel.Field>
        </SidePanel.Content>
      </SidePanel>
    </WixDesignSystemProvider>
  );
};

export default Panel;
```

For more on the panel, see [Site Plugin Extension Files and Code](https://dev.wix.com/docs/site-extensions/site-plugins/site-plugin-extension-files-and-code.md#settings-panel).

## Step 5 | Render again when the cart changes

The Side Cart doesn't recreate the plugin when the site visitor changes the cart in it, such as when they change an item's quantity. A custom element that renders only in `connectedCallback()` keeps showing the cart as it was when it rendered.

To render again, listen for cart changes with [`onCartChange()`](https://dev.wix.com/docs/sdk/frontend-modules/ecom/on-cart-change.md) from `@wix/site-ecom`. It runs when the site's own cart UI changes the cart, such as the **Add to Cart** button and the quantity buttons.

A new site project doesn't include `@wix/site-ecom`. To install it, run `npm install @wix/site-ecom` in a terminal on the **Code** tab. For details, see [Work with npm Packages](https://dev.wix.com/docs/studio-two/code-your-site/developer-environment/packages/work-with-npm-packages.md).

`connectedCallback()` can run more than once for the same element, so register the listener only the first time.

The complete custom element for the example plugin:

```ts
import { currentCartV2 } from '@wix/ecom';
import { ecom } from '@wix/site-ecom';

class FreeShippingNote extends HTMLElement {
  private listening = false;

  static get observedAttributes() {
    return ['threshold'];
  }

  connectedCallback() {
    this.render();
    if (!this.listening) {
      ecom.onCartChange(() => this.render());
      this.listening = true;
    }
  }

  attributeChangedCallback() {
    this.render();
  }

  async render() {
    const value = this.getAttribute('threshold');
    const threshold = value && Number.isFinite(Number(value)) ? Number(value) : 100;
    let subtotal: number;
    let currency: string;

    try {
      const { cart } = await currentCartV2.getCurrentCart();
      subtotal = Number(cart?.subtotal?.amount ?? 0);
      currency = cart?.businessInfo?.currencyCode ?? '';
    } catch (error) {
      // Keep showing the last message rather than one with a wrong subtotal.
      console.error('Failed to get the current cart:', error);
      return;
    }

    const remaining = threshold - subtotal;
    const message = remaining > 0
      ? `Add ${remaining.toFixed(2)} ${currency} more to get free shipping.`
      : 'Your order ships free.';

    this.innerHTML = `<p style="padding: 8px 16px; margin: 0;">${message}</p>`;
  }
}

export default FreeShippingNote;
```

## Step 6 | Place and check the plugin

Place the plugin in its slot from the **Plugin Market** of the widget that has the slot, then publish your site. For the steps, see [Add a Site Plugin Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/add-a-site-plugin-extension-in-studio-two.md#step-3--place-the-plugin-in-its-slot).

If the slot already holds a plugin, the **Plugin Market** shows **Replace** instead of **Add**, and replacing removes the other plugin from the slot. That happens if you followed the example in the Add task, which uses the same slot.

To check the plugin, go to the page with its slot on your live site. For the example plugin, click **Add to Cart** on a product page. The Side Cart opens, and the plugin shows how much more to add to get free shipping. When you change the quantity in the Side Cart, the message changes with it.

To change the threshold, select the plugin in the editor and click **Settings** in its toolbar. Change **Free shipping threshold**, then publish your site again.

## See also

- [About Site Plugins](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/about-site-plugins.md)
- [Add a Site Plugin Extension in Studio Two](https://dev.wix.com/docs/studio-two/code-your-site/build-a-custom-frontend/site-plugins/add-a-site-plugin-extension-in-studio-two.md)
- [Site Plugin Extension Files and Code](https://dev.wix.com/docs/site-extensions/site-plugins/site-plugin-extension-files-and-code.md)
- [Site Plugins](https://dev.wix.com/docs/overview/extensions-framework/site-extensions/site-plugins.md)