Build-Time Token
Inject your FastStats token at build time so it stays out of your code and forks don't send data to your project
The setup examples pass "YOUR_TOKEN" directly to the context factory. That is
fine for trying things out, but once your project lives in a public repository,
a hardcoded token gets copied into every fork. Anyone who builds and runs a fork
then sends their data to your FastStats project, mixing it with your own. The
token also stays in the git history even after you remove it.
Instead, leave the token out of your code and let your build write it into a resource file. Gradle reads it from an environment variable (a CI secret) or a local Gradle property, and the SDK picks it up when the context is created. Forks don’t have your secret, so their builds don’t send data to your project.
0.32.2 or newer.How It Works
When you create a factory without a token, the SDK reads it from the resource
META-INF/faststats/<id>.token in your jar. Surrounding whitespace is ignored.
<id> depends on the platform:
| Platform | <id> | Factory without token |
|---|---|---|
| Bukkit | plugin name from plugin.yml | new BukkitContext.Factory(plugin) |
| BungeeCord | plugin name from plugin.yml | new BungeeContext.Factory(plugin) |
| Hytale | plugin name | new HytaleContext.Factory(plugin) |
| Nukkit | plugin name from plugin.yml | new NukkitContext.Factory(plugin) |
| Velocity | plugin id | don’t call .token(...) |
| Sponge | plugin id | don’t call .token(...) |
| Fabric/Forge/NeoForge | mod id | new FabricContext.Factory(modId) etc. |
| Minestom | minestom | new MinestomContext.Factory() |
The file name includes the id because Fabric, Forge and NeoForge load every mod from one shared class loader, so each mod needs its own file.
If the file is missing, empty or doesn’t contain a valid token, the context is created without any services: FastStats stays disabled, never sends data, and your plugin keeps working normally. With debug logging enabled, a warning names the expected resource path.
Gradle Setup
Add the following to your build script. Replace <id> with the id for your
platform from the table above, for example your plugin name.
// 1. FASTSTATS_TOKEN environment variable (e.g. a CI secret)
// 2. faststats.token Gradle property (-P flag)
// 3. faststats.token file in the project directory (git-ignored)
val faststatsToken = providers.environmentVariable("FASTSTATS_TOKEN")
.orElse(providers.gradleProperty("faststats.token"))
.orElse(providers.fileContents(layout.projectDirectory.file("faststats.token")).asText.map { it.trim() })
.orElse("")
tasks.processResources {
val token = faststatsToken
inputs.property("faststatsToken", token)
doLast {
val file = destinationDir.resolve("META-INF/faststats/<id>.token")
file.parentFile.mkdirs()
file.writeText(token.get())
}
} The file is written to build/, so it is never committed. Declaring the token
as a task input reruns processResources whenever the token changes. The setup
is compatible with the Gradle configuration cache. If you relocate FastStats
with the Shadow plugin, the resource path stays the same.
Gradle skips processResources when src/main/resources is empty, so no
token file is written. Plugins and mods always have a descriptor such as
plugin.yml there. On Minestom, add any resource file if you don’t have one.
Maven Setup
View full Maven example on GitHub
Maven can write the file with resource filtering. Create the token file with a
placeholder and commit it. Replace <id> in the file name with the id for your
platform from the table above:
${faststats.token} Then add the following to your pom.xml:
<properties>
<!-- empty by default, so builds without a token still work -->
<faststats.token></faststats.token>
</properties>
<profiles>
<!-- CI: use the FASTSTATS_TOKEN environment variable when it is set -->
<profile>
<id>faststats-env</id>
<activation>
<property>
<name>env.FASTSTATS_TOKEN</name>
</property>
</activation>
<properties>
<faststats.token>${env.FASTSTATS_TOKEN}</faststats.token>
</properties>
</profile>
</profiles>
<build>
<resources>
<!-- filter only the token file, so ${...} in other resources stays untouched -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>META-INF/faststats/*.token</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>META-INF/faststats/*.token</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- local builds: read faststats.token from a git-ignored faststats.properties file, if present -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>${project.basedir}/faststats.properties</file>
</files>
<quiet>true</quiet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build> If you already filter all of src/main/resources (for example to put
${project.version} into your plugin.yml), you don’t need the two
<resource> blocks. The token file is then filtered with everything else.
Maven takes the token from the first of these that is set:
-Dfaststats.token=your-tokenon the command linefaststats.token=your-tokenin afaststats.propertiesfile next to yourpom.xml. Add that file to your.gitignore.- The
FASTSTATS_TOKENenvironment variable, for example a CI secret
If a property name has a typo, Maven leaves ${faststats.token} in the file
as is. The SDK treats an invalid token in the file like a missing one and
disables FastStats instead of failing on startup.
Using the Token
Create the factory without a token:
import dev.faststats.bukkit.BukkitContext;
import org.bukkit.plugin.java.JavaPlugin;
public final class ExamplePlugin extends JavaPlugin {
// Reads META-INF/faststats/<id>.token, where <id> is the plugin name
private final BukkitContext context = new BukkitContext.Factory(this)
.metrics(factory -> factory.create())
.create();
@Override
public void onEnable() {
context.ready();
}
@Override
public void onDisable() {
context.shutdown();
}
} Local Development
With Gradle, provide the token in one of these ways when building locally (for Maven, see Maven Setup):
# Environment variable
FASTSTATS_TOKEN=your-token ./gradlew build
# Gradle property on the command line
./gradlew build -Pfaststats.token=your-token To avoid passing it on every build, put the token in a faststats.token file
in your project directory and add that file to your .gitignore. Each project
keeps its own file, so this also works when you manage several projects:
your-token faststats.token Never put the token in your project’s own gradle.properties. That file is
committed with the rest of the project. Avoid ~/.gradle/gradle.properties
too: it is shared by all your projects, so every project would get the same
token.
Without a token the build still succeeds and writes an empty token file. The plugin then runs with FastStats disabled.
GitHub Actions
- In your repository, open Settings → Secrets and variables → Actions.
- Click New repository secret, name it
FASTSTATS_TOKENand paste your token as the value. - Pass the secret as an environment variable to the build step of your workflow. This works the same for Gradle and Maven:
- name: Build with Gradle
run: ./gradlew build
env:
FASTSTATS_TOKEN: ${{ secrets.FASTSTATS_TOKEN }} Other CI systems work the same way: store the token as a secret and expose it
to the build as the FASTSTATS_TOKEN environment variable.
GitHub does not pass secrets to workflows triggered by pull requests from forks. Those builds still compile and run, but FastStats is disabled in them, so they don’t send data to your project.
Limitations
This is not meant to hide the token. Its purpose is to keep the token out of your code, so forks don’t send data to your project. The token is still stored in your jar as a plain text file, and anyone who has the jar can extract it. Project tokens can’t be rotated, so treat a token in a published jar as public.