To make a Horizon mod, first pick the kind. A content mod (.hmod) adds blocks, items, recipes and textures from data files, and you build it with the cargo horizon-mod tool. A native mod (.hnmod) is Rust code that hooks into the game: a HUD, keys, commands, new behaviour. You write it against Horizon’s hook points, check it with native.py from the registry repository, build a game with it through the launcher, then submit it at playhorizon.dev/mods/submit for review.
Everything below follows the official docs. Where the docs show code, we quote the small pieces that matter; we don’t invent APIs.
Pick the kind
| Content mod | Native mod | |
|---|---|---|
| What it adds | Blocks, items, recipes, loot tables, tags, models, textures, names, structures | HUD elements, keys, commands, settings, new behaviour |
| File | .hmod |
.hnmod |
| Code | None | Rust, compiled into the game |
| Players get it by | The game reading it at start | The launcher compiling a game with it |
| Safety | Can’t run anything | Not sandboxed |
Rule of thumb from the docs: if you add a thing (a block, an item, a structure), make a content mod. If the mod has to do something, make a native mod. They can ship side by side, with the native part holding behaviour and the content part holding the blocks.
Path A: a content mod in an afternoon
Install the tool (it needs Rust from rustup.rs and builds from source in a few minutes), then scaffold a mod:
cargo install --git https://github.com/horizon-rust/horizon cargo-horizon-mod
cargo horizon-mod new my-blocks --template content
The template gives you one working block, Glowing Amethyst: an amethyst block that gives light, with its block state, model, item model, name, loot table, a recipe and the pickaxe tag. Copy and rename its files to add your own.
The pieces you’ll touch:
horizon-mod.toml, the manifest:id,name,version,description,authors,api = "0.2",side = "both",capabilities = ["registries"]. Unknown keys are refused, so typos fail loudly.- Blocks in
data/<id>/horizon/block/<name>.json. Each copies a vanilla template (minecraft:iron_block,minecraft:oak_door…) and overrides properties such as hardness, light (0–15), sounds or map colour. - Items in
data/<id>/horizon/item/<name>.json, also from a template, with optionalmax_stack_sizeandrarity. - Everything else uses vanilla 26.3’s own data pack and resource pack formats: recipes, loot tables, tags, block states, models, textures,
lang/en_us.json.
While you work, run the game with the folder as a dev mod: in the launcher’s Settings, put --dev-mod "/path/to/your-mod" in Game arguments and press Play. When it’s ready, cargo horizon-mod build packs target/horizon-mod/<id>.hmod, and cargo horizon-mod check on that file reads it the way the registry does. One limit to plan around: content mods work in single player today; playing them together on a server is planned.
Path B: a native mod
Tools
- Rust (stable toolchain) and Git.
- Python 3.11 or later and
rsync. On Windows, use WSL for the checks. - A clone of the registry repository:
git clone https://github.com/horizon-rust/horizon-mods. Itsnative/tools/native.pyapplies your bundle to Horizon’s source, checks it, builds it and packages the.hnmod. - A current launcher, prepared at least once. Launcher 0.2.37 or later also builds mods written with the newer typed API.
How it works
A native mod is a bundle for horizon-mixins: a manifest, horizon-mixin.toml, plus a few .rs files. Before the game compiles, horizon-mixins pastes your code into Horizon’s source and calls it from the hook points the manifest names. Horizon exposes more than 350 hook points and 100 helpers; the first eleven cover most beginner mods, among them native_tick, native_frame, native_hud, native_key, native_command and native_mods (your row in Options → Mods). Hook points keep their names and parameters between releases and are only ever added to, so a mod using only them keeps building on later Nightlies.
The worked example: Playtime
The docs build Playtime, which counts how long you’ve played, shows it in the top-right corner, answers /playtime and /playtime reset, and has a settings page with one button. It hooks six points. Its own logic lives in runtime.rs as plain Rust you can unit-test without the game, for example the formatter:
/// `1h 05m` from a number of ticks (20 a second).
pub fn format(ticks: u64) -> String {
let minutes = ticks / 20 / 60;
format!("{}h {:02}m", minutes / 60, minutes % 60)
}
Each hook then gets a small helper file (tick.rs, hud.rs, command.rs…) that starts by checking game.native_on(m::ID), so the mod does nothing while switched off, and calls helpers like native_text, native_fill and native_set_setting. The full listing, including the [[injection]] entries of the manifest, is on the Hook points and helpers page. If you’d rather start from real code, Horizon’s own mods sit in horizon-mods’ native/ folder; the docs call fps-overlay, the source of the FPS Counter mod, the smallest.
Check, build, play
cd horizon-mods
python3 native/tools/native.py check /path/to/playtime
horizon-launcher native-build /path/to/playtime/mixins/playtime --trust-native-code
check downloads Horizon’s source at the commit your compatibility.json names, applies the bundle and runs cargo check. The first run takes several minutes, later ones seconds to a minute. native-build compiles a game for the instance picked in the launcher; the first build downloads Rust and takes 20 to 30 minutes. Tip from the docs: horizon-launcher settings native-profile dev makes rebuilds about six times faster while the game runs up to about twice as slow. Switch back to performance to play.
The typed API
Nightlies since 3 November 2026 also offer a typed API: one crate implementing the Hooks trait, with a typed event per hook (the docs’ example halves damage to pigs). It’s newer, and native.py doesn’t handle it yet, so the docs advise mixin helpers for a mod you want in the registry today.
Test it
cargo testfor your own logic.native.py check … --horizon latestagainst the newest Nightly, every time one comes out.- In game: your row in Options → Mods, the switch off (the mod must do nothing), every command with good and bad arguments, single player and a server, F1 and F3, quit and restart.
- A panic in your code stops the game, so use
OptionandResultas the helpers do.
Content mods: place, mine and craft each thing, read logs/latest.log. For both kinds, finish with cargo horizon-mod check <file>, which runs the registry’s own checks.
Publish it
- Bump
versionin the manifest; published files never change. - Put the source in a public repository under an SPDX licence such as MIT or Apache-2.0. Reviewers read it.
- For native mods,
native.py packagewritesnative/target/<id>-<version>.hnmod. The same files always give the same file, so anyone can rebuild and compare. - Optionally sign with minisign. Once a project has a public key, every later version must be signed with it.
- Submit at playhorizon.dev/mods/submit with a GitHub sign-in. That opens a pull request in horizon-mods; a reviewer reads it, and for a native mod reads every line.
After the merge the file becomes a registry release within minutes and appears in the launcher and in our mod catalog. Players then install it as described in how to install mods, behind a warning that names your mod, because native code isn’t sandboxed. Why that review matters is covered in Is Horizon safe?