Valheim Mod Manager (vmm)
A command-line tool for managing and automatically downloading Valheim mods and their dependencies.
Features
- Installs mods and their full dependency closure from Thunderstore
- Tracks what is installed in an r2modman-compatible
mods.yml, so an install can be uninstalled exactly, without guessing from directory contents - Enables and disables mods in place, without uninstalling them
- Installs into your game directory by default; named profiles are opt-in
- Launches the game with a profile's mods applied
- Exports and imports profiles as
.r2zfiles or Thunderstore profile codes - Shares one download and extraction cache across every target
- Shows live progress while it works: a spinner for the package index fetch and a byte bar per mod download
Installation
From Source
# Clone the repository
# Install the application
# The binary will be installed in your Cargo bin directory
Configuration
vmm looks for configuration in the following order:
- A
vmm_config.tomlin the current directory (local config) ~/.config/vmm/vmm_config.toml(global XDG config)
If neither exists, a default config is created at the global location on first run.
You can also specify a config file directly with the --config flag, which bypasses the above lookup entirely.
The config file supports the following settings:
log_level: Logging verbosity (error,warn,info,debug,trace)game_dir: Your Valheim game folder, the directory holding the game executable, where the mod loader is installeddata_dir: Optional. Where the package cache, exports, and profiles live. Defaults to~/.config/vmm[launch] store:steam,steam-direct, orother[launch] runtime:nativeorproton[launch] extra_args: Extra arguments passed to the game on every launchmod_list: Deprecated. Superseded bymods.yml. Runvmm migrateto adopt it, then delete the keyinstall_dir: Deprecated. Superseded bygame_dir. If onlyinstall_diris set,vmmrefuses to install and prints migration guidance instead, since the on-disk layout changed along with the key
game_dir and data_dir both expand a leading ~ and environment variables,
so ~/.steam/... and $HOME/.steam/... are equivalent. A variable that is not
set is left as written. vmm refuses to run rather than installing into a
game_dir that does not exist, so a typo is reported instead of silently
creating a fresh mod tree somewhere else.
Example configuration:
= "info"
= "~/.steam/steam/steamapps/common/Valheim"
[]
= "steam"
= "native"
= []
Upgrading from a mod_list config
mods.yml inside your target directory is now the record of what is installed,
replacing mod_list. To adopt an existing list:
This installs every mod in mod_list, writes mods.yml, and removes the
folders of any mods you had dropped from the list under the old installer. Then
delete mod_list from your config.
Usage
Managing mods
# Install mods and their dependencies
# Install an exact version instead of the latest (downgrades if newer is installed)
# Reinstall a mod even if that version is already installed
# List what is installed
# Take a mod out of play without uninstalling it
# Disable or enable every installed mod at once (the mod loader is left alone)
# Remove a mod, its files, and its tracked state
# Remove it even though unrecorded mod folders are present (deletes those too)
# Remove every installed mod, including the mod loader, leaving a vanilla install
# Update every installed mod to its latest version
# Reinstall every installed mod from a clean folder, even if already current
# Refresh the cached package index
# Search Thunderstore
vmm enable --all and vmm disable --all apply to every mod recorded in the
current target's mods.yml; the mod loader is left out of the batch, since it
has no disabled state. A mod already in the requested state is counted
separately as unchanged rather than as a failure. If one mod's toggle fails,
the rest of the batch still applies and vmm exits non-zero, naming what
failed. Like every other command, --all respects --profile and
--no-profile, applying to that target instead of game_dir.
A mod named with a version (Owner-ModName-1.2.3) installs exactly that
version, or fails if Thunderstore does not have it. mods.yml does not
remember the pin, so a later vmm update mods moves it to the latest.
vmm update mods reinstalls a recorded mod only when a newer version is out
or some of its files are missing. A mod already at its latest version with its
files in place is skipped and counted as up to date, so hand edits to its files
survive. Only whether files exist is checked, not their contents, so a plain
update will not repair a file that was edited or corrupted.
vmm update mods --force reinstalls every recorded mod regardless: it removes
each mod's folder under BepInEx/plugins/ and writes the package's files
fresh, discarding hand edits and stray files there. Either way, files you place
directly under BepInEx/config/ are left alone if they already exist, and
mods you have disabled stay disabled.
mods.yml owns the whole mod tree
mods.yml is authoritative over every per-mod folder under the install routes
(BepInEx/plugins/<Owner-ModName>/ and the other namespaced routes), not just
over the mods vmm happens to have installed. An uninstall reconciles that whole
tree against mods.yml, so any <Owner-ModName> folder it does not record can
be removed by an uninstall, including mods you installed by hand, mods
r2modman put there, and mods left by an older vmm whose mod_list was never
migrated. In game_dir mode that tree is your live game directory.
vmm uninstall therefore checks first: if it finds mod folders mods.yml does
not record, it names them and refuses without changing anything. Bring them under
management (vmm install <Owner-ModName>, or vmm migrate for a deprecated
mod_list), or re-run with --force to remove the named mods and accept that
those folders go too. Folders that are not <Owner-ModName>-shaped, such as
loader directories like BepInEx/plugins/MMHOOK and anything directly under
BepInEx/config/, are never touched.
vmm uninstall --all removes every mod recorded for the current target,
including the mod loader, leaving a vanilla install; files under
BepInEx/config are left alone regardless. It asks for confirmation before
removing anything, listing what will go, and --yes skips that prompt. With no
terminal available and no --yes, it refuses rather than assuming an answer.
Untracked mod folders are handled a little differently here than for a
single-mod uninstall: the confirmation prompt lists them too, so accepting it
covers them without needing --force. --yes skips that same prompt, though,
so on that path --force is still required to accept those folders being
removed alongside everything else. As with any other target-scoped command,
--profile and --no-profile choose which target --all operates on.
Profiles
Mods install into game_dir by default. Named profiles keep separate mod sets
outside the game directory, in a layout r2modman can read:
# Select a profile for subsequent commands
Launching
A profile lives outside the game directory, so the loader has to be pointed at
it. In game_dir mode the loader is already installed beside the game and Steam
launches it normally; vmm launch is only needed for profiles.
On Linux with a native (non-Proton) Steam copy, injection goes through a wrapper script that Steam must invoke. Steam only reads launch options from its own config, so pasting them is a one-time manual step:
Paste the printed string into Valheim's Steam launch options.
Sharing
# Write an .r2z into <data_dir>/valheim/exports/
# Upload and print a shareable Thunderstore profile code
# Import from a file, a profile code, or an r2modman profile directory
A file or profile-code import installs each mod at the version the export recorded, not the latest. If Thunderstore no longer has a mod or that version, the import fails and names it, and its config files are not restored.
Importing a directory adopts it as-is at the versions the source recorded, and
downloads nothing, with one exception: if the adopted mods.yml names a mod
loader, the loader is reinstalled from Thunderstore afterward (at its
latest version, not the one the source recorded) so it gains the install
record that makes it manageable and removable; a source naming no loader stays
fully offline. It
is also a raw copy with no pre-clean: if the destination already has different
mods installed, mods.yml is overwritten while the previous mods' files stay
on disk, now untracked and orphaned. Import into an empty target, or accept
that leftover.
Global Options
--config <path>
Override the config file location, bypassing the local/global lookup:
--profile <name> / --no-profile
Operate on a named profile, or force game-dir mode, for a single invocation,
overriding any selection made with vmm profile use. The two flags conflict
and cannot be combined:
Downloads and cached data always go to data_dir (or ~/.config/vmm if unset)
regardless of which config file is used. Respects $XDG_CONFIG_HOME when
data_dir is not set.
How It Works
- Resolves the target to operate on:
game_dir, or a selected profile underdata_dir - Downloads the mod manifest from Thunderstore, caching it under
data_dir - Resolves the full dependency closure for the requested mods
- Downloads and extracts each package into the shared package cache, skipping anything already fetched at the required version
- Installs each package into the target using the mod loader's install rules,
and records it in the target's
mods.yml
Directory Structure
data_dir(or~/.config/vmmif unset, respecting$XDG_CONFIG_HOME): holds the downloaded package cache,.r2zexports, and named profiles, underdata_dir/valheim/- Your target directory (
game_dir, or a profile directory underdata_dir/valheim/profiles/<name>/) holds the installed mod files,mods.yml, and any loader state
Troubleshooting
If you encounter issues, increase log verbosity in your config:
= "debug"
Then run the command again to see more detailed output.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Thunderstore for hosting Valheim mods
- The amazing Valheim modding community