Skip to content
  • Our Product
    • Namazu Elements
      • What is Elements?
      • Why open source?
      • Docs
        • Namazu Elements in Five Minutes or Less
        • RESTful APIs Library
        • Security Model
        • Accessing the Web UI (CMS)

    Our Product

    A logomark with three layered rhombuses adorning the lettermark that says Elements in bold all-caps sans-serif letters.
    • What is Namazu Elements? Discover our easy-to-use backend network solution built for online games. Rapidly enables full-scale multiplayer games or online solo adventures.
    • Why open source? Is there a truly open source server backend for connected games? There is now. Download and run a local copy of Namazu Elements and try it for yourself.
    Download Namazu Elements

    Get started

    • Quick start Read our Elements 5-minute quick start guide
    • Documentation Read our developer docs for learning more about Elements
    • RESTful APIs A full list of core API specs for working with the Elements framework
    • Security An overview of the server-authoritative security model of Elements
    • Accessing the CMS Manage your game with ease via the Namazu Elements CMS.

    Co-development Reimagined

    • Best real-time game backends in 2026 If you're researching an alternative to your current backend solution, we've prepared a report of all of the backend solutions on the market in 2026 and how Namazu Elements compares.
      Co-dev

    Recent Posts

    • The watercolor-styled Namazu Studios logo over a giant namazu lurking in the depth
      Namazu Studios Featured in San Diego Business Journal
      22 Sep 2025 Press
    • Namazu Elements 3.1 Released – Service Layer Fixes, Secure APIs, and Steam Bug Fix
      22 Apr 2025 Release Notes
  • Case Studies
  • About Us
  • News
  • Services
  • Book a call
namazu-studios-logo
Book a call
Popular Search Roblox

Getting Started

  • Namazu Elements in Five Minutes or Less
  • Overview
  • Accessing the Web UI (CMS)
  • CMS Feature Overview

Fundamentals

  • Why You Need a Server (and What “Authoritative” Means)
  • Elements as a Game Runtime
  • Where Your Authoritative Code Runs
  • Lifecycles and Flows

General Concepts

  • Custom Elements
  • Data Models
  • Security Model
  • N-Tier Architecture

Namazu Elements Core Features

  • Email Service
  • Applications
  • Sessions
  • Users and Profiles
  • Digital Goods
  • Progress and Missions
  • Progress and Missions (3.4+)
  • Leaderboards
  • Matchmaking – Comprehensive Guide
  • Friends
  • Followers
  • Product Bundles and SKUs
  • Item Ledger
  • Receipts
  • Reward Issuances
  • Save Data
  • Metadata
  • Metadata (3.4+)
  • Queries
  • User Authentication / Sign In
    • What is a User?
    • Email Verification
    • User Authentication in Elements
    • Account Linking
    • Setting Up Twitch OIDC Login (Backend)
    • OIDC Login for Thick Clients (Browser Redirect Flow)
    • Auth Schemes
      • Auth Schemes
      • OAuth2
      • OIDC
  • Features
    • Web3
      • Wallets
      • Vaults
      • Omni Chain Support
      • Smart Contracts
        • Smart Contracts
  • Queries
    • Advanced Operators
    • Object Graph Navigation
    • Boolean Queries
    • Base Query Syntax
  • Advanced Operators
    • .name
    • .ref

Your Game Code - Adding Custom Elements

  • Custom Code Overview
  • Windows Setup
  • Mac OS Setup
  • Ubuntu Linux Setup
  • Element Anatomy: A Technical Deep Dive
  • Introduction to Guice and Jakarta in Elements
  • Structuring your Element
  • Events
  • Packaging an Element with Maven
  • Deploying an Element
  • Preparing for code generation
  • Properties
  • Websockets
  • RESTful APIs
  • Direct MongoDB Access (3.5+)
  • Building the Example Element: A Complete Walkthrough
  • Building the Kotlin Example Element: A Complete Walkthrough

Configuration

  • Configuring External URLs for Deployment
  • Matchmaking – Comprehensive Guide
  • Direct Database Access and Batch Configuration
  • Connecting Namazu Elements to a TLS-Enabled MongoDB Replica Set
  • Batch Samples
    • Mission Upload Bash Script Sample
    • Item Upload Bash Script Sample

RESTful APIs

  • Importing into Postman
  • RESTful APIs Library
  • Swagger and Swagger UI

Add-Ons

  • Crossplay
    • Crossfire Client Libraries (JVM & Browser)
    • Crossfire Protocol Reference
    • Crossfire: Custom Matchmaking Algorithms
    • Namazu Crossfire (Multiplayer)
    • Deploying Namazu Crossfire in your game
  • Roblox
    • Roblox Overview
    • Secure Player Authentication & Registration
    • Global Matchmaking
    • Roblox Security Best Practices
  • Container on Demand
    • Namazu Conductor
    • Namazu Conductor Admin API
    • Configuring Namazu Conductor Providers
  • Commerce
    • Stripe
      • Stripe
      • Configuring the Stripe Element
      • Stripe REST API Reference
      • Stripe REST API Reference

Game Engine & Client Support

  • Unity
    • Elements Unity Plugin
    • Unity Crossfire Plugin
  • Gamemaker
    • Incorporating GMEXT-Elements into a GameMaker Project

Troubleshooting

  • Common Issues with Docker
  • Local SDK
    • Unable to deploy application : dev.getelements.elements.sdk.exception.SdkElementNotFoundException
    • Could not load class : java.lang.NoClassDefFoundError
  • Namazu Elements Community Edition
    • Common Issues with Docker
    • Unable to deploy application : dev.getelements.elements.sdk.exception.SdkElementNotFoundException
    • Running in the IDE
      • Exception in monitor thread while connecting to server localhost:27017
      • Could not deployAvailableApplications Jetty server Failed to bind to /0.0.0.0:8080 Address already in use

Releases

  • 3.8 Release Notes
  • 3.7 Release Notes
  • 3.6 Release Notes
  • 3.5 Release Notes
  • 3.4 Release Notes
  • 3.3 Release Notes
  • 3.2 Release Notes
  • 3.1 Release Notes
