Skip to content
You are reading the docs for Wanaku next, the Rust-based server (early access of 0.3.0). Read the Classic docs for the Java-based Wanaku 0.2.

Admin UI plugin architecture ​

Purpose ​

This document defines the high-level architecture and development guidelines for making the application's web UI extensible through plugins.

The audience is Wanaku maintainers and plugin platform designers. Plugin authors who need the current, implemented API should use Build an Admin UI plugin and the Plugin reference.

Implementation Status ​

This document contains both current behavior and design requirements. Unless a section says Implemented, treat its must and should statements as requirements for future platform work, not as claims about the current release.

Implemented: filesystem discovery, manifest loading, ES module activation, navigation entries, pages, static plugin files, configured backend service proxy mappings, the plugin catalog, plugin installation from a ZIP archive, and service configuration from the admin UI.

Declared but not implemented: deactivate() (the host does not call it) and notifications (host.notifications.show writes to the browser console only).

Declared but not enforced: manifest permissions and host API compatibility ranges. Wanaku currently loads plugins as trusted, same-origin JavaScript. It does not provide a security sandbox.

Planned: capability enforcement, compatibility rejection, additional extension points, a plugin test kit, a fake development host, and stronger isolation for untrusted plugins.

The main application consists of:

  • a Rust backend;
  • a Carbon-based web UI;
  • a plugin host/runtime in the web UI;
  • an optional backend proxy/service-resolution layer in the Rust application.

Plugins may:

  • contribute pages, navigation entries, actions, panels, or other UI elements to the main application;
  • render their own UI inside explicit extension points exposed by the host;
  • call one or more REST APIs;
  • use host-provided capabilities such as navigation, notifications, authentication-aware HTTP, and configuration.

The plugin UI and the plugin backend are independent deployables. A plugin backend may use any implementation technology, including Quarkus, Rust, Spring Boot, Go, or another platform.


1. Core Architectural Principles ​

1.1 The plugin contract is a browser API, not a framework API ​

The host must expose a stable plugin API that is independent of the host UI implementation details.

Plugins must not depend directly on internal Carbon component state, internal routing implementation, private stores, or undocumented DOM structure.

Prefer:

ts
host.navigation.add(...)
host.pages.register(...)
host.http.get(...)
host.notifications.show(...)

Avoid:

ts
document.querySelector("#internal-menu").append(...)

The host's implementation may use Carbon internally, but the public plugin contract must remain stable even if the host UI is later refactored.

1.2 Use ES modules as the primary plugin format ​

The default UI plugin format should be a JavaScript/TypeScript ES module.

A typical plugin package contains:

text
plugin/
├── plugin.json
├── plugin.js
├── plugin.css
└── assets/

WebAssembly may be used internally by a plugin, but it should be treated as an implementation detail rather than the platform contract.

For example:

text
plugin.js
  └── loads plugin.wasm

The host should only care that the plugin implements the supported ES module contract. The server serves .wasm files with Content-Type: application/wasm, so WebAssembly.instantiateStreaming can load them.

1.3 Plugins must use explicit extension points ​

Plugins should not receive unrestricted access to the host UI.

The host should define supported extension points such as:

  • navigation entries;
  • pages/routes;
  • toolbar actions;
  • dashboard widgets;
  • detail-page tabs;
  • context-menu actions;
  • settings pages;
  • notifications;
  • command palette entries.

Each extension point should have a stable API and lifecycle.

1.4 Plugin UI and backend are separate deployables ​

A plugin UI must not assume that its backend is colocated with the UI artifact.

The plugin backend may be deployed:

  • in the same Kubernetes cluster;
  • in another cluster;
  • behind a gateway;
  • on another domain;
  • as an external SaaS API.

The UI plugin should therefore refer to logical services rather than physical hostnames whenever possible.

1.5 Prefer host-mediated HTTP access ​

The preferred model is:

text
Plugin UI
   |
   | host.http.get("customer-api", "/customers")
   v
Host Plugin API
   |
   | authentication
   | authorization
   | routing
   | tracing
   | tenant/context propagation
   v
Rust backend / gateway
   |
   | reverse proxy / service resolution
   v
Plugin backend

This avoids hard-coded backend hostnames and centralizes cross-cutting concerns.


