Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
ServerInfo
Exposes a small HTTP JSON API with a Valheim server's world name, connected players (with Steam avatars), and installed mods - built for live status pages that read it directly from the browser.
By Odin_Sons
| Date uploaded | a week ago |
| Version | 2.2.4 |
| Download link | Odin_Sons-ServerInfo-2.2.4.zip |
| Downloads | 19 |
| Dependency string | Odin_Sons-ServerInfo-2.2.4 |
This mod requires the following mods to function
denikson-BepInExPack_Valheim
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2350ValheimModding-JsonDotNET
Shared version 13.0.3 of Json.NET from Newtonsoft, net45 package for use in Valheim mods. Maintained by the ValheimModding team.
Preferred version: 13.0.4README
Server Info
A BepInEx plugin for Valheim dedicated servers. Exposes a small HTTP API
with the world name, connected players, and installed mods (enriched with
each mod's manifest.json when it has one) — used by the project's
status page to show live server state.
Installing
Copy ServerInfo.dll into the server's BepInEx/plugins/ folder and
restart the server process — that's it. BepInEx generates
BepInEx/config/Odin_Sons.ServerInfo.cfg on first run with every setting
below at its default, so there's nothing else to set up before it works.
Network setup
The plugin runs its own HTTP server; it doesn't need nginx or any other reverse proxy in front of it. Two things to check on a fresh server:
- Firewall / port forwarding. Whatever port you configure (
8880by default) needs to be reachable from wherever your status page or browser calls it from — open it in your firewall, and forward it on your router if the server is behind NAT. - Windows: URL reservation.
HttpListenerneeds an admin-granted reservation before a non-admin process can bind a port for all hosts. This only controls which local Windows accounts may bind that port — it has no effect on network access, which is what the firewall step above is for. If the log shows an access error on startup, find the account actually running the server (whoami) and run once as administrator (replace8880andYOUR_USERNAME):
If you're not sure which account will run it (e.g. a service manager that may change this),netsh http add urlacl url=http://+:8880/ user=YOUR_USERNAMEuser=Everyonealways works but lets any local account on the machine claim that reservation too — fine on a single-user machine, worth avoiding on a shared one.
Cross-origin requests are controlled by two settings, both under [Server]:
Domain— the public domain or IP this server is reachable at. Empty by default. It isn't used by the plugin for anything except gating the next setting — think of it as "this server is ready to be public" rather than an address the plugin acts on.AllowedOrigin— who's allowed to read the response from a browser. Defaults to*(any site), but is only actually sent onceDomainis set — so a fresh install doesn't advertise a CORS policy for a server that isn't publicly reachable yet.
If your status page reads this endpoint directly from the browser, set
Domain to this server's real address and, if you want to restrict who
can read the response, set AllowedOrigin to the status page's exact
origin (scheme+host+port, e.g. https://status.example.com — the
browser requires an exact match, so a domain that merely points at the
same server doesn't count as the same origin).
Configuration
Set via BepInEx/config/Odin_Sons.ServerInfo.cfg. Numeric settings
declare an acceptable range — BepInEx clamps an out-of-range value in
the file to the nearest bound instead of accepting it as-is, and any
BepInEx config-editor mod (e.g. Configuration Manager) renders them as a
bounded slider rather than free text.
[Server]
| Key | Type | Default | Range | Meaning |
|---|---|---|---|---|
Port |
Int32 | 8880 |
1–65535 | HTTP port for the web server. |
ServerInfoPath |
String | /serverinfo |
— | URL path the server info endpoint is served on. |
Domain |
String | (empty) | — | This server's public domain or IP. Gates AllowedOrigin — see below. |
AllowedOrigin |
String | * |
— | Value sent as Access-Control-Allow-Origin, only once Domain is set. |
RequestTimeoutSeconds |
Int32 | 5 |
1–60 | How long a request waits for the game's main thread before timing out. |
[Cache]
| Key | Type | Default | Range | Meaning |
|---|---|---|---|---|
CacheIntervalSeconds |
Int32 | 5 |
1–3600 | How often the server/player list is recomputed. |
AvatarCacheMinutes |
Int32 | 60 |
1–1440 | How long a fetched avatar URL is reused, and how long an inactive player's entry is kept. |
[Steam]
| Key | Type | Default | Meaning |
|---|---|---|---|
SteamApiKey |
String | (empty) | Optional; without it, the plugin still works — players is returned as usual, just without AvatarUrl. |
[Mods]
| Key | Type | Default | Meaning |
|---|---|---|---|
MetadataSources |
String | Manifest,Assembly |
Comma-separated order of precedence for a mod's description/websiteUrl/dependencies — see below. |
[Logging]
| Key | Type | Default | Meaning |
|---|---|---|---|
LogLevel |
Flags enum | Error, Warning |
Which message levels this plugin writes to the BepInEx log. Combine values in the .cfg (e.g. Error, Warning, Info) to include more detail; Info adds startup/diagnostic messages not needed for normal operation. |
How the avatar cache works
Fetched avatar URLs are kept in memory, keyed by SteamID — not written
to disk, and gone on restart. An entry younger than AvatarCacheMinutes
is reused as-is; once it's older, the next request for that player
fetches a fresh URL from Steam and replaces it. Entries nobody's asked
for in over AvatarCacheMinutes are dropped during the next refresh, so
a player who was online once doesn't sit in memory forever.
There's no live command to clear it early — restarting the server is
the only way, since the cache doesn't survive that anyway. To make it
refresh sooner across the board, lower AvatarCacheMinutes.
Example response
GET /serverinfo
{
"name": "MyValheimServer",
"playersCount": 1,
"players": [
{
"Name": "Ari",
"SteamID": "76561198000000000",
"AvatarUrl": "https://avatars.steamstatic.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_full.jpg"
}
],
"mods": [
{
"name": "Conditional Config Sync",
"guid": "_shudnal.ConditionalConfigSync",
"version": "1.0.4",
"description": "Shared config synchronization and server policy library for Valheim mods.",
"websiteUrl": "https://github.com/shudnal/ConditionalConfigSync",
"dependencies": ["denikson-BepInExPack_Valheim-5.4.2333"],
"namespace": "shudnal",
"packageName": "ConditionalConfigSync"
},
{
"name": "BepInExPack_Valheim",
"guid": "denikson.BepInExPack_Valheim",
"version": "5.4.2333",
"description": "BepInEx pack for Valheim.",
"websiteUrl": "https://github.com/BepInEx/BepInEx",
"dependencies": [],
"namespace": "denikson",
"packageName": "BepInExPack_Valheim"
}
]
}
name/guid/version on a mod come from BepInEx itself, so they're
always present. Where description/websiteUrl/dependencies come
from is controlled by MetadataSources:
Manifest— the mod's ownmanifest.json(Thunderstore/Hexium package):description,website_url,dependencies.Assembly— the mod's own compiled metadata:AssemblyDescriptionfordescription, an embeddedRepositoryUrl(some build setups add this automatically) forwebsiteUrl, and its BepInEx dependency GUIDs fordependencies.
For each field, sources are tried in the configured order and the first
non-empty value wins — so the default Manifest,Assembly prefers the
manifest and only falls back to the assembly for mods that don't have
one. Set it to Manifest to disable the assembly fallback entirely, to
Assembly to ignore manifests altogether, or to Assembly,Manifest to
prefer assembly metadata when both are present. A field is null if
none of the configured sources have a value for it.
namespace/packageName aren't affected by MetadataSources — they're
Thunderstore/Hexium package identity, so they only ever come from a
manifest.json, or null without one.
namespace + packageName build a Thunderstore or Hexium mod page URL
(both use the same shape):
https://thunderstore.io/c/valheim/p/{namespace}/{packageName}/https://valheim.hexium.gg/mods/{namespace}/{packageName}
Credits
Based on PublicWebLink by Maddy.
Contributing
See CONTRIBUTING.md for building from source.
CHANGELOG
Changelog
All notable changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning (with the BepInEx-safe caveat in CONTRIBUTING.md — no prerelease suffixes).
Unreleased
2.2.7 - 2026-09-13
Fixed
- Stopping the server no longer logs a spurious
ObjectDisposedExceptionerror fromHandleLoop—StopServerclosing theHttpListenerwhile a request is pending is expected during shutdown, not a real failure.
2.2.6 - 2026-09-13
Added
- Licensed under GPL-3.0-or-later (see
LICENSE, full official text). GPL-3.0 requires forks/redistributions to keep this project's copyright and license notices intact — those notices point back to https://github.com/odin-sons/serverinfo. Added the matching SPDX header to every source file and aLicensesection to the README. LICENSEis now included in the published package zip.
Changed
manifest.json's andthunderstore.toml'swebsite_urlnow point to the repository instead of being empty.
2.2.5 - 2026-09-13
Added
- VirusTotal scan of the built package before publishing to either
platform, gated on the
VIRUSTOTAL_API_KEYrepository secret. A malicious/suspicious verdict fails the release before anything ships; the scan report link is added to the GitHub Release notes either way. - A VirusTotal badge in the README, linking to the latest release (where that release's own scan report is linked).
2.2.4 - 2026-09-13
Fixed
- Removed the em dash from
manifest.json'sdescription— Thunderstore's manifest validator rejects it (confirmed by hand, undocumented). Uses a plain hyphen now; a test guards against it coming back. CHANGELOG.mdis now actually included in the published package zip.tcli buildonly copiesicon/readmeautomatically; the changelog needed its own[[build.copy]]entry inthunderstore.toml.
2.2.3 - 2026-09-12
Fixed
- Hexium publish uploaded a corrupted package (
"Invalid ZIP file."from the server). PowerShell's$array[$a..$b]range-index isn't guaranteed to stay a[byte[]]— it silently decayed to a genericobject[], whichInvoke-WebRequest -Bodysent as something other than raw bytes. Switched to[Array]::Copyinto a realbyte[].
2.2.2 - 2026-09-12
Fixed
tcli publishin the release pipeline now tolerates Thunderstore's "already exists" response instead of failing the job — a version that publishes there successfully but fails at a later step (e.g. Hexium) can now be safely re-run without the Thunderstore step blocking it.
2.2.1 - 2026-09-12
Added
- Automated Hexium publishing in the release pipeline, alongside
Thunderstore. Reverse engineered from
Kesomannen/gale's open-source
client against Hexium's own (undocumented) submission API:
initiate a multipart upload,
PUTthe package bytes, finish the upload, then submit. No plugin code changes — release tooling only.
2.2.0 - 2026-09-12
Added
- Unit test project (
ServerInfo.Tests) covering the reflection helpers, ID/path parsing, and config parsing, plus packaging validation (manifest.jsonfields,icon.pngdimensions, and version consistency acrossProgram.cs/AssemblyInfo.cs/manifest.json/thunderstore.toml). - CI (
.github/workflows/ci.yml): builds and runs the full test suite on every push and pull request. - Automated release pipeline (
.github/workflows/publish.yml): pushing av*.*.*tag builds, tests, and — only if that passes — publishes to Thunderstore and attaches the package zip to a GitHub Release for a manual Hexium upload (Hexium has no public upload API yet). manifest.json,icon.png,thunderstore.toml, andpackage.ps1for publishing to Thunderstore and Hexium (see CONTRIBUTING.md).
Changed
- Breaking: no longer bundles its own copy of Newtonsoft.Json.
Declares a dependency on the shared
ValheimModding-JsonDotNETlibrary instead, to avoid two copies of the same assembly being loaded at once. Installs through a mod manager pull this in automatically; a manual DLL-only install now needs that library present too. - Packaged files no longer sit under a
plugins/subfolder in the release zip — they ship flat at the package root instead, somanifest.jsonends up next to the installed DLL (the plugin reads its own neighboring manifest.json at runtime;plugins/gets flattened straight intoBepInEx/plugins/on install, separating the two).
[2.1.0] - 2026-09-12
Added
Domainconfig ([Server]section) — this server's public domain or IP. It's purely a readiness flag for the plugin: theAccess-Control-Allow-Originheader is now only sent onceDomainis set, so a fresh install doesn't advertise a CORS policy before the server is actually meant to be reachable from the outside.AllowedOriginitself is unchanged (*by default).
[2.0.0] - 2026-09-12
Changed
- Breaking: renamed the project from PublicWebLink to Server
Info: plugin GUID changed to
Odin_Sons.ServerInfo, display name toServer Info, assembly toServerInfo.dll, namespace toServerInfo. BepInEx treats the new GUID as a different plugin and generates a freshOdin_Sons.ServerInfo.cfg; the oldMaddy.Publicweblink.cfgis not migrated.
[1.6.0] - 2026-09-12
Added
MetadataSourcesconfig ([Mods]section) — a comma-separated order of precedence (Manifest,Assembly) admins can set to control where a mod'sdescription/websiteUrl/dependenciescome from, down to disabling one source entirely or reversing the default priority.
[1.5.0] - 2026-09-12
Added
LogLevelconfig ([Logging]section) — a BepInExLogLevelflags value controlling which message levels this plugin writes to the log, defaulting toError, Warning; addInfoto also get startup/diagnostic messages.- Mods without a
manifest.jsonnow fall back to whatever their own assembly carries fordescription(AssemblyDescription),websiteUrl(an embeddedRepositoryUrl), anddependencies(their BepInEx dependency GUIDs), instead of always returningnull.
Changed
- Documented that the plugin works fully without
SteamApiKeyset —playersis still returned, just withoutAvatarUrl.
Fixed
- The "SteamApiKey is not set" warning is now logged once instead of on every refresh.
[1.4.0] - 2026-09-12
Added
- Numeric settings (
Port,RequestTimeoutSeconds,CacheIntervalSeconds,AvatarCacheMinutes) now declare an acceptable range, so BepInEx clamps an out-of-range value in the.cfgfile instead of accepting it as-is, and config-editor mods render them as a bounded slider.
Changed
- Config settings split into
[Server],[Cache], and[Steam]sections instead of one flat[General]. - Documented in README: how the avatar cache is keyed and expires, and that installing is just "drop the DLL" — BepInEx generates the config file with defaults on first run.
[1.3.1] - 2026-09-12
Changed
- The Windows URL-ACL access-denied message no longer suggests
user=Everyoneas the primary fix — it now points at finding and using the actual account running the server (whoami), withEveryonedocumented as a fallback rather than the default: it lets any local account on the machine claim the same URL reservation, not just the one that needs it.
[1.3.0] - 2026-09-12
Added
ServerInfoPathconfig option — the endpoint path no longer has to be/serverinfo.- CORS support (
AllowedOriginconfig,*by default) so a status page on another domain can call the endpoint directly from the browser. - A startup log line confirming the port and path the server is listening on.
- Startup now detects a Windows URL-ACL access-denied error specifically
and logs the exact
netshcommand to fix it.
Changed
- Errors while handling a request are now logged server-side (with the full exception), not just returned in the response body.
[1.2.0] - 2026-09-12
Added
- Avatar URLs are cached (
AvatarCacheMinutesconfig, default 60) and pruned once entries go stale, instead of being fetched fresh on every refresh for every connected player.
Changed
- Steam avatar lookups for all connected players are batched into a
single
GetPlayerSummariescall instead of one request per player. CacheIntervalSecondsandRequestTimeoutSecondsare now configurable instead of hardcoded.- Code reorganized from a single file into
GameReflection.cs,GameServerInfoProvider.cs,PackageManifestReader.cs,SteamAvatarService.cs, andModels.cs. - Renamed internal
Thunderstore*types/methods to be format-agnostic (PackageManifest,ParseNamespace) — Hexium uses the same namespace/name convention, so nothing here was Thunderstore-specific.
Security
- Steam avatar requests now use HTTPS instead of HTTP.
[1.1.1] - 2026-09-12
Fixed
namespacecoming backnullfor mods whose package folder has no version suffix (some mod managers update packages in place).
[1.1.0] - 2026-09-12
Changed
modsin/serverinfochanged from a plain string array to objects enriched with each mod's ownmanifest.json:description,websiteUrl,dependencies,namespace,packageName.- Build no longer requires a local Valheim or BepInEx install — moved to
NuGet-only dependencies (
BepInEx.Core,UnityEngine.Modules), withZNet/Peeraccess rewritten to use reflection instead of a direct assembly reference.
Removed
- The unused Harmony dependency.