View Categories
  • Home
  • Docs
  • Your Game Code - Adding Custom Elements
  • Building the Example Element: A Complete Walkthrough

Building the Example Element: A Complete Walkthrough

Est. read time: 34 min read

AI Doc Summarizer Doc Summary
AI Doc Summarizer Thinking Thinking

A complete tour of the Example Element project, from a blank checkout to a running backend with a custom REST API and a dashboard plugin.


What You’ll Build #

The Example Element is a reference Custom Element for Namazu Elements 3.8. Once running locally, it gives you:

  • A REST API mounted at /Element/example/REST/api with an open probe endpoint, an authenticated endpoint that greets the logged-in User, and a POST/PUT demo resource.
  • A Guice-wired Service layer that injects the SDK’s own UserService.
  • A dashboard UI plugin (React, built as a standalone bundle) that appears in the Elements admin dashboard sidebar.
  • A packaged .elm archive you can deploy to any Elements instance.

This guide covers setup end-to-end, then breaks down every Maven module and every source file so you understand exactly what each piece does and why it’s there.


Prerequisites #

  1. Java 21 (JDK)
  2. Apache Maven
  3. Docker with Docker Compose (used to run a local MongoDB replica set)
  4. Git
  5. Node.js (the build can also fetch its own pinned Node v22.14.0 via a Maven Profile — see the Maven deep dive below — but a system Node install is the fastest path for local iteration)

Note

Since Elements is a Java 21 project, we recommend IntelliJ as your IDE. See the platform-specific setup guides (Windows, Mac, Linux) if you haven’t set up a local Elements development environment before.


Step by Step: Setup to Running #

1. Clone the Repository #

git clone https://github.com/NamazuStudios/Element-example.git
cd Element-example

The project is a four-module Maven build:

Element-example/
├── api/          # Exported interfaces (other Elements depend on this)
├── ui/           # TypeScript/React UI plugin source (Vite; not deployed directly)
├── Element/      # Implementation module — builds the .elm archive
├── debug/        # Local development runner (not deployed)
└── services-dev/ # Docker Compose services (MongoDB) for local dev

2. Build Everything #

mvn install

This compiles api and Element in dependency order, packages Element into a .elm archive, and installs everything (including the classified API jar and the .elm artifact) into your local Maven repository. The ui module’s real npm build is skipped unless you pass -Pbuild-ui — see the Maven deep dive below for why.

3. Start MongoDB #

Docker compose -f services-dev/Docker-compose.yml up -d

This starts a single-node MongoDB 6.0.9 instance configured as replica set local-test on port 27017, plus a one-shot rs-init sidecar container that waits for Mongo to accept connections and then runs rs.initiate(...) to actually form the replica set. Elements’ core SDK requires a replica set (it relies on transactions/change streams), so a plain standalone mongod will not work.

4. Run the Element Locally #

Run debug/src/main/java/run.java from your IDE, or from the command line:

mvn -pl debug exec:java -Dexec.mainClass=run

Note

The working directory must be the project root (Element-example/), because run.java shells out to npm in ui/ and Docker compose in services-dev/ using relative paths. In IntelliJ: Run → Edit Configurations → set the working directory to the project root.

When it runs, run.java does the following, in order:

  1. If ui/node_modules doesn’t exist yet, runs npm install in ui/ (first run only).
  2. Runs npm run build in ui/, which builds both the superuser and User dashboard plugin bundles and writes them directly into Element/src/main/ui/superuser/ and Element/src/main/ui/User/.
  3. Runs Docker compose up -d in services-dev/ (the same command as step 3 — safe to run again).
  4. Builds an ElementsLocalBuilder from the SDK’s local runtime, configured to build and deploy com.example.Element:Element:elm:1.0-SNAPSHOT from source.
  5. Calls local.start() — this triggers the Maven build of the Element module (picking up the freshly built UI bundles), then boots the full local Elements runtime with your Element deployed.
  6. Calls local.run(), which blocks and serves requests until you stop the process.

After a short startup you’ll see log output indicating the Elements runtime is listening, by default on port 8080.

5. Verify It’s Working #

Hit the open probe endpoint — no authentication required:

curl http://localhost:8080/Element/example/REST/api/helloworld
# => Hello world!

Hit the authenticated endpoint without a Session — you’ll be greeted as a guest:

curl http://localhost:8080/Element/example/REST/api/hellowithauthentication
# => Hello, Guest!

Create a User, log in to get a Session secret, then call it again with the Elements-SessionSecret header — see Creating a User and User Authentication in Elements if you need a refresher on those two calls. With a valid Session you should get Hello, <your name>! instead of Hello, Guest!.

Explore the generated OpenAPI spec (this Element’s routes appear alongside the REST of the platform’s, tagged Example):

http://localhost:8080/api/REST/openapi.json

Finally, log in to the dashboard at http://localhost:8080/admin/login as a superuser and look for “Example Element” in the sidebar — that’s the React plugin bundle shipped from ui/src/superuser/ExamplePlugin.tsx, served from this Element’s UI content tree.


Maven Structure Deep Dive #

Root pom.xml #

The root is a pure aggregator (<packaging>pom</packaging>) with no parent of its own. It declares the four modules, pins shared properties, and centralizes dependency versions and scopes:

<modules>
    <module>api</module>
    <module>ui</module>
    <module>Element</module>
    <module>debug</module>
</modules>

<properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <elements.version>3.8.14</elements.version>
    <api.classifier>${project.groupId}.api</api.classifier>
    <!-- swagger.version, Guice.version, rs.api, Jakarta.websocket.version,
         crossfire.version, servlet.api, logback.version also declared here -->
</properties>

