---
title: Build your first plugin
description: A small local notes tool, from two source files to an installed plugin.
group: Start here
---

# Build your first plugin

You need Hay, a [Hay account](accounts/), and a text editor. Building the optional bundle with the SDK also needs Node.js. The source-folder route does not require an npm project.

## Create a source folder

Make a folder named `little-notes`. Add `manifest.json`:

```json
{
  "schemaVersion": "hay.app/1",
  "id": "example.little-notes",
  "name": "Little notes",
  "version": "0.1.0",
  "description": "A small place for one useful note.",
  "author": "Your name",
  "entrypoint": "index.html",
  "icon": "FileText",
  "color": "#20242c",
  "capabilities": ["storage"],
  "views": [{"id": "main", "title": "Notes", "description": "Your note"}],
  "intents": []
}
```

Use an ID you control before sharing a real release. The `example` prefix is for this tutorial.

## Add the interface

Add `index.html`. Keep scripts and styles inline; Hay injects the SDK before your script.

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Little notes</title>
<style>
  body { max-width: 44rem; margin: 3rem auto; padding: 0 1rem;
         font: 16px/1.6 system-ui; color: #20242c; }
  textarea { box-sizing: border-box; width: 100%; min-height: 12rem;
             padding: 1rem; font: inherit; }
  button { margin-top: 1rem; padding: .6rem 1rem; font: inherit; }
</style>
<h1>Little notes</h1>
<label for="note">What would you like to keep?</label>
<textarea id="note"></textarea>
<button id="save" type="button" disabled>Save note</button>
<p id="status" role="status">Opening your note…</p>
<script>
  const note = document.querySelector('#note');
  const save = document.querySelector('#save');
  const status = document.querySelector('#status');
  async function start() {
    try {
      await Hay.ready;
      note.value = await Hay.storage.get('note') ?? '';
      status.textContent = '';
      save.disabled = false;
    } catch (error) { status.textContent = error.message; }
  }
  save.addEventListener('click', async () => {
    save.disabled = true;
    try {
      await Hay.storage.set('note', note.value);
      status.textContent = 'Note saved.';
    } catch (error) { status.textContent = error.message; }
    finally { save.disabled = false; }
  });
  start();
</script>
</html>
```

## Install and grant access

In Hay, open **Plugins**, choose **Import local plugin**, and select the source folder. Review the installation request. Then enable **Local plugin storage** in its installed card and choose **Open a new task**.

Installation itself grants no capabilities. If storage is off, the example reports the host error instead of pretending to save.

## Test the saved copy

Save a note, leave the task, return, and check that it is still there. Revoke storage permission and check the error state. Try keyboard navigation, an empty note, and a narrow window.

A regular browser does not provide `window.Hay`. Test host integration inside Hay. Local previews should use explicitly labelled fixtures rather than pretending to provide the host.

## Share it

Open **Plugins / Share & publish**, choose Little notes, and select **Export plugin file**. Send the `.hay-plugin.json` file to someone who uses Hay. Their records and permissions are their own.

For a reproducible source build, use the packager from the [Hay SDK source](https://github.com/use-hay/app/tree/main/packages/sdk):

```sh
node /path/to/hay/packages/sdk/build-plugin.mjs ./little-notes
```

The result is `little-notes/dist/example.little-notes-0.1.0.hay-plugin.json`. Read [package formats](manifest/) before adding capabilities or sample data.
