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.json at the repo root.
  • looks_two Multi-mod, one folder per mod — one openmods.json inside each mod's folder.
  • looks_3 Multi-mod, all in one folder — one openmods-label.json per mod, sharing the same folder (and a single media/ 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).

// Single mod
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
└── *
bolt

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.

history_edu

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:

{ // Subnautica.SN/openmods.json "schemaVersion": 2, "slug": "subnautica-sn-helper", "name": "SN Helper", "supportedGameId": 1, "releaseAssets": ["Subnautica.SN-*.zip"], "primaryAsset": "Subnautica.SN-*.zip" }
  • 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.

verified

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.

{ "$schema": "https://openmods.net/manifest.schema.json", "schemaVersion": 2, // Required when this repo declares multiple openmods.json files. "slug": "my-mod", "name": "My Mod", "supportedGameId": "subnautica", // or 1 (numeric id) // Optional. Glob patterns filter which release assets belong to this mod. "releaseAssets": ["MyMod-*.zip"], "primaryAsset": "MyMod-*.zip", // Optional. Custom README for this mod's page body. Falls back to the repo README. "readme": "README.md", "thumbnail": "cover.png", "add-on-of": 42, "links": [ { "label": "Discord", "icon": "chat", "url": "https://discord.gg/abc" }, { "label": "Website", "url": "https://example.com" } ], "media": [ { "type": "image", "url": "screenshot-1.png", "label": "Boss fight" }, { "type": "image", "url": "screenshot-2.jpg" }, { "type": "video", "url": "https://youtube.com/watch?v=xyz" } ], "faq": [ { "question": "How do I install?", "answer": "Use the OpenMods Manager or extract the zip into your game folder." }, { "question": "Is it multiplayer-safe?", "answer": "Yes — fully compatible." } ], "dependencies": { "1.0.0": [ { "modId": 88, "release": "3.2.1" } ], "2.0.0": [ { "modId": 88, "fromRelease": "4.0.0", "toRelease": "4.5.0" }, { "modId": 142 } ] }, "install": { "path": "BepInEx/plugins", "custom-path": { "src/plugin.dll": "BepInEx/plugins/", "config/": "BepInEx/config/" } } }
edit_note

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 · optional

First-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 · optional

Glob 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 · optional

Glob 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.

filter_list_off

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.

history

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 · required

Visible button text.

url string · required

Destination URL.

icon string · optional

A 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 · required

A 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" · optional

Inferred from the URL extension when omitted. Use video for YouTube/Vimeo embeds.

label string · optional

Caption / alt text. Parsed but not currently rendered — use it as an organisational note.

Supported images

.png · .jpg · .jpeg · .webp · .gif

Supported videos

.mp4 · .webm

warning

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.

tag

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 · required

Numeric 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 · optional

Exact-version pin (shortcut for fromRelease == toRelease). Overrides the from/to fields when set.

fromRelease / toRelease string · optional

Inclusive 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"
history

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.

warning

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 · optional

Parent 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 · optional

Per-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 install block → 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-path uses exact destinations for entries it covers; the rest fall under <path>/<mod name>/.
info

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.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.