The important piece is dependencyManagement, which imports the Elements SDK BOM:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.getelements.elements</groupId>
            <artifactId>sdk-bom</artifactId>
            <version>${elements.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.example.Element</groupId>
            <artifactId>api</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.example.Element</groupId>
            <artifactId>api</artifactId>
            <version>${project.version}</version>
            <classifier>${api.classifier}</classifier>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Importing sdk-bom means every child module gets the correct version and the correct scope for every SDK artifact automatically — child pom.xml files never declare a <version> or <scope> for an SDK dependency. The api artifact is declared twice: once as a plain dependency (used at compile time by Element) and once with the ${api.classifier} classifier (a second, classified jar of the same code — see below).


The api Module: a Classified Jar #

api/pom.xml depends on nothing but the core SDK (scope provided) — by design. The API module should stay as lean as possible, since every API jar in a deployment shares a common classpath with every other Element’s API jar; bloating it with third-party libraries invites classpath conflicts across unrelated Elements.

The interesting part is its build configuration:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <executions>
        <execution>
            <id>classified-jar</id>
            <phase>package</phase>
            <goals><goal>jar</goal></goals>
            <configuration>
                <classifier>${api.classifier}</classifier>
            </configuration>
        </execution>
    </executions>
</plugin>

This produces a second jar — api-1.0-SNAPSHOT-com.example.Element.api.jar — in addition to the normal jar. The plain jar is what Element compiles against; the classified jar is what gets copied into the api/ directory inside the final .elm archive, which is how other deployed Elements can depend on and resolve this Element’s exported interfaces at runtime.


The Element Module: Dependencies #

Element/pom.xml declares no versions or scopes — every dependency’s scope comes from the imported BOM:

<dependencies>
    <dependency> <!-- own API, classified jar -->
        <groupId>com.example.Element</groupId>
        <artifactId>api</artifactId>
        <classifier>${api.classifier}</classifier>
    </dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk</artifactId></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-model</artifactId></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-Service</artifactId></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-spi-Guice</artifactId></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-Jakarta-rs</artifactId></dependency>
    <dependency><groupId>com.google.inject</groupId><artifactId>Guice</artifactId></dependency>
    <dependency><groupId>Jakarta.ws.rs</groupId><artifactId>Jakarta.ws.rs-api</artifactId></dependency>
    <dependency><groupId>Jakarta.websocket</groupId><artifactId>Jakarta.websocket-api</artifactId></dependency>
    <dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-annotations</artifactId></dependency>
    <dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-jaxrs2-Jakarta</artifactId></dependency>
</dependencies>

The BOM scopes sdk, sdk-model, sdk-Service, sdk-spi-Guice, and sdk-Jakarta-rs as provided — the Elements runtime already has these on its classpath, so they’re compiled against but never bundled. Guice, the Jakarta RS/WebSocket APIs, and the Swagger artifacts are also provided by the runtime. This Element only uses the Guice SPI (sdk-spi-Guice) — there’s no non-Guice sdk-spi variant in use here.

Note

Only artifacts that are not provided get bundled into the .elm‘s lib/ directory. Always double-check the BOM’s scoping if you see duplicate-class errors at runtime — it usually means something that should be provided got bundled twice.


The .elm Packaging Pipeline #

Unlike older Elements projects that used the Maven Assembly Plugin to zip a libs/ + classpath/ directory pair, this project builds its .elm archive with the Maven Antrun Plugin and Maven Dependency Plugin, no assembly descriptor required. Two properties define the staging layout:

<elm.staging.dir>${project.build.directory}/${project.groupId}.${project.artifactId}-${project.version}</elm.staging.dir>
<elm.Element.dir>${elm.staging.dir}/${project.groupId}.${project.artifactId}</elm.Element.dir>

i.e. target/com.example.Element.Element-1.0-SNAPSHOT/com.example.Element.Element/. Five executions build up that directory and then zip it, in this order:

  1. elm-copy-api-deps (maven-dependency-plugin, phase prepare-package) — copies this project’s own ${api.classifier}-classified jar (and any transitive ones sharing the same groupId) into <elm.Element.dir>/api.
  2. elm-copy-lib-deps (maven-dependency-plugin, phase prepare-package) — copies every non-provided-scope dependency into <elm.Element.dir>/lib, prepending the group id to each filename.
  3. elm-stage-classpath (maven-antrun-plugin, phase prepare-package) — copies compiled classes and src/main/resources into <elm.Element.dir>/classpath.
  4. elm-stage-static-content (maven-antrun-plugin, phase prepare-package) — copies src/main/static into <elm.Element.dir>/static and src/main/ui into <elm.Element.dir>/ui.
  5. elm-write-manifest (maven-antrun-plugin, phase prepare-package) — resolves git rev-parse HEAD (falling back to unknown) and writes dev.getelements.Element.manifest.properties at the Element root with Element-Version, Element-Build-Time, Element-Revision, and Element-Builtin-Spis.
  6. elm-create-archive (maven-antrun-plugin, phase package) — ensures api/, lib/, and classpath/ exist even if empty, then zips the whole staging directory to <elm.staging.dir>.elm: <zip destfile="${elm.staging.dir}.elm" basedir="${elm.staging.dir}"/>.

Finally, build-helper-maven-plugin‘s attach-artifact goal (phase package) attaches that .elm file to the Maven project as artifact type elm:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>attach-elm</id>
            <phase>package</phase>
            <goals><goal>attach-artifact</goal></goals>
            <configuration>
                <artifacts>
                    <artifact>
                        <file>${elm.staging.dir}.elm</file>
                        <type>elm</type>
                    </artifact>
                </artifacts>
            </configuration>
        </execution>
    </executions>
</plugin>

This is what makes mvn install publish com.example.Element:Element:1.0-SNAPSHOT:elm into your local repository alongside the normal jar — exactly the coordinate run.java references with .elmArtifact("com.example.Element:Element:elm:1.0-SNAPSHOT"), and the coordinate you’d reference from a deployment configuration.

The resulting .elm archive layout:

com.example.Element.Element/
  api/            <- classified API jar(s)
  lib/            <- bundled runtime jars (non-provided scope)
  classpath/      <- compiled classes + src/main/resources
  static/         <- src/main/static
  ui/             <- src/main/ui (superuser/User plugin bundles)
  dev.getelements.Element.manifest.properties

The debug Module #

debug/pom.xml intentionally depends on nothing but the SDK’s local runtime and logging:

<dependencies>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-local</artifactId><scope>compile</scope></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-local-maven</artifactId><scope>compile</scope></dependency>
    <dependency><groupId>dev.getelements.elements</groupId><artifactId>sdk-logback</artifactId><scope>compile</scope></dependency>
    <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><scope>compile</scope></dependency>
</dependencies>

The module’s own comment sums up its purpose: sdk-local is a thin wrapper around a fully configured instance of Namazu Elements, and this configuration should almost never need changes. It’s never deployed — it exists purely so you can boot the whole platform, with your Element loaded from source, inside your IDE.


The ui Module #

ui/pom.xml is packaged pom and does no work by default. Its build-ui Profile uses frontend-maven-plugin to install a pinned Node version and run the npm build, for CI/release builds that shouldn’t depend on the machine’s own Node install:

<Profile>
    <id>build-ui</id>
    <build>
        <plugins>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>1.15.0</version>
                <configuration>
                    <nodeVersion>v22.14.0</nodeVersion>
                </configuration>
                <executions>
                    <execution><id>install-node-and-npm</id><goals><goal>install-node-and-npm</goal></goals></execution>
                    <execution><id>npm-install</id><goals><goal>npm</goal></goals></execution>
                    <execution>
                        <id>npm-build</id>
                        <goals><goal>npm</goal></goals>
                        <configuration><arguments>run build</arguments></configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</Profile>

Activate it with mvn install -Pbuild-ui. Day to day, developers run npm directly in ui/ (or let run.java do it) using their own Node install — this Profile exists for build environments that shouldn’t assume Node is already present.


Java Source Deep Dive #

All source lives under com.mystudio.mygame, split across the api and Element modules. Here’s every file, in the order you’d read them to understand how the pieces connect.

1. package-info.java — Declaring the Element #

Element/src/main/java/com/mystudio/mygame/package-info.java:

// Required annotation for an Element. Will recursively search folders
// from this point to include classes in the Element if recursive is true.
// Otherwise, you must include additional package-info.java files in child packages.
@ElementDefinition(recursive = true)
// Enables DI via Guice
@GuiceElementModule(MyGameModule.class)
// Allows injecting DAO layer from Elements Core
@ElementDependency("dev.getelements.elements.sdk.DAO")
// Allows injecting Service layer from Elements Core
@ElementDependency("dev.getelements.elements.sdk.Service")
package com.mystudio.mygame;

import com.mystudio.mygame.Guice.MyGameModule;
import dev.getelements.elements.sdk.annotation.ElementDefinition;
import dev.getelements.elements.sdk.annotation.ElementDependency;
import dev.getelements.elements.sdk.spi.Guice.annotations.GuiceElementModule;
  • @ElementDefinition(recursive = true) is what makes the SDK’s classloading/discovery mechanism recognize com.mystudio.mygame — and every sub-package, because recursive = true — as one Element. Without it, nothing here would be treated as an Element at all. If recursive were false, each sub-package (REST, Service, model, Guice) would need its own package-info.java.
  • @GuiceElementModule(MyGameModule.class) tells the SDK which Guice module to install when it bootstraps this Element’s private injector.
  • The two @ElementDependency annotations declare dependencies on other Elements — the core SDK’s DAO layer and Service layer. This is what lets GreetingServiceImpl (below) @Inject the SDK’s own UserService even though this Element never binds it itself.

2. Guice/MyGameModule.java — the Guice Module #

package com.mystudio.mygame.Guice;

import com.google.inject.PrivateModule;
import com.mystudio.mygame.Service.GreetingService;
import com.mystudio.mygame.Service.GreetingServiceImpl;

public class MyGameModule extends PrivateModule {

    @Override
    protected void configure() {

        bind(GreetingService.class).to(GreetingServiceImpl.class);

        expose(GreetingService.class);
    }
}
  • It extends PrivateModule, not plain AbstractModule. Elements gives each Element its own private Guice environment so internal bindings can’t leak into, or collide with, other Elements’ bindings.
  • bind(GreetingService.class).to(GreetingServiceImpl.class) is a standard interface-to-implementation binding.
  • expose(GreetingService.class) is required precisely because this is a PrivateModule — nothing inside one is visible outside it by default. Without this call, the Service-locator lookup in HelloWithAuthentication (below) would fail.

3. Service/GreetingService.java — the API Interface #

Lives in the api module (api/src/main/java/com/mystudio/mygame/Service/GreetingService.java), not Element, so other Elements can depend on the interface without pulling in the implementation or Guice wiring:

package com.mystudio.mygame.Service;

public interface GreetingService {

    /**
     * Attempts to fetch the current User for the Session header and return an appropriate greeting
     * @return The greeting based on if a logged-in User is found
     */
    String getGreeting();

}

Note that the interface itself carries no @ElementServiceExport annotation — in this project that annotation is applied to the implementation class instead.


4. Service/GreetingServiceImpl.java — the Implementation #

package com.mystudio.mygame.Service;

import dev.getelements.elements.sdk.annotation.ElementServiceExport;
import dev.getelements.elements.sdk.model.User.User;
import dev.getelements.elements.sdk.Service.User.UserService;
import Jakarta.inject.Inject;


@ElementServiceExport(GreetingService.class)
public class GreetingServiceImpl implements GreetingService {

    private UserService userService;

    public UserService getUserService() {
        return userService;
    }

    @Inject
    public void setUserService(final UserService userService) {
        this.userService = userService;
    }

    @Override
    public String getGreeting() {
        // Because we set the dev.getelements.elements.auth.enabled attribute to "true" in the HelloWorldApplication,
        // the UserService will be automatically injected with the current User. This will apply an authentication
        // filter to every request and every Service that is used in this Application.
        final User currentUser = userService.getCurrentUser();
        final boolean isLoggedIn = !User.Level.UNPRIVILEGED.equals(currentUser.getLevel());
        final String name = isLoggedIn ? currentUser.getName() : "Guest";

        return "Hello, " + name + "!";
    }
}
  • @ElementServiceExport(GreetingService.class) tells the SDK to expose this concrete class under the GreetingService Service-locator key — the annotation-driven counterpart to the explicit bind()/expose() calls in MyGameModule. Both point at the same binding.
  • setUserService is Guice setter injection (@Inject on a setter rather than a constructor) — a common pattern for SDK-provided dependencies.
  • UserService comes from dev.getelements.elements.sdk.Service.User — resolvable here only because package-info.java declared @ElementDependency("dev.getelements.elements.sdk.Service").
  • The auth check is: fetch currentUser, then compare its level against User.Level.UNPRIVILEGED to tell a real logged-in User from an anonymous/guest request. This is the only auth-level check in the project, and it distinguishes “logged in vs. not” — it does not check for SUPERUSER.

5. HelloWorldApplication.java — Registering Endpoints #

package com.mystudio.mygame;

import com.mystudio.mygame.REST.ExampleContent;
import com.mystudio.mygame.REST.HelloWithAuthentication;
import com.mystudio.mygame.REST.HelloWorld;
import dev.getelements.elements.sdk.annotation.ElementDefaultAttribute;
import dev.getelements.elements.sdk.annotation.ElementServiceExport;
import dev.getelements.elements.sdk.annotation.ElementServiceImplementation;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import Jakarta.ws.rs.core.Application;

import java.util.Set;

@ElementServiceImplementation
@ElementServiceExport(Application.class)
public class HelloWorldApplication extends Application {

    @ElementDefaultAttribute("true")
    public static final String AUTH_ENABLED = "dev.getelements.elements.auth.enabled";

    @ElementDefaultAttribute("/Element/example/REST/api")
    public static final String RS_ROOT = "dev.getelements.elements.Element.rs.root";

    @ElementDefaultAttribute("/Element/example/ws")
    public static final String WS_ROOT = "dev.getelements.elements.Element.ws.root";

    @ElementDefaultAttribute("/app/static/test/path")
    public static final String STATIC_CONTENT_URI = "dev.getelements.Element.static.uri";

    @ElementDefaultAttribute("/app/ui/test/path")
    public static final String UI_CONTENT_URI = "dev.getelements.Element.ui.uri";

    public static final String OPENAPI_TAG = "Example";

    /**
     * Here we register all the classes that we want to be included in the Element.
     */
    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(
                //Endpoints
                HelloWorld.class,
                HelloWithAuthentication.class,
                ExampleContent.class,

                // Exposes the default security rules for the API. Assumes you are using the builtin Elements auth
                // system by setting `dev.getelements.elements.auth.enabled` to true in the annotation above.
                OpenAPISecurityConfig.class

        );
    }

}
  • @ElementServiceImplementation marks this as a concrete implementation the SDK should instantiate and manage, rather than a plain POJO.
  • @ElementServiceExport(Application.class) is the key wiring annotation — it exposes this class under the JAX-RS Application Service-locator key, which is how the Elements HTTP runtime discovers which Application subclass to mount for this Element, and at what root path.
  • Each @ElementDefaultAttribute field declares a default value for a named configuration attribute — the string constant is the attribute key, the annotation value is its default. AUTH_ENABLED defaulting to "true" is what turns on the built-in Elements auth filter for every request and Service in this Element, which is exactly what makes UserService.getCurrentUser() populated in GreetingServiceImpl. RS_ROOT and WS_ROOT set the REST and WebSocket mount points; STATIC_CONTENT_URI and UI_CONTENT_URI are example placeholder paths, not wired to real content.
  • getClasses() is the actual JAX-RS registration point. Note that OpenApiResource is imported but never added to the returned set in this project’s current form — only HelloWorld, HelloWithAuthentication, ExampleContent, and OpenAPISecurityConfig are registered.

