NNO Docs
Guides

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.tsx

Minimum 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 dev

Navigate 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 dev

Step 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 publish

CI 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-feature

Auto-discovery handles the rest — no changes to features.config.ts are needed.

Next steps

On this page