Dev UI
General overview
Quarkus Dev UI is a developer-friendly user interface that comes to life when you run your application in development mode (./mvnw quarkus:dev).
It serves as a powerful portal for exploring, debugging, and interacting with your application - all while it’s running - with zero code changes or restarts.
What is Dev UI?
The Dev UI is designed to boost developer joy by making Quarkus' inner workings observable and extensible. It offers:
-
Insight into available extensions and their capabilities
-
Access to runtime metadata and tools
-
A responsive frontend that integrates tightly with hot reload
-
A flexible way for extensions to expose configuration, documentation, and actions
Whether you’re a developer exploring your project or an extension author enhancing the experience, Dev UI is your entry point.
Layout at a Glance
When running in dev mode, Quarkus provides a dynamic, modular web interface accessible at http://localhost:8080/q/dev-ui. You can also click d in the console log, that will open your default browser at Dev UI.
The basic layout has the following parts:
-
Menu
-
Page
-
Footer
-
Card
-
Settings
Below is a visual overview of the layout:
Menu (Left Sidebar)
The menu on the left-hand side provides structured access to built-in pages:
| Menu Item | Description |
|---|---|
Extensions |
Displays all Quarkus extensions (used by the current application) as cards. Each card may provide configuration, documentation, or tools. |
Configuration |
Shows all configuration options (with current, default, and documentation info). Includes a config editor (text & form modes). |
Workspace |
A project file browser. Files can be opened and edited inline. Extensions may add workspace actions. |
Endpoints |
Lists all HTTP endpoints (REST, GraphQL, internal, etc.). Helps you inspect available routes in your app. |
Continuous Testing |
Monitors and controls continuous testing. View test results, rerun tests, or toggle testing state. |
Dev Services |
Displays information about automatically started services (e.g., databases, Kafka brokers). |
Build Metrics |
Provides performance metrics from the last build and reload cycle. |
Readme |
Renders your project’s |
Dependencies |
Shows all runtime and deployment dependencies with dependency path exploration and search functionality. |
| You can drag a page from an extension card into the menu to bookmark it for quick access. |
Page Area (Main Content)
The main panel displays content for the selected menu item or extension. Depending on the selected page, it may render:
-
Extension-specific tools and UIs (e.g., GraphQL UI, Swagger UI)
-
JSON viewers, code editors, or metrics charts
-
Interactive actions (e.g., buttons, toggles)
-
Documentation and links
Pages are modular and dynamically loaded. Extensions can contribute custom pages written using Web Components and interact with the backend via JSON-RPC.
Footer
The footer at the bottom is primarily used to display logs while the application runs in dev mode.
By default, the Server log (standard Quarkus log output) is selected, but additional log tabs may appear depending on which extensions are present. For example:
-
HTTP,GraphQL,Scheduler, etc., may appear when relevant extensions are in use -
Dev Serviceslog output from started services like databases, Kafka brokers, etc.
This live log view helps developers track behavior, debug issues, and monitor Dev Services without switching to the terminal.
Cards
Each Quarkus extension that contributes to Dev UI is represented as a card in the Extensions page. These cards provide quick access to features, documentation, configuration, and runtime tools offered by the extension.
Each card may include the following elements:
| Element | Description |
|---|---|
Logo (optional) |
An optional logo representing the extension or its domain |
Title |
The name of the extension as recognized in the Dev UI. |
Favorite |
Marks the card as a favorite. Favorited cards are always displayed first in the Extensions view for easier access. |
Guide (optional) |
Links to the online Quarkus guide for the extension (if available). |
Description |
A short explanation of what the extension does or enables. |
Page Links (optional) |
Interactive entries that navigate to custom pages contributed by the extension. A page link can also optionally contain a label that we discuss later. |
Configuration |
A shortcut to the configuration editor filtered to settings relevant to this extension. |
Underlying Library (optional) |
Shows the version of the main library powering the extension (if any). |
More Details |
Opens a dialog with all information |
| Clicking a page link inside a card navigates to a dedicated UI page for that feature. These pages can be bookmarked into the menu for quick access. |
Extensions are free to customize their cards and add interactive behaviors based on what they provide at runtime.
Guide for Extension Developers
Quarkus extensions can enhance the developer experience by contributing to the Dev UI. The rest of this guide outlines how to integrate your extension into the Dev UI, covering metadata configuration, adding pages to the card, menu, and footer, and best practices for dynamic content.
quarkus-extension.yaml
To have your extension appear in the Dev UI, ensure the quarkus-extension.yaml file is present in the src/main/resources/META-INF directory of your extension’s runtime module. This file provides metadata that the Dev UI uses to generate the extension card.
Example:
name: "Hibernate ORM"
description: "Define your persistent model with Hibernate ORM and Jakarta Persistence"
guide: "https://quarkus.io/guides/hibernate-orm"
metadata:
categories:
- "data"
config:
- "quarkus.hibernate-orm"
Key fields:
-
name: Displayed as the extension title in the Dev UI card. -
description: Shown as the card’s summary. -
guide: URL to the extension’s guide; used to render the guide icon on the card. -
metadata.config: Filters configuration keys shown when clicking "Configuration" on the card.
Adding Pages to the Dev UI
Your extension needs to have the following dependency in it’s deployment module:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devui-deployment-spi</artifactId>
</dependency>
This can be transitivly or directly and is only needed in deployment module. This gives your processor access to the relative Build Items.
Extensions can contribute interactive pages to the Dev UI in the following areas:
-
Card: Add links to pages directly on the extension card.
-
Footer: Add tabs to the footer for logs or other runtime information.
-
Menu: Add pages to the Dev UI’s left-hand menu.
-
Settings: Add a tab to the settings dialog
-
Unlisted: Pages that are not listed anywhere above, but are added to the router and can be linked to.
Card
The most common way to contribute to Dev UI is via Pages from your extension card in the Extension page.
To add links to your extension’s card, produce a CardPageBuildItem in a @BuildStep:
@BuildStep(onlyIf = IsLocalDevelopment.class) (1)
void createJokesPageOnCard(BuildProducer<CardPageBuildItem> cardsProducer) {
CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); (2)
cardPageBuildItem.setLogo("clown.svg", "clown.svg"); (3)
cardPageBuildItem.addPage(Page.webComponentPageBuilder() (4)
.title("Joke List") (5)
.icon("font-awesome-solid:cubes") (6)
.componentLink("qwc-jokes-list.js")); (7)
cardsProducer.produce(cardPageBuildItem);
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | To add anything on the card, you must return/produce a CardPageBuildItem. |
| 3 | You can optionally add a logo (dark and light mode) in deployment/src/main/resources/dev-ui/. |
| 4 | To add a link on the card, you can use the addPage method, as all links go to a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later. |
| 5 | You can (optionally) add the title, else it will be derived from the component link. |
| 6 | You can add an icon. All free font-awesome icons are available. |
| 7 | Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js |
Later we will discuss how to create this web component page.
|
Note about icons
If you find your icon at Font awesome, you can map as follow: Example <i class="fa-solid fa-house"></i> will map to font-awesome-solid:house, so fa becomes font-awesome and for the icon name, remove the fa-.
|
Optional: Library Version
You can add a underlying library badge on the card. This is usefull for cases where the extension is exposing a well known library. As example, the quarkus-hibernate-orm extension provides access to Hibernate. Showing the underlying Hibernate details could be useful information for the user:
This can be done using the addLibraryVersion method:
cardPageBuildItem.addLibraryVersion("org.hibernate.orm", "hibernate-core", "Hibernate", "https://hibernate.org/orm/");
You provide the groupId and artifactId (so that we can look up the version) and a Name and optionally a url.
Optional: Build Time Data
You can pass data gathered during build to the Page (js):
cardPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());
You can add multiple of these key-value pairs for all the data you know at build time that you need on the page.
The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).
Optional: Labels
You can add an optional label to the link in the card using one of the builder methods on the page builder. These labels can be
-
static (known at build time)
.staticLabel("staticLabelValue") -
dynamic (loaded at runtime)
.dynamicLabelJsonRPCMethodName("yourJsonRPCMethodName") -
streaming (continuously streaming updated values at runtime)
.streamingLabelJsonRPCMethodName("yourJsonRPCMethodName")
For dynamic and streaming labels, the method name is the name of the JsonRPC method, that we will discuss later.
Footer
Apart from adding a card and a page, extensions can add a tab to the footer. This is useful for things that are happening continuously. A page will disconnect from the DOM (and maybe the backend) when navigating away from that page (or webcomponent), and a log in the footer will be permanently connected to the DOM, as it’s always part of the view (or app)
There are 2 ways to add things to the footer. There easiest is to just expose the log in a BuildItem. Here we expect either a supplier (function) that return a Flow.Publisher<String> or a RuntimeValue<SubmissionPublisher<String>>:
@BuildStep(onlyIf = { IsLocalDevelopment.class }) (1)
public void createFooterLog(BuildProducer<FooterLogBuildItem> footerLogProducer){
footerLogProducer.produce(new FooterLogBuildItem("My Extension Log", () -> {(2)
return createLogPublisher();(3)
}));
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | You must return/produce a FooterLogBuildItem. |
| 3 | Here return a Flow.Publisher<String> that will stream the log |
The RuntimeValue is useful if the log is only available at runtime. If you can get the log in the deployment module you can use the supplier.
Or you can take full control of the UI and produce a FooterPageBuildItem and provide a custom web component in a js file:
@BuildStep(onlyIf = IsLocalDevelopment.class)(1)
void createJokesLog(BuildProducer<FooterPageBuildItem> footerProducer) {
FooterPageBuildItem footerPageBuildItem = new FooterPageBuildItem();(2)
footerPageBuildItem.addPage(Page.webComponentPageBuilder()(3)
.title("Joke Log")(4)
.icon("font-awesome-regular:face-grin-tongue-wink")(5)
.componentLink("qwc-jokes-log.js"));(6)
footerProducer.produce(footerPageBuildItem);
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | To add anything on the footer, you must return/produce a FooterPageBuildItem. |
| 3 | To add a tab in the footer, you can use the addPage method, as all tabs renders a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later. |
| 4 | You can (optionally) add the title, else it will be derived from the component link. |
| 5 | You can add an icon. All free font-awesome icons are available. |
| 6 | Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js |
Optional: Build Time Data
You can pass data gathered during build to the Page (js):
footerPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());
You can add multiple of these key-value pairs for all the data you know at build time that you need on the page.
The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).
Menu
To add a page to the Dev UI menu, produce a MenuPageBuildItem:
@BuildStep(onlyIf = IsLocalDevelopment.class) (1)
void createJokesMenu(BuildProducer<MenuPageBuildItem> menuProducer) {
MenuPageBuildItem menuPageBuildItem = new MenuPageBuildItem(); (2)
menuPageBuildItem.addPage(Page.webComponentPageBuilder() (3)
.title("One Joke") (4)
.icon("font-awesome-regular:face-grin-tongue-wink") (5)
.componentLink("qwc-jokes-menu.js")); (6)
menuProducer.produce(menuPageBuildItem);
}
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | To add anything on the menu, you must return/produce a MenuPageBuildItem. |
| 3 | To add a link in the menu, you can use the addPage method, as all links go to a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later. |
| 4 | You can (optionally) add the title, else it will be derived from the component link. |
| 5 | You can add an icon. All free font-awesome icons are available. |
| 6 | Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js |
This page will appear alongside built-in menu items like "Configuration" or "Workspace."
Optional: Build Time Data
You can pass data gathered during build to the Page (js):
menuPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());
You can add multiple of these key-value pairs for all the data you know at build time that you need on the page.
The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).
Settings
To add a tab to the Dev UI settings dialog, produce a SettingPageBuildItem:
@BuildStep(onlyIf = IsLocalDevelopment.class) (1)
void createMCPSettingsTab(BuildProducer<SettingPageBuildItem> settingPageProducer) {
SettingPageBuildItem mcpSettingTab = new SettingPageBuildItem();(2)
mcpSettingTab.addPage(Page.webComponentPageBuilder() (3)
.title("Dev MCP")(4)
.icon("font-awesome-solid:robot") (5)
.componentLink("qwc-dev-mcp-setting.js")); (6)
settingPageProducer.produce(mcpSettingTab);
}
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | To add anything on the settings tab, you must return/produce a SettingPageBuildItem. |
| 3 | To add a tab in the setting dialog, you can use the addPage method. |
| 4 | You can (optionally) add the title, else it will be derived from the component link. |
| 5 | You can add an icon. All free font-awesome icons are available. |
| 6 | Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js |
This page will will be loaded as the content for that tab in the settings dialog
Unlisted Page
Lastly, you can add pages that is not listed anywhere, but added to the Router, and can be navigated to. This is usefull if you are building very complex pages that navigates to more pages.
To do this, produce a UnlistedPageBuildItem:
@BuildStep(onlyIf = IsLocalDevelopment.class) (1)
void createMCPUnlistedPages(BuildProducer<UnlistedPageBuildItem> unlistedPageProducer) {
UnlistedPageBuildItem mcpOtherPages = new UnlistedPageBuildItem(); (2)
mcpOtherPages.addPage(Page.webComponentPageBuilder() (3)
.title("Tools") (4)
.icon("font-awesome-solid:screwdriver-wrench") (5)
.componentLink("qwc-dev-mcp-tools.js")); (6)
unlistedPageProducer.produce(mcpOtherPages);
}
}
| 1 | Always make sure that this build step is only run when in local dev mode. |
| 2 | To add an unlisted page, you must return/produce a UnlistedPageBuildItem. |
| 3 | Use the addPage method. |
| 4 | You can (optionally) add the title, else it will be derived from the component link. |
| 5 | You can add an icon. All free font-awesome icons are available. |
| 6 | Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js |
This page will will be registered to the Router and can be nagigated to by a normal link or using the Router:
import { RouterController } from 'router-controller'; (1)
// ...
routerController = new RouterController(this); (2)
// ...
let unlistedPages = this.routerController.getPagesForCurrentNamespace(); (3)
// or if you not in your own space
// let unlistedPages = this.routerController.getPagesForNamespace("your namespace"); (4)
// ... loop over page
this.routerController.go(page); (5)
| 1 | Import the RouterController |
| 2 | Create a new instance passing in this to scope it to your namespace |
| 3 | Get all pages of your namespace |
| 4 | Or if you are on another page (not one of yours) you can get the pages for your namespace |
| 5 | Then you can loop over the pages and build the links/buttons. To actions the navigation your use the go method |
Building Web Components
Dev UI uses Lit to make building these web components easier. You can read more about Web Components and Lit:
Most of the JavaScript code in Dev UI is written using Lit, so look for the available operations in the Lit documentation. For instance, for each cycle can be done like this:
render() {
return html`
<ul>
${this.colors.map((color) =>
html`<li style="color: ${color}">${color}</li>`
)}
</ul>
`;
}
Basic structure of a Web component page
A Web component page is just a JavaScript Class that creates a new HTML Element:
import { LitElement, html, css} from 'lit'; (1)
import { jokes } from 'build-time-data'; (2)
export class QwcJokesList extends LitElement { (3)
static styles = css` (4)
.buttonBar {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
width: 90%;
color: var(--lumo-primary-text-color); (5)
}
.buttonBar .button {
width: 100%;
}
`;
static properties = {
_jokes: {state: true},
_numberOfJokes: {state: true},
_message: {state: true},
_isStreaming: {state: true} (6)
};
constructor() { (7)
super();
this._jokes = [];
this._numberOfJokes = 0;
this._isStreaming = false;
}
connectedCallback() { (8)
super.connectedCallback();
jokes.forEach((joke) =>{
var item = this._toJokeItem(joke);
this._jokes.push(item);
});
this._numberOfJokes = this._jokes.length;
}
disconnectedCallback() { (9)
if(this._isStreaming){
this._observer.cancel();
}
super.disconnectedCallback()
}
render() { (10)
return html`<h3>Here are ${this._numberOfJokes} jokes</h3> (11)
<vaadin-message-list .items="${this._jokes}"></vaadin-message-list>
${this._renderLoadingMessage()}
<div class="buttonBar">
<vaadin-button class="button" theme="success" @click=${() => this._fetchMoreJokes()}>
<vaadin-icon icon="font-awesome-solid:comment"></vaadin-icon> Tell me more jokes
</vaadin-button>
<vaadin-checkbox class="button" label="Stream new jokes continuously" @input=${(e) =>this._startStopStreaming(e)}></vaadin-checkbox>
</div>
`;
}
// ... more private methods
}
customElements.define('qwc-jokes-list', QwcJokesList); (12)
| 1 | You can import Classes and/or functions from other libraries.
In this case, we use the LitElement class and html & css functions from Lit |
| 2 | Build time data as defined in the Build step and can be imported using the key and always from build-time-data. All keys added in your Build step will be available. |
| 3 | The component should be named in the following format: Qwc (stands for Quarkus Web Component), then Extension Name, then Page Title, all concatenated with Camel Case. This will also match the file name format as described earlier. The component should also extend LitComponent. |
| 4 | CSS styles can be added using the css function, and these styles only apply to your component. |
| 5 | Styles can reference globally defined CSS variables to make sure your page renders correctly, especially when switching between light and dark mode. You can find all CSS variables in the Vaadin documentation (Color, Sizing and Spacing, etc.) |
| 6 | Properties can be added. Use _ in front of a property if that property is private. Properties are usually injected in the HTML template, and can be defined as having state, meaning that if that property changes, the component (or part of it) should re-render. In this case, the jokes are Build time data that we gathered while building. |
| 7 | Constructors (optional) should always call super first and then set the default values for the properties. |
| 8 | connectedCallback is a method from Lit that gets called when this component are connected to the DOM. This is a good place to do 'page load' type things, like fetching data from the backend (we will discuss this later) |
| 9 | disconnectedCallback us a method from Lit that gets called when this component are disconnected from the DOM. This is a good place to do any clean up. |
| 10 | render is a method from Lit that will be called to render the page. In this method, you return the markup of the page you want. |
| 11 | You can use the html function from Lit, which gives you a template language to output the HTML you want. Once the template is created, you must only set or change the properties to re-render the page content. Read more about Lit html |
| 12 | You must always register your Web component as a custom element, with a unique tag. Here, the tag will follow the same format as the filename (qwc dash extension name dash page title ); |
Hot reload
You can update a screen automatically when a hot reload happens. To do this, replace the LitElement that your Webcomponent extends with QwcHotReloadElement.
QwcHotReloadElement extends LitElement, so your component is still a Lit Element.
When extending a QwcHotReloadElement, you have to use the hotReload method. (You must also still provide the render method from Lit)
import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element';
// ...
export class QwcMyExtensionPage extends QwcHotReloadElement {
render(){
// ...
}
hotReload(){
// ..
}
}