6. OpenAPISecurityConfig.java — Documenting the Auth Scheme #

package com.mystudio.mygame;

import dev.getelements.elements.sdk.model.Headers;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.security.SecuritySchemes;

import static dev.getelements.elements.sdk.Jakarta.rs.AuthSchemes.SESSION_SECRET;
import static io.swagger.v3.oas.annotations.enums.SecuritySchemeIn.HEADER;
import static io.swagger.v3.oas.annotations.enums.SecuritySchemeType.APIKEY;

@OpenAPIDefinition(
        info = @Info(
                title = "Example Element",
                description = "An example Element.",
                contact = @Contact(
                        url = "https://namazustudios.com",
                        email = "info@namazustudios.com",
                        name = "Namazu Studios"
                )
        ),
        externalDocs = @ExternalDocumentation(
                url = "https://namazustudios.com/docs",
                description = "Please see the Namazu Elements Manual for more information."
        ),
        security = {
                @SecurityRequirement(name = SESSION_SECRET)
        }
)
@SecuritySchemes({
        @SecurityScheme(
                type = APIKEY,
                in = HEADER,
                name = SESSION_SECRET,
                paramName = SESSION_SECRET,
                description = "Session secret required for authenticated endpoints")
})
public class OpenAPISecurityConfig {}

