Documentation Index
Fetch the complete documentation index at: https://na-36-handover-docs-v2-into-docs-v2-dev-20260518.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
By the end of this tutorial you’ll have a NaaP plugin running locally inside the
operator.livepeer.org shell, a backend API that queries a Postgres schema isolated to your plugin, and a plugin.json manifest ready to publish to the marketplace. The plugin uses ShellContext for auth, navigation, and theme, which means you write product code without rebuilding the infrastructure underneath it.
This is the Persona 2 and Persona 3 join: the platform builder shipping operator tooling and the compute primitive builder shipping a network utility. NaaP is where both audiences ship UI that reaches the Livepeer operator community directly.
Required Tools
- Node.js 20 or later
- Docker (for the local PostgreSQL container)
- Git
- A code editor
./bin/start.sh in the cloned NaaP repository. First run installs dependencies, starts Postgres, runs schema migrations, builds plugin bundles, and starts the shell. Subsequent starts take 6-8 seconds.
Plugin Architecture
NaaP is a micro-frontend platform. The shell is a Next.js 15 host application atoperator.livepeer.org. Plugins are compiled to UMD bundles that the shell loads at runtime via a plugin registry. Each plugin owns a full vertical slice.
| Layer | What the plugin owns | What the shell provides |
|---|---|---|
| Frontend | React components, routes, custom UI | Layout, navigation, theme, notifications |
| Backend | API logic, business rules | Auth middleware, request envelope, error handling |
| Database | Schema definitions, queries | Shared Postgres, per-plugin schema isolation |
| Identity | Plugin-scoped permissions | OIDC auth, RBAC, user context |
ShellContext object on mount. This is the entire interface between a plugin and the platform. Plugins do not import shell internals directly.
/api/v1/[plugin-name]/* in production (Vercel) and proxy to standalone Express backends on ports 4001-4012 in local development. The same route handlers serve both environments.
Platform Bootstrap
Start the platform
.env files, builds plugin bundles, and starts the shell.Subsequent runs take 6-8 seconds.Open the shell
Navigate to
http://localhost:3000. The NaaP shell loads with the default plugins installed. Sign in with the development credentials displayed in the terminal output.The 12 default plugins cover developer, operator, monitoring, and governance use cases. Your custom plugin will mount alongside them after scaffolding.Plugin Scaffold
Scaffold a new plugin
Edit the manifest
Open The five required fields are
plugin.json:id, name, version, category, and permissions. The shell uses id for routing (/orchestrator-health), database namespacing (orchestrator_health schema), and API path (/api/v1/orchestrator-health/*).Frontend Implementation
The plugin entry point receivesShellContext via the useShell() hook. Save as src/index.tsx:
@naap/plugin-sdk and pulls every shell service through hooks. useShell() returns navigation, notifications, and logger; useAuth() returns the authenticated user. No direct calls to shell internals.
Backend Routes
Plugin backends run as route handlers under/api/v1/[plugin-name]/*. The standard response envelope wraps every response.
Save as src/api/orchestrators.ts:
@naap/database module exposes the shared Postgres connection. Plugin schemas are isolated by Postgres schema name; the orchestrator-health plugin owns the orchestrator_health schema and cannot read or write to other plugins’ schemas.
Wire the route handler in src/api/index.ts:
/api/v1/orchestrator-health/. Every request gets pre-authenticated by the shell middleware; the route handler trusts that the request has a valid user.
Database Schema
Plugins ship migrations undermigrations/. The CLI generates timestamped files; the migration runs on shell startup and is idempotent.
Save as migrations/001_init.sql:
orchestrator_health schema, so cross-plugin reads fail with permission denied even if a plugin tries to escape its boundary.
Inter-Plugin Communication
Plugins communicate through the shell’s event bus. Use it to publish notable state changes (a new orchestrator detected, an alert threshold crossed) without coupling plugins to each other.[domain].[verb] convention. The shell broadcasts events for plugin lifecycle (plugin.installed, plugin.removed), auth (user.signed-in, user.signed-out), and theme (theme.changed). Custom events use your plugin’s namespace (orchestrator-health.alert).
Marketplace Publication
NaaP ships a Plugin Publisher plugin (one of the 12 default plugins) that handles marketplace submission. The flow:Validate the bundle
Build the production bundle
dist/. The UMD bundle is what the shell loads at runtime.Publish via the Plugin Publisher plugin
Open the Plugin Publisher plugin in the live shell at
operator.livepeer.org/plugin-publisher. Upload the bundle plus the plugin.json manifest. The publisher walks the same validation, then submits to the marketplace queue for review.Production Considerations
Six things change between the local development flow and a published plugin. Permission scoping. Declare only the permissions your plugin actually uses. Marketplace review rejects overscoped manifests. If the plugin reads orchestrator data, requestorchestrator:read, not network:write.
Schema migrations are forward-only. Once a migration runs in production, you cannot undo it. Test migrations against a fresh database before publishing; never edit a published migration file.
Backend cold-start. Vercel functions cold-start on first request after idle. For plugins that need consistent latency, batch initial loads or use the shell’s caching layer.
AI prompt templates. The Prompts section in the NaaP docs ships eight templates for plugin scaffolding, UI design, testing, and publishing. Paste them into any AI assistant for boilerplate generation.
Versioning. Use semantic versioning for the manifest version field. Breaking changes (manifest schema shifts, permission additions) increment major. Bug fixes increment patch.
Changelog discipline. The NaaP platform itself ships breaking SDK changes between beta releases. Subscribe to the changelog at operator.livepeer.org/docs/community/changelog and test against the next beta before it ships stable.
Common Errors
Plugin fails to load with 'invalid manifest'
Plugin fails to load with 'invalid manifest'
The
plugin.json is missing a required field or has an invalid value. Run naap-plugin validate for specific errors. The five required fields are id, name, version, category, permissions.`/api/v1/[plugin]/*` returns 401 in local dev
`/api/v1/[plugin]/*` returns 401 in local dev
The Express backend is running on the right port (4001-4012) but the shell isn’t proxying to it. Restart the shell with
./bin/start.sh --restart-shell to pick up the new plugin’s route handlers.Postgres schema 'permission denied' on plugin query
Postgres schema 'permission denied' on plugin query
The plugin schema didn’t exist before the route handler ran. Confirm
migrations/001_init.sql ran during ./bin/start.sh; the schema name must match plugin.json’s id with hyphens converted to underscores (orchestrator-health becomes orchestrator_health).`useShell()` throws 'shell context unavailable'
`useShell()` throws 'shell context unavailable'
Event bus subscriptions fire stale data
Event bus subscriptions fire stale data
useEventBus() returns a new subscribe function on every render unless wrapped in useCallback. The subscriber closure captures stale state. Pass the values you need to the publisher; don’t rely on closure over render-time state.AI agent prompt
Next Steps
NaaP Docs
Full developer documentation, SDK hooks reference, prompt templates.
NaaP Repo
Source code, contribution guide, issue tracker.
Pymthouse Tutorial
Pair a NaaP plugin with pymthouse for billed multi-tenant access.
Eliza Plugin Tutorial
Build an AI agent plugin for the Eliza framework.