> ## Documentation Index
> Fetch the complete documentation index at: https://empty-ad9a3406.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your First Plugin

> Step-by-step guide to creating a Blank Board plugin

This guide walks you through building a complete plugin from scratch.

## Prerequisites

* A modern browser
* A text editor
* Blank Board running locally (`npx serve`)

## Step 1: Create the File

```
plugins/
  └── my-plugin/
       └── plugin.js
```

## Step 2: Export `meta` and `setup`

```javascript theme={null}
// plugins/my-plugin/plugin.js

export const meta = {
  id: 'my-plugin',
  name: 'My First Plugin',
  version: '0.1.0',
  compat: '>=3.3.0'
};

export function setup(api) {
  // Your code here
}

export function teardown() {
  // Cleanup (optional but recommended)
}
```

### The `meta` Object

| Field     | Required | Description                       |
| --------- | -------- | --------------------------------- |
| `id`      | ✅        | Unique identifier. Use kebab-case |
| `name`    | ✅        | Display name in Plugin Manager    |
| `version` | ✅        | Semantic version                  |
| `compat`  | ❌        | Core version compatibility range  |

## Step 3: Build Your UI

Use the managed container for standard plugins:

```javascript theme={null}
export function setup(api) {
  const container = api.container;
  container.innerHTML = `
    <div style="padding: 20px; background: white; border-radius: 12px;">
      <h3 style="margin: 0 0 10px 0;">My Plugin</h3>
      <p>Hello from my first plugin!</p>
    </div>
  `;
  // Container is already draggable and resizable!
}
```

Or create your own element for more control:

```javascript theme={null}
export function setup(api) {
  const box = document.createElement('div');
  box.className = 'plugin-box';
  box.style.cssText = `
    left: 100px;
    top: 100px;
    width: 300px;
    padding: 20px;
    background: white;
    border-radius: 12px;
  `;

  box.innerHTML = `
    <h3 style="margin:0 0 10px 0;">My Plugin</h3>
    <p>Hello from my first plugin!</p>
  `;

  api.boardEl.appendChild(box);
  api.makeDraggable(box);
  api.makeResizable(box);
}
```

## Step 4: Add Storage

Use plugin-scoped storage for clean key management:

```javascript theme={null}
let currentApi = null;

export function setup(api) {
  currentApi = api;
  const container = api.container;

  // Restore saved position
  const pos = api.storage.getForPlugin(meta.id, 'pos');
  if (pos) {
    container.style.left = pos.left + 'px';
    container.style.top = pos.top + 'px';
  }

  container.innerHTML = `
    <div style="padding: 20px; background: white; border-radius: 12px;">
      <p>Drag me — I remember my position!</p>
    </div>
  `;

  // Save position on drag end
  api.bus.on('plugin:dragend', ({ el }) => {
    if (el.dataset.pluginId === meta.id) {
      api.storage.setForPlugin(meta.id, 'pos', {
        left: parseInt(el.style.left),
        top: parseInt(el.style.top)
      });
    }
  });
}
```

## Step 5: Add Event Communication

```javascript theme={null}
let currentApi = null;

export function setup(api) {
  currentApi = api;
  const container = api.container;

  let count = api.storage.getForPlugin(meta.id, 'count') || 0;

  container.innerHTML = `
    <div style="padding: 24px; text-align: center; background: white; border-radius: 12px;">
      <div style="font-size: 14px; color: #888;">Counter</div>
      <div id="count" style="font-size: 48px; font-weight: 700; margin: 16px 0;">${count}</div>
      <button id="inc" style="padding: 10px 24px; font-size: 18px; cursor: pointer;">+</button>
    </div>
  `;

  container.querySelector('#inc').addEventListener('click', () => {
    count++;
    container.querySelector('#count').textContent = count;
    api.storage.setForPlugin(meta.id, 'count', count);
    api.bus.emit('counter:changed', { value: count });
  });
}
```

## Step 6: Inject Scoped CSS

