Skip to content

Extensibility

Mateu is extensible at multiple levels: individual UI elements, internal framework services, routing, and the frontend renderer itself. Most applications only need the first level; the others exist for platforms or framework integrators.


The most common extension point is embedding custom or third-party web components. Use Element.builder() to render any HTML element by tag name:

Element.builder()
.name("my-custom-component")
.attributes(Map.of(
"src", "/path/to/file.gltf",
"data-theme", "dark"
))
.style("width: 100%; height: 400px;")
.build()

To load the component’s JavaScript, inject a <script> tag using UICommandType.AddContentToHead:

@Override
public List<UICommand> commands(HttpRequest httpRequest) {
return List.of(UICommand.builder()
.type(UICommandType.AddContentToHead)
.data(Element.builder()
.name("script")
.attributes(Map.of(
"id", "my-component-js",
"src", "/js/my-component.js",
"type", "module"
))
.build())
.build());
}

See Custom web components for the full pattern, including event listeners and OnValueChangeTrigger.


Mateu’s internal services are wired via Spring (or Micronaut) dependency injection. Any internal service interface can be replaced by annotating your implementation with @Primary:

@Primary
@Service
public class MyCustomSessionStore implements SessionStore {
// your custom implementation
}

The framework picks up @Primary beans and uses them in place of the defaults. This works for:

  • Custom serializers/deserializers
  • Custom authorization logic
  • Custom route resolvers
  • Custom component mappers

Implement a @Primary RouteResolver bean to intercept and rewrite routes before they are dispatched. Combined with a custom resolver, you can implement dynamic routing based on tenant, feature flags, or external configuration:

@Route(value = "/custom-resolver", parentRoute = "")
public class CustomRouteResolverPage implements ComponentTreeSupplier {
// page loaded via a custom routing strategy
}

Embed an external frontend inside a Mateu page using MicroFrontend:

MicroFrontend.builder()
.url("https://other-service.internal/ui")
.style("width: 100%; height: 600px;")
.build()

The embedded frontend communicates with the host via custom events. Use OnCustomEventTrigger (see Triggers) to receive events from the embedded app and route them to a backend action.


Decoupled frontend (alternative renderers)

Section titled “Decoupled frontend (alternative renderers)”

The backend and frontend communicate through a protocol-agnostic UIDL (UI Definition Language) API:

Mateu backend → UIDL JSON → Mateu frontend renderer

The frontend renderer is swappable. Mateu ships a default renderer, but any renderer that speaks the UIDL protocol can be plugged in. This is the foundation for:

  • Rendering Mateu UI in mobile apps
  • Alternative web renderers (React, Lit, etc.)
  • Headless testing without a browser

Extension pointHow
Custom HTML elementsElement.builder() with a custom tag name
Custom web componentsElement.builder() + UICommandType.AddContentToHead
Embedded micro-frontendsMicroFrontend.builder()
Override internal services@Primary @Service on your implementation
Custom route resolution@Primary RouteResolver bean
Alternative renderersImplement the UIDL frontend protocol

  • Custom web components — detailed walk-through of Element, events, and script injection
  • Security — extend authorization by overriding the authorization service
  • Real-world patterns — how extensibility applies in distributed deployments