The manifest
Lesson 3.2
Every mod has a file called clight.mod.json in its top folder. It is the mod’s passport: the game reads it before it loads any code, and the Celestial website reads it when you upload the mod.
The template’s manifest
{
"schema": 1,
"id": "my_mod",
"version": "1.0.0",
"name": "My Mod",
"description": "A CLight mod written in C (WebAssembly).",
"authors": [],
"license": "MIT",
"api": 1,
"game": ">=26.3 <26.4",
"side": "both",
"sync": "optional",
"runtime": { "type": "wasm", "entry": "bin/my_mod.wasm" },
"permissions": []
}
It is JSON, not C: keys and strings use double quotes, there are no comments, and there is no comma after the last entry.
The fields
| Field | What it means |
|---|---|
schema |
The manifest format. Always 1. |
id |
The mod’s id. It must match the folder name under assets/ and datapack/, and it can’t change once the mod is published. |
version |
The mod’s version, like 1.0.0. Raise it for every release. |
name |
The name players see. |
description |
One or two sentences about the mod. |
authors |
A list of names: ["Alex", "Sam"]. |
license |
The licence: an SPDX id such as "MIT", "Apache-2.0", or "All-Rights-Reserved". |
api |
The CLight API your mod is written for. Use 1. |
game |
The game versions the mod works with. |
side |
Where the mod runs: "client", "server" or "both". |
sync |
Whether players need the mod to join a server that has it. |
runtime |
"type": "wasm" for C, and the compiled file the game loads. |
permissions |
Extra things the mod asks the player for. Usually empty. |
runtime: where the WASM file is
For a C mod, runtime always looks like this:
"runtime": { "type": "wasm", "entry": "bin/my_mod.wasm" }
entry is the path inside the packed mod. clight build compiles your sources to build/bin/my_mod.wasm and packs that file as bin/my_mod.wasm, so the template’s value is right. Keep it as bin/<your id>.wasm. If you rename your mod, change it here too.
Versions
Versions follow SemVer: three numbers, major.minor.patch.
- Fixed a bug? Raise the last number:
1.0.0to1.0.1. - Added something new? Raise the middle one and reset the last:
1.0.1to1.1.0. - Changed things so old worlds or other mods may break? Raise the first:
1.1.0to2.0.0.
The website won’t accept the same version twice, so a new upload always needs a new number.
“api”: one mod, every build
"api": 1 means “this mod uses CLight API 1”. The API only ever grows: functions are added, never removed or changed. So a mod built for API 1 keeps working on every later Celestial build with API 1, without you rebuilding it.
Your compiled mod also tells the game which API version its headers came from. CLIGHT_MOD_ENTRY does that for you. A mod built with newer headers than the player’s game refuses to load with a clear message, instead of crashing later.
New functions arrive in minor versions (1.1, 1.2, …). The header marks each one with the version it arrived in. If you use a newer function and still want to load on older builds, ask first:
if (cl_core_has(CL_STR("sound"))) {
// the sound module exists in this game
}
cl_core_has knows modules ("gui", "sound"), single functions ("level.set_block"), sides ("side.client") and features ("hot_reload.wasm"). Unknown names give false.
side and sync
side decides where the mod is loaded:
side |
In the game you play | On a dedicated server |
|---|---|---|
"client" |
yes | no |
"server" |
yes (in singleplayer worlds) | yes |
"both" |
yes | yes |
Most mods use "both": a new item has to exist on the server (which owns the world) and on the client (which draws it). A mod that only adds a HUD or a keybind can be "client". Contexts and sides explains the two halves.
sync says whether a player needs the mod to join a server that runs it: "required", "optional", "client_only" or "server_only". The website shows it on the mod’s page.
More fields
You won’t need these at first, but they exist:
| Field | Example | Use |
|---|---|---|
depends |
{ "other_mod": ">=1.2" } |
Mods that must be installed. Without them your mod doesn’t load. |
optional |
{ "other_mod": ">=1.0" } |
Mods you work with if they are there. |
breaks |
{ "old_mod": "*" } |
Mods that can’t run next to yours. |
icon |
"icon.png" |
The icon file, if it isn’t the default icon.png. |
links |
{ "homepage": "https://...", "issues": "https://..." } |
Shown on the mod’s page. |
limits |
{ "memory_mb": 64 } |
The mod’s memory limit in MB. The default is 256. |
custom |
see below | Extra compiler or linker flags. |
Extra flags for clight build go into custom.clight_build:
"custom": {
"clight_build": {
"cflags": ["-Wall", "-Wextra", "-DRUBY_DEBUG=1"],
"ldflags": []
}
}
-Wall -Wextra is a good idea: the compiler warns about many small mistakes before they reach the game.
clight check tells you about mistakes in the manifest before the game or the website does:
> clight check
ok: my_mod 1.0.0
Try it
Your mod ruby_tools should be version 0.2.0, written by you, MIT licensed, needed on both sides, and players should need it to join. Turn on compiler warnings too. Write the manifest.
Show a solution
{
"schema": 1,
"id": "ruby_tools",
"version": "0.2.0",
"name": "Ruby Tools",
"description": "Ruby gear and a ruby counter.",
"authors": ["Alex"],
"license": "MIT",
"api": 1,
"game": ">=26.3 <26.4",
"side": "both",
"sync": "required",
"runtime": { "type": "wasm", "entry": "bin/ruby_tools.wasm" },
"permissions": [],
"custom": { "clight_build": { "cflags": ["-Wall", "-Wextra"] } }
}
Run clight check to make sure it is valid. Note the entry: it uses the new id.
Recap: clight.mod.json names your mod and says where it runs; for C, runtime is wasm with entry bin/<id>.wasm, raise version for every release and keep "api": 1.