```javascript theme={null}
export function setup(api) {
  api.injectCSS(meta.id, `
    .my-counter {
      background: linear-gradient(135deg, #667eea, #764ba2);
      color: white;
      padding: 24px;
      border-radius: 16px;
      text-align: center;
    }
    .my-counter button {
      background: rgba(255,255,255,0.2);
      border: none;
      color: white;
      padding: 10px 24px;
      border-radius: 8px;
      font-size: 18px;
      cursor: pointer;
    }
    .my-counter button:hover {
      background: rgba(255,255,255,0.3);
    }
  `);

  const container = api.container;
  container.innerHTML = `
    <div class="my-counter">
      <div style="font-size: 14px; opacity: 0.8;">Counter</div>
      <div id="count" style="font-size: 48px; font-weight: 700; margin: 16px 0;">0</div>
      <button id="increment">+</button>
    </div>
  `;
}
```

## Step 7: Handle Cleanup

```javascript theme={null}
let currentApi = null;

export function setup(api) {
  currentApi = api;
  // ... setup code ...
}

export function teardown() {
  currentApi?.removeCSS(meta.id);
  console.log('Plugin cleaned up');
}
```

## Step 8: Show Notifications

```javascript theme={null}
export function setup(api) {
  const container = api.container;
  container.innerHTML = `
    <div style="padding: 20px; text-align: center;">
      <button id="notify">Notify me!</button>
    </div>
  `;

  container.querySelector('#notify').addEventListener('click', () => {
    api.notify('Button clicked!', 'success', 3000);
  });
}
```

## Step 9: Install and Test

### Option A: Manual Install

1. Right-click the board → Plugin Manager
2. Click **Install via URL**
3. Enter ID and URL: `http://localhost:3000/plugins/my-plugin/plugin.js`
4. Click **Install**

### Option B: Debug in Console

```javascript theme={null}
// In browser DevTools:
await window.blankBoard.api.installPlugin(
  'my-plugin',
  'http://localhost:3000/plugins/my-plugin/plugin.js',
  'My Plugin'
);
```

## Debugging

Open browser DevTools:

```javascript theme={null}
// Access the core API
window.blankBoard.api.registry.getAll();
window.blankBoard.api.version;  // → "4.0.0"

// Emit test events
window.blankBoard.bus.emit('test', { hello: true });

// Check storage
window.blankBoard.api.storage.list();

// Reload a plugin
await window.blankBoard.api.reloadPlugin('my-plugin');
```

## Complete Example

Here's a full plugin using all the patterns above:

```javascript theme={null}
export const meta = {
  id: 'my-awesome-plugin',
  name: 'My Awesome Plugin',
  version: '1.0.0',
  compat: '>=3.3.0'
};

let currentApi = null;

export function setup(api) {
  currentApi = api;

  // Inject scoped styles
  api.injectCSS(meta.id, `
    .maw-widget {
      background: #1a1a2e;
      color: #eee;
      padding: 24px;
      border-radius: 16px;
      font-family: system-ui, sans-serif;
    }
    .maw-btn {
      background: #7c6fff;
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 8px;
      cursor: pointer;
      margin-top: 12px;
    }
  `);

  // Restore state
  const count = api.storage.getForPlugin(meta.id, 'count') || 0;
  const pos = api.storage.getForPlugin(meta.id, 'pos');

  // Container
  const container = api.container;
  if (pos) {
    container.style.left = pos.left + 'px';
    container.style.top = pos.top + 'px';
  }

  container.innerHTML = `
    <div class="maw-widget">
      <h3 style="margin: 0 0 8px 0;">Awesome Plugin</h3>
      <div>Count: <span id="maw-count">${count}</span></div>
      <button class="maw-btn" id="maw-inc">+ Increment</button>
    </div>
  `;

  container.querySelector('#maw-inc').addEventListener('click', () => {
    const el = container.querySelector('#maw-count');
    const newCount = parseInt(el.textContent) + 1;
    el.textContent = newCount;
    api.storage.setForPlugin(meta.id, 'count', newCount);
    api.bus.emit('counter:changed', { value: newCount });
    api.notify(`Count: ${newCount}`, 'info', 1500);
  });

  // Save position on drag
  api.bus.on('plugin:dragend', ({ el }) => {
    if (el.dataset.pluginId === meta.id) {
      api.storage.setForPlugin(meta.id, 'pos', {
        left: parseInt(el.style.left),
        top: parseInt(el.style.top)
      });
    }
  });
}

export function teardown() {
  currentApi?.removeCSS(meta.id);
}
```

<CardGroup cols={2}>
  <Card title="Persisting Data" icon="database" href="/guides/persisting-data">
    Deep dive into storage patterns
  </Card>

  <Card title="Plugin Communication" icon="comments" href="/guides/plugin-communication">
    Events, hooks, and patterns
  </Card>
</CardGroup>