2. High-Level Architecture ​

text
┌──────────────────────────────────────────────────────────────┐
│                        Main Application                      │
│                                                              │
│  Carbon Web UI                                               │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ Plugin Runtime                                         │  │
│  │                                                        │  │
│  │  - plugin discovery                                    │  │
│  │  - manifest validation                                 │  │
│  │  - lifecycle management                                │  │
│  │  - extension-point registration                        │  │
│  │  - capability enforcement (planned)                    │  │
│  │  - compatibility checks (planned)                      │  │
│  └──────────────────────────┬─────────────────────────────┘  │
│                             │                                │
│                             v                                │
│                    Plugin ES Modules                         │
│                             │                                │
│                             │ host.http                      │
└─────────────────────────────┼────────────────────────────────┘
                              │
                              v
┌──────────────────────────────────────────────────────────────┐
│                       Rust Backend                           │
│                                                              │
│  - authentication                                            │
│  - authorization                                             │
│  - plugin configuration                                      │
│  - service resolution                                        │
│  - HTTP proxying                                             │
│  - tracing / telemetry                                       │
│                                                              │
└─────────────────────────────┬────────────────────────────────┘
                              │
                              v
                 ┌─────────────────────────┐
                 │ Plugin / External APIs  │
                 │                         │
                 │ Quarkus                 │
                 │ Rust                    │
                 │ Spring Boot             │
                 │ SaaS APIs               │
                 │ etc.                    │
                 └─────────────────────────┘

3. Plugin Manifest ​

Implemented. The Plugin reference lists the plugin.json fields.

Design rules:

  • The manifest must contain logical requirements only, such as service IDs and the host API range.
  • Do not hard-code environment-specific backend hostnames into the manifest, unless the API is intentionally external and public.
  • Permissions such as service:customer-api:read are reserved for future capability enforcement.

4. Plugin Lifecycle ​

Implemented in part. The host calls activate(host). It does not call deactivate() yet. The Build an Admin UI plugin guide describes the current behavior.

Design rules:

  • The host owns plugin loading and unloading.
  • A plugin must release all resources that it creates. When the host calls deactivate() in the future, the plugin must release its listeners, subscriptions, and timers there.

5. Plugin Host API ​

Implemented in part. The Plugin reference describes host.version, host.navigation, host.pages, host.http, and host.notifications.

Design rules:

  • The host API must stay small, capability-oriented, versioned, and framework-independent.
  • Planned extension points, such as a toolbar API and a configuration API, must follow the same rules.

6. UI Rendering Guidelines ​

6.1 The host owns the shell ​

The main application owns:

  • global navigation;
  • top-level routing;
  • global theme;
  • authentication UX;
  • global notifications;
  • application chrome;
  • top-level error handling.

Plugins should contribute content inside host-controlled extension points.

6.2 Plugins own their mounted subtree ​

When a plugin page is mounted, the host provides a container:

ts
mount(container: HTMLElement)

The plugin may render its UI inside that container.

The plugin must not assume that it owns the entire document.

6.3 Carbon consistency ​

Because the host application uses Carbon, plugin authors should preferably use the same Carbon design system when building first-party plugins.

However, the plugin API should not require access to host-internal Carbon component instances.

Where practical, publish a supported UI package for plugin authors, for example:

text
@application/plugin-ui

This package may expose:

  • supported Carbon component wrappers;
  • theme utilities;
  • spacing and typography tokens;
  • common loading and error states;
  • shared form patterns.

6.4 CSS isolation ​

Plugins should avoid global CSS.

Prefer one or more of:

  • CSS modules;
  • strict plugin-specific class prefixes;
  • Shadow DOM where appropriate;
  • host-provided style primitives.

A plugin must not redefine global selectors such as:

css
body { ... }
button { ... }
.bx--some-global-class { ... }

unless explicitly supported by the plugin platform.


7. Backend Service Resolution ​

Implemented. The Plugin reference describes the service mappings.

Design rules:

  • Plugins use logical service identifiers, for example host.http.get("customer-api", "/customers"). The plugin does not know the physical address of the backend.
  • The platform resolves the logical name. The Rust backend acts as a reverse proxy at /api/plugins/{pluginId}/{serviceId}/{path}.
  • One browser origin for all plugin calls reduces CORS complexity.

