Nitrado:WebServer includes built-in support for HTML templating using Thymeleaf. This allows plugins to render dynamic HTML pages with a consistent look and feel.
To use templating, extend TemplateServlet instead of HttpServlet:
import net.nitrado.hytale.plugins.webserver.WebServerPlugin;
import net.nitrado.hytale.plugins.webserver.servlets.TemplateServlet;
public class MyServlet extends TemplateServlet {
public MyServlet(WebServerPlugin parentPlugin, JavaPlugin thisPlugin) {
super(parentPlugin, thisPlugin);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
var vars = new HashMap<String, Object>();
vars.put("title", "My Page");
vars.put("items", List.of("Item 1", "Item 2", "Item 3"));
this.renderTemplate(req, resp, "myplugin.mytemplate", vars);
}
}The renderTemplate method takes:
req/resp: The servlet request and responsetemplate: The template name (without.htmlextension)variables: A map of variables to pass to the template (optional)
Place your template files in src/main/resources/templates/ with a .html extension.
Nitrado:WebServer provides a base layout that includes Bootstrap CSS, a sidebar with plugin navigation, and user authentication status. To use it, add this to your template's <html> tag:
<html th:replace="~{nitrado.webserver :: layout(~{::title}, ~{::section})}" lang="en">
<head>
<title>My Page Title</title>
</head>
<body>
<section class="container py-4">
<!-- Your content here -->
<h1>Hello World</h1>
</section>
</body>
</html>The th:replace attribute replaces your entire HTML with the base layout, injecting:
~{::title}- Your page's<title>element~{::section}- Your page's<section>element as the main content
The base layout provides optional slots for injecting custom CSS and JavaScript into your pages. Use elements with specific IDs that the layout will pick up:
| Element ID | Location in Layout | Purpose |
|---|---|---|
additionalCss |
End of <head> |
Custom stylesheets or styles |
additionalJavascript |
End of <body> |
Custom scripts |
Example with both CSS and JavaScript:
<html th:replace="~{nitrado.webserver :: layout(~{::title}, ~{::section})}" lang="en">
<head>
<title>My Page</title>
<link id="additionalCss" th:href="@{/MyOrg/MyPlugin/static/css/my-styles.css}" rel="stylesheet"/>
</head>
<body>
<section class="container py-4">
<h1>My Content</h1>
</section>
<th:block id="additionalJavascript">
<script th:src="@{/MyOrg/MyPlugin/static/js/my-script.js}" type="text/javascript"></script>
</th:block>
</body>
</html>Notes:
- Thymeleaf's
@{...}resolves URLs from the server root, so you must include the full path with your plugin group and name (e.g.,@{/MyOrg/MyPlugin/static/...}) - Static files are not served automatically (see "Serving Static Files" in the next section)
- For CSS, apply the
id="additionalCss"directly to the<link>or<style>element - For JavaScript, wrap your
<script>tags in a<th:block id="additionalJavascript">element - If these elements are not present in your template, the slots are simply omitted
- Paths are case-sensitive - use the exact casing from your plugin's
PluginIdentifier
To serve your custom CSS and JavaScript files, use the StaticFileServlet. See Serving Static Files for complete documentation on:
- Serving files from JAR resources or filesystem
- Hybrid approaches with filesystem override and JAR fallback
- Supported MIME types
Below is an example from Nitrado:Query:
<html th:replace="~{nitrado.webserver :: layout(~{::title}, ~{::section})}" lang="en">
<head>
<title>Nitrado:Query</title>
</head>
<body>
<section class="container py-4">
<h1 class="mb-5">Server Information</h1>
<!-- Conditional rendering -->
<div th:if="${response.server != null}" class="mb-4">
<h5>Server</h5>
<table class="table table-striped">
<tbody>
<tr><th scope="row">Name</th><td th:text="${response.server.name}"></td></tr>
<tr><th scope="row">Version</th><td th:text="${response.server.version}"></td></tr>
</tbody>
</table>
</div>
<!-- Iterating over collections -->
<div th:if="${response.players != null}" class="mb-4">
<h5>Players</h5>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>UUID</th>
</tr>
</thead>
<tbody>
<tr th:each="player : ${response.players}">
<td th:text="${player.name}"></td>
<td th:text="${player.uuid}"></td>
</tr>
</tbody>
</table>
<p th:if="${response.players.isEmpty()}" class="text-muted">No players online</p>
</div>
</section>
</body>
</html>The following variables are automatically available in all templates:
| Variable | Type | Description |
|---|---|---|
user |
HytaleUserPrincipal |
The authenticated user (if any) |
version |
String |
The WebServer plugin version |
pluginsByGroups |
Map<String, List> |
Registered plugins grouped by group (if user has permission) |
pluginGroups |
List<String> |
Sorted list of plugin group names (if user has permission) |
Nitrado:WebServer supports a theme folder system that allows you to override templates and static files without rebuilding your plugin JAR. This is useful for:
- Development: Edit templates and see changes immediately without recompiling
- Customization: Server admins can customize the look and feel without modifying plugin code
Place theme files in the plugin's data directory under theme/:
mods/
└── Nitrado_WebServer/
└── theme/
├── templates/
│ └── nitrado.webserver.html # Override base layout
└── static/
└── css/
└── custom.css # Custom static files
For your own plugin:
mods/
└── YourGroup_YourPlugin/
└── theme/
└── templates/
└── yourplugin.mytemplate.html # Override your plugin's template
Templates are resolved in the following order (first match wins):
- Plugin's theme folder (
mods/YourGroup_YourPlugin/theme/templates/) - Plugin's JAR (
templates/in the plugin JAR) - WebServerPlugin's theme folder (
mods/Nitrado_WebServer/theme/templates/) - WebServerPlugin's JAR (default templates)
This allows you to:
- Override your own plugin's templates during development
- Override WebServerPlugin's base layout for custom styling
- Fall back to defaults when no override exists
For rapid template development:
- Build and deploy your plugin once to register the servlet
- Copy your template files to the theme folder:
mkdir -p mods/YourGroup_YourPlugin/theme/templates cp src/main/resources/templates/*.html mods/YourGroup_YourPlugin/theme/templates/ - Edit templates in the theme folder — changes are reflected immediately (no caching)
- When satisfied, copy the templates back to
src/main/resources/templates/and rebuild
Alternative: Using symlinks
Instead of copying files back and forth, you can symlink the theme folder directly to your project's templates directory:
# Remove the theme folder if it exists
rm -rf mods/YourGroup_YourPlugin/theme
# Create a symlink to your project's resources
ln -s /path/to/your/project/src/main/resources mods/YourGroup_YourPlugin/themeThis way, edits to your source templates are immediately reflected without any copying. When you're ready to release, simply rebuild the JAR - the templates are already in the right place.
Note: Theme folder templates are not cached, so every request loads the template from disk. This is ideal for development but may impact performance in production. Remove theme folders (or symlinks) when deploying to production.
Common Thymeleaf attributes:
| Attribute | Description | Example |
|---|---|---|
th:text |
Sets element text content | <span th:text="${user.name}"></span> |
th:if |
Conditional rendering | <div th:if="${items != null}"> |
th:unless |
Inverse conditional | <div th:unless="${items.isEmpty()}"> |
th:each |
Iteration | <tr th:each="item : ${items}"> |
th:href |
Dynamic href attribute | <a th:href="@{/path}"> |
th:class |
Dynamic class attribute | <div th:class="${active ? 'active' : ''}"> |
For complete documentation, see the Thymeleaf documentation.