Skip to content

[feat] pluggable extension system#135

Open
stooit wants to merge 1 commit intouselagoon:mainfrom
stooit:feat/extensions
Open

[feat] pluggable extension system#135
stooit wants to merge 1 commit intouselagoon:mainfrom
stooit:feat/extensions

Conversation

@stooit
Copy link

@stooit stooit commented Feb 13, 2026

Summary

Adds a pluggable extension system that lets downstream deployments inject pages, navigation items, and sidebar sections into Lagoon UI without modifying core code.

Extensions are defined as directories under extensions/, each with an extension.json manifest. At build time, build-extensions copies pages and components into the app and generates a merged extensions.json. At runtime, the ExtensionProvider reads this manifest and makes it available to navigation components.

Included change

  • Build script (scripts/build-extensions.ts) discovers extensions, copies pages/components/lib into the app tree, writes merged manifest
  • Manifest schema & validation (src/lib/extensions/schema.ts) validates extension.json at load time
  • Runtime loader (src/lib/extensions/loader.ts) reads extensions.json and provides it via ExtensionContext
  • Navigation integration sidebar (useSidenavItems), project tabs (ProjectNavTabs), environment tabs (EnvironmentNavTabs) all pick up extension nav items
  • RBAC (src/lib/extensions/rbac.ts, ExtensionRouteGuard) role-based access on nav items and pages via requiredRoles/excludeRoles
  • Icon mapping (src/lib/extensions/icons.ts) maps Lucide icon names from manifests to components

Extension structure

extensions/my-extension/
├── extension.json          # manifest
├── pages/                  # → copied to src/app/(routegroups)/
│   └── my-page/
│       └── page.tsx
├── components/             # → copied to src/components/extensions/<name>/
│   └── MyComponent.tsx
└── lib/                    # → copied to src/lib/extensions/<name>/
    └── utils.ts

Manifest format

{
  "meta": { "name": "my-extension", "version": "1.0.0" },
  "navigation": {
    "items": [
      {
        "id": "my-page",
        "label": "My Page",
        "href": "/my-page",
        "icon": "Star",
        "target": "sidebar-projects"
      }
    ],
    "sections": [
      {
        "section": "My Section",
        "position": "end",
        "items": [
          { "id": "item1", "label": "Item", "href": "/item", "icon": "Star" }
        ]
      }
    ]
  },
  "pages": [
    { "route": "my-page", "requiredRoles": ["admin"] }
  ]
}

Navigation targets

Target Location
sidebar-projects Projects section in sidebar
sidebar-deployments Deployments section
sidebar-organizations Organisations section
sidebar-settings Settings section
project-tabs Project page tabs
environment-tabs Environment page tabs

Docker usage

Downstream images will need to optionally inject extensions before build:

COPY my-extensions/ /app/extensions/
RUN yarn build

Working example

A "functional" analytics dashboard example available here: feat/extensions-example:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant