Build a Feature Package
Scaffold, implement, and publish a feature package for the NNO console shell.
Build a Feature Package
A feature package is a self-contained npm package that plugs into the NNO console shell. It declares its routes, sidebar navigation, and permissions in a FeatureDefinition object. The shell discovers it at build time — no manual registration required.
What is a feature package?
Feature packages follow the @neutrino-io/feature-* naming convention and must declare "neutrino": {"type": "feature"} in their package.json. When installed in a platform console, the neutrino-feature-discovery Vite plugin scans package.json dependencies, finds all matching packages, and generates the virtual:feature-registry module. The shell then boots with routes and sidebar entries sourced entirely from those packages.
Step 1 — Scaffold the package
Create a directory under features/ in your monorepo:
features/my-feature/
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── src/
├── index.ts # Public barrel — exports featureManifest and FeatureDefinition
├── manifest.ts # Auto-discovery metadata
├── feature.ts # Routes, navigation, permissions
└── routes/
└── MyFeaturePage.tsxMinimum package.json:
{
"name": "@neutrino-io/feature-my-feature",
"version": "0.1.0",
"type": "module",
"neutrino": { "type": "feature" },
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"dependencies": {
"@neutrino-io/sdk": "workspace:*",
"@neutrino-io/ui-core": "workspace:*"
}
}Step 2 — Implement the feature manifest
src/manifest.ts provides the auto-discovery metadata the Vite plugin reads:
import type { FeatureManifest } from "@neutrino-io/sdk/types";
export const featureManifest: FeatureManifest = {
id: "my-feature",
name: "My Feature",
package: "@neutrino-io/feature-my-feature",
enabledByDefault: false,
loadPriority: 90,
lazyLoad: true,
environment: "all",
domain: "my-feature",
type: "business",
group: "Overview",
defaultComponent: "MyFeaturePage",
};Set enabledByDefault: false for new features — platform operators activate them via the NNO Portal or features.config.ts.
Step 3 — Define routes and navigation
src/feature.ts exports the FeatureDefinition the shell's FeatureRegistry resolves:
import type { FeatureDefinition } from "@neutrino-io/sdk/feature";
export const myFeatureFeatureDefinition: FeatureDefinition = {
id: "my-feature",
version: "0.1.0",
displayName: "My Feature",
description: "Short description shown in the NNO Portal",
icon: "Zap",
routes: [
{
path: "/my-feature",
component: "MyFeaturePage",
auth: true,
layout: "default",
permissions: ["my-feature:read"],
meta: { title: "My Feature" },
},
],
navigation: [
{
label: "My Feature",
path: "/my-feature",
icon: "Zap",
order: 90,
group: "Overview",
permissions: ["my-feature:read"],
},
],
permissions: ["my-feature:read"],
requiresService: false,
};
export default myFeatureFeatureDefinition;Icon strings are Lucide icon names — the shell resolves them to React components. Permission strings follow the {feature-id}:{action} format.
Step 4 — Wire up the barrel
src/index.ts must export both featureManifest and the FeatureDefinition:
export { MyFeaturePage } from "./routes/MyFeaturePage";
export { myFeatureFeatureDefinition, default } from "./feature";
export { featureManifest } from "./manifest";Step 5 — Test locally
Add the package to apps/console/package.json as a workspace dependency, build, and start the console:
# Add to apps/console/package.json:
# "@neutrino-io/feature-my-feature": "workspace:*"
pnpm --filter @neutrino-io/feature-my-feature build
cd apps/console && pnpm devNavigate to /my-feature in the browser. The sidebar entry should appear under the Overview group. Use watch mode for active development:
# Terminal 1 — rebuild on change
pnpm --filter @neutrino-io/feature-my-feature dev
# Terminal 2 — console with HMR
cd apps/console && pnpm devStep 6 — Publish to the marketplace
Bump the version in package.json, build, and publish to GitHub Packages:
pnpm --filter @neutrino-io/feature-my-feature build
pnpm --filter @neutrino-io/feature-my-feature publishCI publishes automatically on push to main when package files change. Once published, platform operators can install your feature with:
pnpm add @neutrino-io/feature-my-featureAuto-discovery handles the rest — no changes to features.config.ts are needed.
Next steps
- Feature concepts — full
FeatureDefinitioninterface reference - Managing Environments — test your feature in staging
- Authentication — use
useNnoSessionto gate feature access