Repository Structure
OpenMods reads everything it needs from an openmods.json manifest committed in your repo. One repo can ship a single mod, or split into several mods that each have their own manifest, gallery, and release assets.
folder Where to put your manifest(s)
OpenMods accepts two filename forms: openmods.json and openmods-anything.json. The suffix is just a human label — the mod's actual identity comes from the manifest's slug field. Pick whichever layout fits your repo:
- looks_one
Single mod — one
openmods.jsonat the repo root. - looks_two
Multi-mod, one folder per mod — one
openmods.jsoninside each mod's folder. - looks_3
Multi-mod, all in one folder — one
openmods-label.jsonper mod, sharing the same folder (and a singlemedia/next to them).
OpenMods globs for both filename forms up to 4 directory levels deep and skips common noise paths
(node_modules, .git, bin, obj, dist, build, target, vendor, examples, sample(s), test(s), __tests__, fixtures).
your-repo/
├── openmods.json // Manifest at the root
├── media/ // (Optional) Gallery assets resolved relative to the manifest
├── README.md
└── * // Your actual mod source code
// Multi-mod, one folder per mod
your-repo/
├── Subnautica.SN/
│ ├── openmods.json // slug + supportedGameId + releaseAssets pattern
│ └── media/
├── Subnautica.BZ/
│ ├── openmods.json
│ └── media/
└── README.md
// Multi-mod, all in one folder
your-repo/
├── openmods-CopperFromScanning.json
├── openmods-ExpandedInventory.json
├── openmods-MoreQuickSlots.json
├── media/ // Shared by every manifest in this folder
└── *
Manifest is the source of truth
Every refresh rebuilds your links, FAQ, gallery, thumbnail, and (when declared) supported game from what the file says — dashboard tweaks are overwritten by the next sync, so commit your changes.
Legacy location still works
Older repos using .openmods/openmods.json keep syncing unchanged. New repos can drop the .openmods/ wrapper and put the manifest at the root (or in each mod's folder for multi-mod).
stacks Multiple mods in one repository
Drop an openmods.json inside each mod's folder, or sit several openmods-label.json files next to each other in a single folder. Either way, every manifest in a multi-mod repo must declare a slug (its stable identity within the repo) and a supportedGameId. Use releaseAssets patterns to decide which files from a shared GitHub release belong to each mod.
Example — one GitHub release ships zips for both games, each mod picks its own:
- check_circle The first published mod from a repo can stay as it is. The second time you commit a manifest, OpenMods auto-converts the repo to multi-mod and starts surfacing siblings.
- check_circle Newly-discovered manifests show up in your dashboard as drafts — click Publish to make them live.
- check_circle If you remove a manifest from the repo, the matching mod is auto-archived. Restore it by re-committing the manifest.
- warning Duplicate slugs across manifests in the same repo abort the whole sync — fix the duplicate and the next webhook re-applies cleanly.
data_object openmods.json — full example
Every top-level field is optional. Omit a section and that area of your mod page stays untouched.
JSON Schema available
Add "$schema": "https://openmods.net/manifest.schema.json" to the top of your manifest. VS Code and most IDEs will then validate fields, autocomplete property names, and show inline docs as you type — catches typos like Name vs name before you ever push.
Forgiving parser
JSON comments (//) and trailing commas are accepted, so you can leave notes for collaborators inline.
fingerprint
slug, name, supportedGameId — mod identity
Required for multi-mod repos. Optional for single-mod repos (omit them and the dashboard values you picked at publish time stay in charge).
slug
string · required (multi-mod)Stable per-repo identity. Kebab-case (^[a-z0-9-]+$), max 140 chars. Don't change it after publishing — it's the key OpenMods uses to match the manifest back to the mod row, so renaming breaks the link.
name
string · optionalFirst-publish display name. Used when the manifest auto-creates a draft mod; later dashboard edits to the display name win, so it's safe to tweak the manifest later without overwriting your manual changes.
supportedGameId
number or string · required (multi-mod)The game your mod targets. Two forms — pick whichever is easier:
- "subnautica" — the game's URL slug (visible on the game's OpenMods page).
- 1 — the numeric id (shown in small text next to the game's title).
Each sync replaces the mod's supported game with this value (manifest wins).
filter_alt
releaseAssets & primaryAsset — choose which files
Glob patterns that decide which files from a GitHub release sync into this mod. Useful in multi-mod repos where one release ships zips for several games; each manifest grabs its own.
releaseAssets
array of strings · optionalGlob patterns (* = any chars, ? = single char). An asset is included if its filename matches any pattern. Omit or leave empty to include every asset (the default for single-mod repos).
primaryAsset
string · optionalGlob picking the file the Download button points at. Falls back to the first matched asset. Only sets the link when declared — leaving this off preserves anything you set manually in the dashboard.
Releases with zero matches are skipped
If a GitHub release has no asset matching releaseAssets, the release doesn't appear on this mod's page — and any previously-synced release row is pruned. Tighten the pattern intentionally.
description
readme — custom README per mod
Point at a markdown file to use as this mod's page body. Useful for multi-mod repos where each mod deserves its own write-up instead of sharing the repo's root README.md.
readme
string · optional
Path to a markdown file inside the repo. Resolved relative to the manifest's directory by default — so "readme": "README.md" next to a manifest at DroneInventory/openmods.json reads DroneInventory/README.md. A leading slash makes it root-relative: "readme": "/docs/drone.md" reads from the repo root regardless of where the manifest lives.
Falls back to the repo README
Omit the field, or point at a missing file, and OpenMods uses GitHub's standard README detection (root README.md or one of its accepted variants). The previous body stays in place if the custom path 404s — your mod page never goes blank from a typo.
link
links — sidebar links
A list of external links rendered as a styled sidebar section.
label
string · requiredVisible button text.
url
string · requiredDestination URL.
icon
string · optionalA Material Symbols name (e.g. chat, language) or an emoji. When omitted, OpenMods picks a sensible default for common labels like Discord / Website / Support / GitHub.
image
media & thumbnail — gallery
media is the gallery in display order. thumbnail is your mod's hero image.
url
string · requiredA filename in the media/ folder next to your manifest (e.g. cover.png) or a full https:// URL (e.g. a YouTube link). Filenames are resolved against the media folder by basename.
type
"image" | "video" · optionalInferred from the URL extension when omitted. Use video for YouTube/Vimeo embeds.
label
string · optionalCaption / alt text. Parsed but not currently rendered — use it as an organisational note.
Supported images
.png · .jpg · .jpeg · .webp · .gif
Supported videos
.mp4 · .webm
Unresolved files are skipped
If a filename isn't found in the manifest's media/ folder, the entry is dropped from the gallery and a warning is logged. Check spelling and the file extension.
quiz
faq — questions & answers
A list of { "question", "answer" } pairs. Renders as an interactive accordion on your mod page.
extension
add-on-of — declare your mod as an add-on
Numeric mod id of the parent mod. Your mod will appear in that mod's Add-ons section, and its sidebar will show a chip pointing back at the parent.
Where to find a mod's id
Open the mod's page on OpenMods — the id is the # 42 chip in the header, next to the developer name → "add-on-of": 42.
- check_circle Omit the field → whatever you set in the dashboard stays. Use it for a soft migration.
- check_circle Set to a positive number → mod becomes an add-on of that parent.
- check_circle
Set to
null→ clears any existing parent (the mod becomes standalone). - warning Unknown ids or self-references are skipped with a warning — the rest of the manifest still applies.
account_tree
dependencies — per-release requirements
An object keyed by your mod's release tag. Each value is the list of required mods for that release, replacing whatever was previously recorded. Releases not listed are left untouched.
modId
number · requiredNumeric mod id of the required mod (same shape as add-on-of — shown in the header of the mod's page, next to the developer).
release
string · optionalExact-version pin (shortcut for fromRelease == toRelease). Overrides the from/to fields when set.
fromRelease / toRelease
string · optionalInclusive lower/upper bound on acceptable releases of the required mod. Omit one for an open bound. Omit both for "latest".
Resolves to the same constraint labels you see on the mod page:
- check_circle No bounds → "latest"
- check_circle
release: "3.0.1"→ exact pin: "3.0.1" - check_circle
fromRelease: "3.0.0"→ "3.0.0+" - check_circle
toRelease: "3.5.0"→ "≤ 3.5.0" - check_circle
fromRelease+toRelease→ "3.0.0 → 3.5.0"
Inheritance still applies
A release without explicit requirements inherits from the next older release that has them — same behavior as the dashboard. Use "1.0.0": [] to explicitly clear that release's dependencies.
Strict version matching
Release tags must match exactly (case-insensitive) what GitHub shows. Unknown release keys, missing modIds, and unresolved release/fromRelease/toRelease values are skipped with a warning.
deployed_code
install — OpenMods Manager rules
Tells the OpenMods Manager desktop app how to lay your release archive into the player's game folder. Without it, the Manager extracts the archive into the game root.
path
string · optionalParent directory for the mod's files, relative to the game install root. The Manager creates a subfolder named after the mod (or after the archive's single top-level folder, if it has one) so files from different mods sharing the same parent (e.g. BepInEx/plugins) don't collide.
custom-path
object · optionalPer-file / per-folder mapping. Keys are paths inside the archive; values are destinations relative to the game root. Trailing slashes mark folder copies.
- check_circle
No
installblock → archive contents drop into the game root, skipping any bundled.openmods/folder. - check_circle
Only
path→ archive contents land under<path>/<mod name>/so each mod gets its own subfolder. - check_circle
Only
custom-path→ mapped files/folders go to their exact destinations; the rest drop at the game root. - check_circle
Both →
custom-pathuses exact destinations for entries it covers; the rest fall under<path>/<mod name>/.
Ship openmods.json in your release
Include the openmods.json inside your release archive so the Manager can read the install rules without re-fetching the repo.
Ready to distribute your mods?
Join hundreds of other developers using OpenMods to deliver their creations to players faster.