This class has no fields or methods — it exists solely to carry class-level OpenAPI annotations, and is registered in getClasses() purely so Swagger’s scanner picks them up when generating the API spec. It defines the Elements-SessionSecret header as an API-key-style security scheme and sets it as the document-wide default. It plays no role at request-processing time — it’s documentation Metadata only, not enforcement.


7. REST/HelloWorld.java — an Open Probe Endpoint #

package com.mystudio.mygame.REST;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import Jakarta.ws.rs.Consumes;
import Jakarta.ws.rs.GET;
import Jakarta.ws.rs.Path;
import Jakarta.ws.rs.Produces;
import Jakarta.ws.rs.core.MediaType;

import static com.mystudio.mygame.HelloWorldApplication.OPENAPI_TAG;

@Tag(name = OPENAPI_TAG)
@Path("/helloworld")
public class HelloWorld {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Operation(summary = "Hello world probe", description = "Returns a simple greeting")
    public String sayHello() {

        return "Hello world!";

    }

}

The simplest possible JAX-RS resource — a plain GET returning static text, useful as a health-probe. It declares no @SecurityRequirement, so it’s reachable even though this Element enables auth globally: authorization here works by services resolving the current User (or not) rather than a filter rejecting unauthenticated requests outright.


8. REST/HelloWithAuthentication.java — the Service Locator Pattern #

package com.mystudio.mygame.REST;

import com.mystudio.mygame.Service.GreetingService;
import dev.getelements.elements.sdk.Element;
import dev.getelements.elements.sdk.ElementSupplier;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import Jakarta.ws.rs.Consumes;
import Jakarta.ws.rs.GET;
import Jakarta.ws.rs.Path;
import Jakarta.ws.rs.Produces;
import Jakarta.ws.rs.core.MediaType;

import static com.mystudio.mygame.HelloWorldApplication.OPENAPI_TAG;
import static dev.getelements.elements.sdk.Jakarta.rs.AuthSchemes.SESSION_SECRET;


@Tag(name = OPENAPI_TAG)
@Path("/hellowithauthentication")
public class HelloWithAuthentication {

    private final Element Element = ElementSupplier
            .getElementLocal(HelloWithAuthentication.class)
            .get();

    private final GreetingService greetingService = Element
            .getServiceLocator()
            .getInstance(GreetingService.class);


    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.TEXT_PLAIN)
    @Operation(
            summary = "Greeting with login check",
            description = "Checks if the Session token in the header corresponds to at least a User level User.",
            security = { @SecurityRequirement(name = SESSION_SECRET) }
    )
    public String sayHelloWithAuth() {
        return greetingService.getGreeting();
    }

}

This is the class to study for the Service-locator pattern:

  • ElementSupplier.getElementLocal(HelloWithAuthentication.class).get() resolves the Element instance associated with the calling class’s classloader. This is necessary because JAX-RS resources are instantiated by the Jakarta RS container, not by Guice — so they can’t use @Inject directly. This static lookup is how a plain, container-instantiated resource reaches back into its own Element’s private Guice injector.
  • Element.getServiceLocator().getInstance(GreetingService.class) then pulls the singleton out of that injector. This only works because MyGameModule called expose(GreetingService.class) — remove that call and this lookup throws.
  • @SecurityRequirement(name = SESSION_SECRET) on the @Operation is, again, OpenAPI documentation Metadata — it tells Swagger this endpoint expects the header, but the actual “logged in or guest” branching happens inside GreetingServiceImpl, not in a JAX-RS filter defined here.

9. REST/ExampleContent.java — POST/PUT and Path Params #

package com.mystudio.mygame.REST;

import com.mystudio.mygame.model.ExamplePostRequest;
import com.mystudio.mygame.model.ExamplePostResponse;
import com.mystudio.mygame.model.ExamplePutRequest;
import com.mystudio.mygame.model.ExamplePutResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import Jakarta.ws.rs.*;
import Jakarta.ws.rs.core.MediaType;

import java.util.Map;

import static com.mystudio.mygame.HelloWorldApplication.OPENAPI_TAG;

@Tag(name = OPENAPI_TAG)
@Path("/examplecontent")
public class ExampleContent {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Example POST request", description = "Example produces/consumes for POST")
    public ExamplePostResponse examplePost(ExamplePostRequest examplePostRequest) {

        //Normally we'd create a new object in the database with a POST request, but for demonstration
        //purposes, we'll just return an example response object
        final var response = new ExamplePostResponse();

        response.setName(examplePostRequest.getName());

        return response;
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Example PUT request", description = "Example produces/consumes for PUT")
    public ExamplePutResponse examplePost(ExamplePutRequest examplePutRequest) {

        //Normally we'd overwrite an existing object in the database with a PUT request, but for demonstration
        //purposes, we'll just return an example response object
        final var response = new ExamplePutResponse();

        response.setName(examplePutRequest.getName());

        return response;
    }

    @PUT
    @Path("{name}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Example PUT request with a path param", description = "Example produces/consumes for PUT with a path param")
    public ExamplePutResponse examplePutWithPathParam(@PathParam("name") String name, ExamplePutRequest examplePutRequest) {

        //Normally we'd overwrite an existing object in the database with a "name" property that matches the "name" path
        // param with this PUT request, but for demonstration purposes, we'll just return an example response object
        final var response = new ExamplePutResponse();

        response.setName(examplePutRequest.getName());
        response.setMetadata(Map.of("name", name));

        return response;
    }
}

This resource never touches a database — its own comments say so explicitly. It exists to demonstrate JSON request/response bodies, a validated request DTO, and a @PathParam. One of the four request/response DTOs, ExamplePutResponse (in model/), shows the shape all of them share:

package com.mystudio.mygame.model;

import dev.getelements.elements.sdk.model.Constants;
import io.swagger.v3.oas.annotations.media.Schema;
import Jakarta.validation.constraints.NotNull;
import Jakarta.validation.constraints.Pattern;

import java.util.Map;

@Schema
public class ExamplePutResponse {

    @NotNull
    @Pattern(regexp = Constants.Regexp.NO_WHITE_SPACE)
    @Schema(description = "A unique name for the object that we're creating. No spaces allowed.")
    private String name;

    @Schema(description = "The type of request being made. For example/debugging purposes.")
    private String requestType = "ExamplePutResponse";

    @Schema(description = "Any additional information to return.")
    private Map<String, Object> Metadata;

    // getters/setters omitted for brevity
}

@Schema annotations document the field for OpenAPI generation, and @NotNull/@Pattern (using the SDK’s own Constants.Regexp.NO_WHITE_SPACE) are standard Jakarta Bean Validation constraints, enforced automatically by the JAX-RS runtime before the resource method body even runs.


What’s Not Demonstrated in This Repo #

To avoid sending you looking for code that isn’t there, note explicitly what this example does not include, even though these are all valid Elements SDK capabilities described elsewhere in the manual:

  • No WebSocket endpoint (no @ServerEndpoint class), despite HelloWorldApplication.WS_ROOT declaring a base path for one. See WebSockets for the general pattern.
  • No Morphia @Entity/DAO classes — nothing in this project persists to MongoDB directly.
  • No User.Level.SUPERUSER check — the only level check is UNPRIVILEGED vs. logged-in.
  • No use of ElementRegistry for cross-Element Service discovery — GreetingService is only ever resolved locally.

The Debug Runner: run.java #

import dev.getelements.elements.sdk.local.ElementsLocalBuilder;

import java.io.File;
import java.io.IOException;

/**
 * Runs your local Element in the SDK.
 *
 * Working directory must be the project root (Element-example/).
 * IntelliJ: Run → Edit Configurations → Working directory → set to this project root.
 */
public class run {
    public static void main(final String[] args ) throws IOException, InterruptedException {

        // Install npm dependencies on first run, then build both segment bundles.
        // The bundles are written directly to Element/src/main/ui/{superuser,User}/
        // so that the Maven build triggered by local.start() picks them up.
        final var uiDir = new File("ui");

        if (!new File(uiDir, "node_modules").exists()) {
            new ProcessBuilder("npm", "install")
                    .directory(uiDir)
                    .inheritIO()
                    .start()
                    .waitFor();
        }

        new ProcessBuilder("npm", "run", "build")
                .directory(uiDir)
                .inheritIO()
                .start()
                .waitFor();

        new ProcessBuilder("Docker", "compose", "up", "-d")
                .directory(new File("services-dev"))
                .inheritIO()
                .start()
                .waitFor();

        final var local = ElementsLocalBuilder.getDefault()
                .withSourceRoot()
                .withDeployment(builder -> builder
                        .useDefaultRepositories(true)
                        .elementPackage()
                        .elmArtifact("com.example.Element:Element:elm:1.0-SNAPSHOT")
                        .endElementPackage()
                        .build()
                )
                .build();

        local.start();
        local.run();

    }

}

Note it’s a bare, package-less class — the Javadoc comment doubles as its usage instructions. The four steps (npm install/build, Docker compose, then ElementsLocalBuilder) were walked through in the setup section above; the key API points here are .withSourceRoot() (build against the local source tree, not a published artifact) and .elmArtifact("com.example.Element:Element:elm:1.0-SNAPSHOT") (the exact Maven coordinate, including the elm packaging type, produced by the Element module’s attach-artifact step described above).


Dashboard UI Plugin Deep Dive #

Elements can inject custom pages into the Elements admin dashboard by shipping a React component bundle alongside the Java code. The dashboard discovers these at runtime via a plugin.json manifest — no dashboard changes required. The ui/ module is a Vite/TypeScript project that builds these bundles.

Source Layout #

ui/
├── package.json
├── vite.base.config.ts       # shared dev-server / library-build config factory
├── vite.superuser.config.ts  # createConfig('superuser')
├── vite.User.config.ts       # createConfig('User')
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.ts
└── src/
    ├── dev.css                # Tailwind + light/dark tokens, dev shell only
    ├── superuser/
    │   ├── ExamplePlugin.tsx  # the component shown in the dashboard
    │   ├── plugin-entry.ts    # registers the component with window.__elementsPlugins
    │   ├── dev-entry.tsx      # mounts the component for standalone dev (not shipped)
    │   └── index.html         # dev server entry point (not shipped)
    └── User/                  # same four files, simpler component

Build Scripts #

ui/package.json:

{
  "scripts": {
    "dev:superuser": "vite --config vite.superuser.config.ts",
    "dev:User": "vite --config vite.User.config.ts",
    "build": "vite build --config vite.superuser.config.ts && vite build --config vite.User.config.ts"
  }
}

React and its dev tooling are listed only under devDependencies — there’s no runtime dependencies block. That’s deliberate: the built bundle never ships its own copy of React.

The Dual-Mode Vite Config #

vite.base.config.ts exports a createConfig(segment) factory used by both vite.superuser.config.ts and vite.User.config.ts. It branches on Vite’s command:

export function createConfig(segment: string) {
  return defineConfig(({ command }) => {
    if (command === 'serve') {
      // Standalone dev server with HMR: npm run dev:superuser / dev:User
      // API calls are proxied to a running Elements instance (override with ELEMENTS_URL).
      const elementsUrl = process.env.ELEMENTS_URL ?? 'http://localhost:8080'
      return {
        plugins: [react({ jsxRuntime: 'classic' })],
        root: `src/${segment}`,
        server: { proxy: { '/api': elementsUrl, '/app': elementsUrl } },
      }
    }

    // Library/IIFE build: npm run build
    return {
      esbuild: { jsx: 'transform', jsxFactory: 'React.createElement', jsxFragment: 'React.Fragment' },
      build: {
        lib: {
          entry: `src/${segment}/plugin-entry.ts`,
          name: 'ElementPlugin',
          formats: ['iife'],
          fileName: () => 'plugin.bundle.js',
        },
        outDir: `../Element/src/main/ui/${segment}`,
        emptyOutDir: false,
        minify: false,
        rollupOptions: {
          external: ['react'],
          output: { globals: { react: 'window.React' } },
        },
      },
    }
  })
}
  • In serve mode, Vite runs a normal dev server with hot-module-reload against the segment’s own root, proxying /api and /app to a running Elements instance so relative fetches work regardless of the dev server’s port.
  • In build mode, outDir resolves to Element/src/main/ui/{segment} — this is the mechanism that gets the bundle into the right place for the antrun elm-stage-static-content step to pick up later. emptyOutDir: false so the build never deletes the plugin.json file sitting next to it.
  • external: ['react'] plus globals: { react: 'window.React' } rewrites every import React from 'react' into var React = window.React in the compiled IIFE — the bundle never embeds its own React, it shares the host dashboard’s instance.
  • minify: false is intentional — the shipped bundles are left readable.

The Plugin Component #

ui/src/superuser/ExamplePlugin.tsx fetches an unauthenticated platform endpoint and renders the result:

import React from 'react'

interface VersionInfo {
  version: string
  revision: string
  timestamp: string
}

export function ExamplePlugin() {
  const [info, setInfo] = React.useState<VersionInfo | null>(null)
  const [loading, setLoading] = React.useState(false)
  const [error, setError] = React.useState<string | null>(null)

  async function fetchVersion() {
    setLoading(true)
    setError(null)
    try {
      const res = await fetch('/api/REST/version')
      if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
      setInfo(await res.json())
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e))
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="p-6 max-w-2xl">
      <h1 className="text-2xl font-bold mb-2">Example Element</h1>
      <p className="text-muted-foreground mb-6">
        This page is served from the Example Element&rsquo;s superuser UI content directory.
      </p>
      <button onClick={fetchVersion} disabled={loading}>
        {loading ? 'Loading…' : 'Get Platform Version'}
      </button>
      {error && <div>{error}</div>}
      {info && (
        <div>
          <div>Version: {info.version}</div>
          <div>Revision: {info.revision}</div>
          <div>Built: {info.timestamp}</div>
        </div>
      )}
    </div>
  )
}