8. Authentication and Authorization ​

Plugins should not directly manage primary application credentials.

The host HTTP capability should inject authentication information when appropriate.

For example:

text
Plugin
   |
   | host.http.get(...)
   v
Host
   |
   | adds access token
   | adds tenant context
   | adds correlation ID
   v
Plugin backend

Design requirement: A future capability-enforcement layer must check permissions declared in plugin.json before activation. The current runtime does not enforce these declarations.

Example:

json
{
  "permissions": [
    "service:customer-api:read",
    "service:customer-api:write"
  ]
}

After Wanaku implements capability enforcement, the host must expose only the capabilities that the platform grants to the plugin.

Frontend permission checks are not a substitute for backend authorization. Plugin backends must independently validate authorization for every protected operation.


9. Plugin Backend Guidelines ​

Plugin backends are independent services.

They should:

  • expose stable REST APIs;
  • publish an OpenAPI specification when possible;
  • implement their own authorization checks;
  • support distributed tracing/correlation IDs;
  • avoid relying on the physical location of the UI plugin;
  • maintain backward compatibility for supported API versions.

The plugin UI should consume the backend through an API contract rather than shared implementation details.

Recommended:

text
OpenAPI
  |
  +-- backend validation/tests
  |
  +-- generated TypeScript client or typed API model
      |
      v
   Plugin UI

10. Testing Strategy ​

Status: planned. The current repository does not include the test kit or fake development host described in this section.

Testing should be possible without running the full application.

10.1 Plugin unit tests ​

Provide a mockable test host.

Example:

ts
const host = createTestHost();

await activate(host);

expect(host.navigation.entries()).toContainEqual({
  id: "customers",
  label: "Customers",
  route: "/customers"
});

10.2 Plugin test kit ​

Publish a reusable test package, for example:

text
@application/plugin-testkit

It should verify:

  • manifest validity;
  • successful activation;
  • successful deactivation;
  • duplicate extension IDs;
  • route validity;
  • permission declarations;
  • resource cleanup;
  • supported host API usage;
  • compatibility with supported API versions.

10.3 Fake development host ​

Provide a small development application that can load a plugin independently from the real product.

It should allow plugin developers to test:

  • navigation contribution;
  • pages;
  • light/dark themes;
  • permissions;
  • mocked backend responses;
  • backend failures;
  • latency;
  • authentication states;
  • multiple host API versions.

10.4 Browser integration tests ​

Use a browser test framework to run:

text
Fake Host + Real Plugin + Mock Backend

and:

text
Fake Host + Real Plugin + Real Plugin Backend

10.5 Full-system tests ​

Keep a smaller set of tests using:

text
Real Host + Real Plugin + Real Plugin Backend

These should validate critical end-to-end workflows rather than every UI detail.


11. Compatibility and Versioning ​

The plugin host API must be explicitly versioned.

Example:

json
{
  "requires": {
    "hostApi": ">=1.1 <2.0"
  }
}

Design requirement: The host must validate compatibility before it loads the plugin. The current runtime records requires.hostApi but does not reject incompatible ranges.

Avoid silently changing existing behavior within a host API version.

Prefer additive evolution:

text
1.0
  + navigation
  + pages

1.1
  + notifications

1.2
  + toolbar

Breaking changes should require a new major version.

The test kit should allow plugins to run compatibility tests against every supported host API version.


12. Plugin Discovery and Installation ​

The application may support one or more discovery mechanisms:

  • static configuration;
  • server-side plugin registry;
  • filesystem or deployment configuration;
  • remote plugin catalog.

A plugin installation record should associate:

text
plugin identity
       |
       +-- UI artifact location
       |
       +-- logical services
       |
       +-- granted permissions
       |
       +-- configuration

Example server-side configuration. The current loader reads only id and services.<name>.target:

yaml
plugins:
  - id: customer-management
    services:
      customer-api:
        target: http://customer-service:8080

A possible future design adds a manifest URL and a permissions list to each entry. The loader does not support these keys. Do not add them to wanaku.yaml.

This environment-specific configuration belongs to the platform, not the plugin bundle.


