This guide walks you through everything you need to create a plugin, register your team, and start shipping independently. The entire onboarding process is self-service; after one initial review from the core team, your plugin team is fully autonomous.
- Node.js 20 (LTS)
- npm (comes with Node.js)
- Git
- Docker (for the local PostgreSQL database)
Verify your setup:
node --version # v20.x.x
npm --version # 10.x.x
git --version # 2.x.x
docker --version # 24.x.x or laterFrom the NaaP repository root:
npx naap-plugin create <your-plugin-name>This generates the following structure:
plugins/<your-plugin-name>/
├── frontend/
│ ├── src/
│ │ ├── App.tsx # Root component (mounted by the shell)
│ │ ├── index.ts # UMD entry point (mount/unmount exports)
│ │ └── components/ # Your React components
│ ├── package.json
│ └── vite.config.ts # Pre-configured UMD build
├── backend/ # Optional -- delete if not needed
│ ├── src/
│ │ ├── index.ts # Express server entry
│ │ └── routes/ # API routes
│ ├── package.json
│ └── prisma/
│ └── schema.prisma # Plugin-specific database schema
└── plugin.json # Plugin manifest (routes, ports, icons)
Every plugin must include a plugin.json at its root. This file tells the
shell how to load and display your plugin:
{
"name": "your-plugin-name",
"displayName": "Your Plugin",
"version": "1.0.0",
"description": "Brief description of what your plugin does.",
"icon": "LayoutDashboard",
"routes": ["/your-plugin"],
"backendPort": 4010,
"permissions": ["read:network"]
}Start the platform with your plugin:
# Fastest: shell + your plugin backend (~6s)
./bin/start.sh <your-plugin-name>
# With frontend HMR (hot module replacement):
./bin/start.sh dev <your-plugin-name>This starts:
- The Next.js shell on
http://localhost:3000 - Core services (base-svc, plugin-server)
- Your plugin backend (if it exists)
- Your plugin frontend (loaded via CDN bundle, or HMR in dev mode)
Your plugin is accessible at http://localhost:3000/<your-route>.
- Use
@naap/plugin-sdkhooks for authentication, configuration, and inter-plugin communication. - Your frontend is built as a UMD bundle by Vite. The shell loads it at runtime via the plugin server.
- Shared UI components are available from
@naap/ui.
- Your backend is a standalone Express server with its own Prisma schema.
- Each plugin backend gets an isolated PostgreSQL schema (e.g.,
your_plugin_name). - Use
@naap/plugin-utilsfor authentication middleware and API helpers.
Once your plugin is working locally, register your team so you can merge PRs autonomously.
Open the CODEOWNERS file at the repository root and add a line for your
plugin:
/plugins/<your-plugin-name>/ @livepeer/<your-team-name>
This ensures that PRs touching your plugin directory are automatically assigned to your team for review.
Open .github/labeler.yml and add an entry so PRs are auto-labeled:
plugin/<your-plugin-name>:
- changed-files:
- any-glob-to-any-file: 'plugins/<your-plugin-name>/**'Open a single PR with both changes above, targeting the main branch.
A core maintainer will review it once. After merge, your team is autonomous.
Your day-to-day development workflow:
-
Branch from
main:git checkout main && git pull git checkout -b feat/<your-team>/<description>
-
Start developing (~6 seconds):
# Recommended: auto-detects your changed plugins ./bin/start.sh # Or explicitly start your plugin ./bin/start.sh <your-plugin-name>
-
Make changes in
plugins/<your-plugin-name>/. Your plugin is auto-rebuilt on next start. -
Quick restart after changes:
./bin/stop.sh && ./bin/start.sh # ~8s total
-
Commit using conventional commits:
git commit -m "feat(plugin/<your-plugin-name>): add network stats view" -
Open a PR against
main:- The labeler bot auto-labels it (e.g.,
plugin/<your-plugin-name>). - CODEOWNERS auto-assigns your team as reviewers.
- Copilot and CodeRabbit provide automated code review.
- CI runs your plugin's tests.
- A Vercel preview URL is generated for testing.
- The labeler bot auto-labels it (e.g.,
-
Your team reviews and approves.
-
Merge queue merges automatically once approved and CI passes.
-
Production deploys automatically after merge to
main.
As an autonomous plugin team, you are responsible for:
- Review PRs within 24 hours. The review SLA bot will send reminders if a PR goes unreviewed.
- Keep your plugin building. The health dashboard checks all plugins daily. If your build breaks, you will be notified.
- Follow conventional commits. CI enforces this. Non-conforming commits will fail the linter.
- Stay within your directory. Do not modify code outside
plugins/<your-plugin-name>/. If you need changes to shared packages or the shell, open an issue or RFC.
Understanding how plugins work in NaaP:
- Plugins are built as UMD bundles by Vite (
plugin-buildpackage). - The plugin server hosts the bundles at
/cdn/plugins/<name>/<version>/<name>.js. - The shell fetches the plugin manifest from the registry and loads the UMD bundle at runtime using a dynamic script tag.
- The bundle exports
mount(container, context)andunmount(container)functions.
When the shell mounts your plugin, it passes a ShellContext object
containing:
user-- the authenticated userteam-- the current team contextnavigate(path)-- client-side navigationeventBus-- publish/subscribe for inter-plugin communicationapiClient-- pre-configured HTTP client with auth headersconfig-- plugin-specific configuration
Access these via SDK hooks:
import { useShellContext, useEventBus } from '@naap/plugin-sdk';
function MyComponent() {
const { user, team } = useShellContext();
const { emit, on } = useEventBus();
// ...
}| Event | What happens |
|---|---|
| Shell loads | Fetches plugin registry, determines which plugins to load |
| Plugin mount | Shell creates a container div and calls mount(container, context) |
| Route change | Shell unmounts the current plugin and mounts the next one |
| Plugin unmount | Shell calls unmount(container) -- clean up subscriptions and timers |
Can I use a different frontend framework (Vue, Svelte, etc.)?
The UMD contract only requires mount and unmount functions. In theory,
any framework works. In practice, the SDK hooks and shared UI library are
React-only, so using React is strongly recommended.
Can I add npm dependencies to my plugin?
Yes. Your plugin has its own package.json. Add whatever you need. Be
mindful of bundle size since the UMD bundle is loaded at runtime.
What if I need a database table?
Add it to your plugin's Prisma schema in backend/prisma/schema.prisma.
Your schema is isolated to its own PostgreSQL schema namespace. Run
cd packages/database && npx prisma db push to apply changes.
What if I need to change a shared package?
Open an issue or RFC describing what you need. If the change is small and non-breaking, a PR with tests is usually approved quickly.
How do I communicate with another plugin?
Use the event bus (useEventBus from @naap/plugin-sdk). Never import
directly from another plugin's code.
How do I get help?
Open a GitHub Discussion. The community and core team are responsive to questions.