The User segment’s version is simpler — a static informational panel with no fetch call at all. Both are wired to the dashboard’s plugin registry the same way, in plugin-entry.ts:

import { ExamplePlugin } from './ExamplePlugin'

declare const window: Window & {
  __elementsPlugins?: {
    register(route: string, component: unknown): void
  }
}

window.__elementsPlugins?.register('example-Element', ExamplePlugin)

Note

If your Element’s REST endpoint requires authentication, the platform’s convention is to send window.__elementsApiClient.getSessionToken() as an Elements-SessionSecret header on the fetch call (cookies alone aren’t reliable in every dashboard context). This example’s own fetch call only hits the unauthenticated /api/REST/version platform endpoint, so it doesn’t exercise that pattern — if you want to call /Element/example/REST/api/hellowithauthentication from a plugin, you’ll need to add that header yourself, following the same shape shown for server-side auth elsewhere in this guide.

The plugin.json Manifest #

After npm run build, the compiled bundle lands next to a manifest at Element/src/main/ui/superuser/plugin.json (and the equivalent under User/):

{
  "schema": "1",
  "entries": [
    {
      "label": "Example Element",
      "icon": "Package",
      "bundlePath": "plugin.bundle.js",
      "route": "example-Element"
    }
  ]
}

label is the sidebar text, icon is any Lucide icon name, bundlePath is relative to the manifest, and route is the unique key used both in the dashboard URL (/plugin/{route}) and in the .register(route, ...) call above — the two must match. Both the manifest and the built plugin.bundle.js get staged into the .elm‘s ui/ tree by the elm-stage-static-content antrun execution described earlier, and served at /app/ui/{Element-prefix}/{segment}/.


Static & UI Content Serving #

Two source directories are copied verbatim into the .elm archive by the Maven build, with no extra configuration required:

  • Element/src/main/static/ — served at /app/static/{prefix}/
  • Element/src/main/ui/ — served at /app/ui/{prefix}/ (this is where the dashboard plugin bundles live)

Serving behavior for both trees — index file, custom routing rules, response headers, error pages — is controlled by the StaticRuleEngine reading attributes from the Element’s configuration:

  • dev.getelements.static.index / dev.getelements.ui.index — file served at the tree’s context root (default index.html)
  • dev.getelements.static.rule.<name>.regex / dev.getelements.ui.rule.<name>.regex — a regex rule matching file paths
  • dev.getelements.static.rule.<name>.header.<Header>.value / the equivalent ui key — a response header template for matched files (supports $filename, $path, $[0], $[N])
  • dev.getelements.static.error.<code> / the equivalent ui key — file served for a given HTTP error code
  • dev.getelements.Element.static.uri / dev.getelements.Element.ui.uri — override the full serve URI, default /app/static/{prefix} / /app/ui/{prefix}

These attributes are normally embedded in the deployed .elm at dev.getelements.Element.attributes.properties (Element root, same level as api/, lib/, classpath/). This example project does not currently ship that file — if you add one, place it at Element/src/main/elm/dev.getelements.Element.attributes.properties and add an antrun copy step to Element/pom.xml alongside the existing elm-stage-* executions to stage it into ${elm.Element.dir}.


Next Steps #

  • Custom Code Overview — the broader picture of how Elements load and isolate custom code
  • Introduction to Guice and Jakarta in Elements — background on the two frameworks used throughout this example
  • Structuring Your Element — a shorter, pattern-focused companion to this guide
  • Packaging an Element with Maven — more detail on the .elm packaging mechanism

What are your Feelings
Still stuck? How can we help?

How can we help?

Updated on August 14, 2026
Direct MongoDB Access (3.5+)Building the Kotlin Example Element: A Complete Walkthrough
Table of Contents
  • What You'll Build
  • Prerequisites
  • Step by Step: Setup to Running
    • 1. Clone the Repository
    • 2. Build Everything
    • 3. Start MongoDB
    • 4. Run the Element Locally
    • 5. Verify It's Working
  • Maven Structure Deep Dive
    • Root pom.xml
    • The api Module: a Classified Jar
    • The element Module: Dependencies
    • The .elm Packaging Pipeline
    • The debug Module
    • The ui Module
  • Java Source Deep Dive
    • 1. package-info.java — Declaring the Element
    • 2. guice/MyGameModule.java — the Guice Module
    • 3. service/GreetingService.java — the API Interface
    • 4. service/GreetingServiceImpl.java — the Implementation
    • 5. HelloWorldApplication.java — Registering Endpoints
    • 6. OpenAPISecurityConfig.java — Documenting the Auth Scheme
    • 7. rest/HelloWorld.java — an Open Probe Endpoint
    • 8. rest/HelloWithAuthentication.java — the Service Locator Pattern
    • 9. rest/ExampleContent.java — POST/PUT and Path Params
    • What's Not Demonstrated in This Repo
  • The Debug Runner: run.java
  • Dashboard UI Plugin Deep Dive
    • Source Layout
    • Build Scripts
    • The Dual-Mode Vite Config
    • The Plugin Component
  • Example Element
    • The plugin.json Manifest
  • Static & UI Content Serving
    • Next Steps
  • Documentation
  • Terms of Service
  • Privacy Policy
  • Contact us
  • Linkedin
  • Join our Discord

Namazu Studios LLC is powered by Namazu Elements, an open source modular backend framework for connected games.

Namazu Elements
  • Download
  • About Elements
  • Open source
  • Documentation
  • Support
Namazu Studios
  • Case Studies
  • About Us
  • News
Best realtime game backends 2026
Get in Touch
  • info@namazustudios.com
  • Book a call
  • (619) 862-2890
  • Linkedin
  • Discord

©2008-2026 Namazu Studios. All Rights Reserved.