13. Security Guidelines ​

This section defines the target security model. Current plugins are trusted, same-origin code. The runtime does not enforce manifest permissions or isolate plugin JavaScript.

Treat plugins as separate trust boundaries.

The host should:

  • validate plugin manifests;
  • enforce supported host API versions;
  • grant only declared and approved capabilities;
  • restrict backend access to approved logical services;
  • validate service paths where appropriate;
  • apply authorization server-side;
  • apply CSP and integrity controls where appropriate;
  • log plugin activation and relevant API operations;
  • avoid exposing internal application state directly.

For trusted first-party plugins, an ES module loaded into the main page may be sufficient.

For untrusted third-party plugins, stronger isolation may be required, such as:

text
sandboxed iframe
    +
message-based host API

Do not assume that WebAssembly alone provides DOM or browser security isolation.


14. Error Handling ​

A plugin failure should not make the entire host UI unusable.

The plugin runtime should isolate plugin lifecycle failures and present a controlled error state.

Example:

text
Plugin failed to load

Customer Management could not be started.

[Retry]

The host should catch:

  • manifest failures;
  • module loading failures;
  • activation failures;
  • route rendering failures;
  • backend request failures;
  • compatibility failures.

Plugins should use host-provided error and notification primitives where available.


15. Observability ​

Plugin-originated operations should be observable across frontend and backend boundaries.

Prefer propagating:

  • correlation IDs;
  • trace IDs;
  • plugin ID;
  • plugin version;
  • user/session context where appropriate.

Conceptually:

text
Plugin UI
   |
   | plugin=customer-management
   | trace-id=...
   v
Rust Backend
   |
   v
Plugin Backend

This makes debugging plugin/backend interactions significantly easier.


Main application repository ​

The main application should own:

text
plugin-api/
plugin-runtime/
plugin-testkit/
plugin-development-host/
plugin-manifest-schema/
service-resolution/

Responsibilities include:

  • defining the plugin contract;
  • maintaining compatibility;
  • loading plugins;
  • providing host capabilities;
  • proxying/resolving backend services;
  • enforcing permissions;
  • providing the plugin development environment.

Plugin repository ​

A plugin repository should typically contain:

text
ui/
  plugin.json
  src/
  tests/

backend/            # optional and independent deployable
  ...

The UI should depend only on public plugin SDK packages and documented REST API contracts.

The backend should not depend on internal host UI implementation details.


This section describes the target scope. The implementation-status section identifies the parts that are available now.

For the first version, keep the platform deliberately small.

Recommended initial capabilities:

text
Plugin lifecycle
  activate
  deactivate

UI
  navigation.add
  pages.register

Host services
  http.get/post/put/delete
  notifications.show

Platform
  manifest
  permissions
  host API versioning
  development host
  test kit

Avoid adding highly generic extension mechanisms until concrete use cases require them.

For example, prefer:

ts
host.navigation.add(...)

over a generic API such as:

ts
host.extend("anything", {...})

Explicit contracts are easier to version, test, document, and secure.


18. Decision Summary ​

The recommended architecture is:

text
                 Main Application

          Rust Backend + Carbon Web UI
                    |
                    v
            Stable Plugin API
                    |
                    v
               ES Module Plugin
                    |
          +---------+----------+
          |                    |
          v                    v
     UI extensions       host.http(...)
                               |
                               v
                        Rust backend proxy
                               |
                               v
                     Plugin / External REST API

Key decisions:

  1. Use ES modules as the default plugin packaging model.
  2. Keep WebAssembly optional and internal to individual plugins.
  3. Expose explicit extension points rather than arbitrary DOM manipulation.
  4. Keep the plugin UI independent from its backend deployable.
  5. Use logical service identifiers instead of backend hostnames.
  6. Route REST access through a host-provided HTTP capability when possible.
  7. Centralize authentication, authorization context, routing, and observability in the host/platform.
  8. Publish a plugin SDK, test kit, and fake development host.
  9. Version the host plugin API and test compatibility explicitly.
  10. Keep the first plugin API small and evolve it through concrete extension requirements.

The most important boundary is:

A plugin should depend on the public plugin API and its backend API contracts, never on the private implementation details of the main application.

Next steps ​