RomM logo

RomM

Beautiful, powerful, self-hosted rom manager and player

Alternative to: antstream arcade


About Versions (122)

5.3.0-alpha.1

2026-09-12

[!WARNING] Structure B is gone. We no longer detect the {platform}/roms/ layout, and an instance laid out that way refuses to start rather than scanning an empty library and marking every ROM missing. To keep a Structure B library, declare it, and note that it takes two templates:

filesystem:
  structure:
    default: "{platform}/roms/{game}"
    firmware: "{platform}/bios"

filesystem.roms_folder and filesystem.firmware_folder are gone too. Each named one path segment that a template now contains, and an instance that still sets either refuses to start, printing the equivalent template to paste:

filesystem:
  structure:
    default: "<your roms_folder>/{platform}/{game}"
    firmware: "<your firmware_folder>/{platform}"

See Custom library structure and the filesystem.structure schema.

[!WARNING] Emulator streaming config is now one entry per container, not one per emulator. A container’s host, protocol and label become the defaults for every platform it serves, and a platform block only overrides what differs. Existing streaming setups need their config.yml rewritten before the feature will come back up.

See Emulator Streaming for the new schema.

[!NOTE]

Environment variables

Emulator streaming

variabledefaultdescription
STREAMING_LAUNCH_TIMEOUT600Seconds to wait for a streamed emulator to launch
STREAMING_STATE_HISTORY_LIMIT50Save states kept per ROM, emulator and user

Physical games

variabledefaultdescription
UPC_LOOKUP_ENABLEDtrueBarcode lookups when adding a physical game
UPC_LOOKUP_API_KEY-Key for the UPC lookup service
UPC_LOOKUP_URLhttps://api.upcitemdb.com/prod/trial/lookupUPC lookup endpoint

Recommendations

variabledefaultdescription
ENABLE_SCHEDULED_BUILD_RECOMMENDATIONStrueNightly rebuild of the recommendation index
SCHEDULED_BUILD_RECOMMENDATIONS_CRON30 5 * * *When that rebuild runs

Browser players

variabledefaultdescription
DISABLE_JSDOSfalseDisable the js-dos player for everyone
DISABLE_PICO8falseDisable the PICO-8 player for everyone

Metadata providers

variabledefaultdescription
DEMOZOO_API_ENABLEDfalseEnable the Demozoo metadata provider
POUET_API_ENABLEDfalseEnable the Pouët metadata provider
CSDB_API_ENABLEDfalseEnable the CSDb metadata provider
STEAM_API_ENABLEDfalseEnable the Steam storefront metadata provider
HASHEOUS_API_URLhttps://hasheous.org/api/v1Use a self-hosted Hasheous instance

General

variabledefaultdescription
SCAN_WORKERS4Parallel scan workers, was 1
WEB_SERVER_CONCURRENCY4API server workers, was 1
DB_POOL_RECYCLE_SECONDS300Recycle pooled DB connections after this many seconds

Highlights

Custom library structure

Instead of guessing between two folder structures, describe your exact library layout in config.yml. {platform} is the platform folder, the final {game} is where a game starts, and anything else in {braces} is just a folder you use to organize things. You can override the layout per platform, and give a platform a list of layouts when its games sit at more than one depth. #3571

ROM identity is now content-hashed, so you can move files around and keep their saves, favorites, play history and collections. And if you came from Batocera, RetroBat or ES-DE, their platform folder names are recognized out of the box, so most libraries need no mapping at all. #4345

filesystem:
  structure:
    default: "roms/{platform}/{game}"
    firmware: "bios/{platform}"
    ps3: "roms/{platform}/{category}/{game}"
    snes: "roms/{platform}/{region}/{game}"
    nes:
      - "roms/{platform}/{game}"
      - "roms/{platform}/{category}/{game}"

Docs: Custom library structure, Folder name aliases

Emulator streaming V2

A massive update to emulator streaming. You configure one entry per container instead of one per emulator, a platform can pool several containers and grab whichever one is free, and platforms that do both browser and streamed play now get separate Play and Stream buttons. #4314

Saves and save states made while streaming are pulled back into your library with history and thumbnails. Multi-disc games get a disc-swap control, and whichever disc was mounted when you saved is the one remounted when you load. PS2 and GameCube can sync a whole memory card instead of individual save files, with a memory card library to create, rename, version, share and download them. Admins get a desktop session for setting up an emulator with no game running, plus a fleet view to force-release sessions across every container.

Docs: Emulator Streaming

Physical games

Own a game on a cartridge or disc, but have no ROM for it? You can now add it anyway, by name or by scanning its barcode with your camera, and it gets matched to metadata like any other game. #3848

Docs: Physical Games

Screenshot 2026-09-11 at 6 00 19 PM

Walkthroughs

Walkthroughs are now documents on a game, sitting next to its manuals. Upload your own, or paste a GameFAQs URL and have it fetched directly (as plain text). It’ll even remember where you left off and show a progress bar for each user. #3816

Docs: Walkthroughs

Screenshot 2026-09-11 at 6 03 47 PM

Title IDs read from the binary

Scans now pull a game’s native Title ID straight out of its binary on PSX, PS2, PS3, PSP, PS Vita, Switch, 3DS, Wii, Wii U, GameCube, Dreamcast, Xbox and Xbox 360. It identifies games on the platforms we don’t hash, so a renamed or moved file keeps its saves and collections, and it tells the system where a game writes its saves, so device sync knows what to look for. #3925

Docs: Title ids read from the binary

Jukebox

A soundtrack player for your whole library that follows you as a mini player anywhere in the app. Browse by album, platform, artist or genre, or just put a playlist on: Free Radio picks a randomized, album-balanced hour, and there’s a Decade Mix, recently added, and your personal favorites. #4197

Docs: Jukebox

Screenshot 2026-09-11 at 6 05 57 PM

JS-DOS and PICO-8 in the browser

Windows 3.x and 9x games now run on js-dos and its DOSBox-X backend. Note that js-dos saves stay in your browser and are not synced back to the server. #4061

PICO-8 cartridges (.p8 and .p8.png) are also now playable, on FAKE-08, with keyboard + mouse, gamepad, or touch input. #4400

Docs: js-dos, PICO-8

Screenshot 2026-09-11 at 6 06 41 PM

Four new metadata sources

Steam is now a metadata source for the win, linux and mac platforms, no API key required! #4241

Demozoo, Pouët and CSDb cover demoscene productions on platforms we already support. Tag a file (demozoo-108), or paste an id or a production URL into the ROM editor. #4221

scan:
  priority:
    metadata:
      - steam # PC platforms only

Docs: Metadata Providers

EmulatorJS cores and multi-disc games

A multi-disc game is handed to EmulatorJS all at once now, so you can swap discs from the emulator’s own menu instead of going back and reloading. Set emulatorjs.disable_batch_bootup to boot only the disc you launched, if a core mishandles the batch. #3988

You can also preselect a core per platform with emulatorjs.default_cores. That sets the starting point, and anyone who has already picked a core on their device keeps their choice. #4452

Docs: Cores, Multi-disc games

Library-aware recommendations

Recommendations are now built from your own library, blending metadata overlap, the list pulled from IGDB, and what you actually play. You’ll find them under Similar games on a game’s page, and in a new Recommended for you row on the home screen. Enable it by setting ENABLE_SCHEDULED_BUILD_RECOMMENDATIONS=true in your env vars. #4174

Docs: Recommendations

Screenshot 2026-09-11 at 6 08 32 PM

Automatic save sync

A save reaches the server when you save and quit, which means a closed tab, a browser crash or a laptop going to sleep leaves it on the device. Turn on emulatorjs.auto_save_sync and the player uploads the save whenever the emulator writes one instead, so there’s nothing to lose by closing the tab mid-game. It’s off by default because a game that saves often uploads often, and each upload is the whole save file. #4378

Docs: Automatic save sync

Sort and filter by game length

You can now sort and filter the gallery by how long a game takes, straight from the gallery rather than having to build a collection for it. It’s based on the HowLongToBeat main story time, so it needs HLTB_API_ENABLED and a scan that matched the game. #4392

Docs: Game length

Minor changes

Fixes

[!NOTE]

API changes

ChangeDescription
⚠️ POST /export/gamelist-xml, POST /export/pegasusNow require the PLATFORMS/WRITE grant instead of ROMS/READ, and enforce platform visibility.
⚠️ GET /setup/libraryReplaces the detected_structure discriminator (struct_a/struct_b/null) with library_ready: bool plus library_structure: str.
⚠️ ConfigResponseSplits DEFAULT_EXCLUDED_DIRS into DEFAULT_EXCLUDED_PLATFORM_DIRS and DEFAULT_EXCLUDED_MULTI_FILE_DIRS.
ConfigResponseAdds EJS_DEFAULT_CORES and EJS_ENABLE_AUTO_SAVE_SYNC.
HeartbeatResponseAdds DEMOZOO_API_ENABLED, POUET_API_ENABLED, CSDB_API_ENABLED and STEAM_API_ENABLED; TITLE_ID_EXTRACTION_ENABLED; DISABLE_JSDOS and DISABLE_PICO8.
DetailedRomSchemaAdds publishers and developers alongside the existing companies, plus is_physical, upc, has_file_on_disk, title_id, save_target and the demozoo/pouet/csdb/steam id and metadata fields.
PUT /roms/{id}/identityRebind a ROM’s identity after the files behind it moved.
POST /roms/physicalCreate a ROM with no file on disk.
GET /roms/{id}/similarLibrary-aware similar games for one ROM.
GET /recommendationsThe requester’s recommended games.
GET /music/games, /game-genres, /platforms, /statsJukebox browsing facets over the soundtrack library.

Walkthrough endpoints

MethodPathDescription
POST/roms/{id}/walkthroughs/filesUpload a walkthrough document
POST/roms/{id}/walkthroughs/gamefaqsImport one from a GameFAQs URL, as plain text
DELETE/roms/{id}/walkthroughs/files/{file_id}Delete a walkthrough
GET/roms/{rom_id}/files/{file_id}/progressThe requester’s reading progress for a document
PUT/roms/{rom_id}/files/{file_id}/progressRecord reading progress

Memory card endpoints

MethodPathDescription
GET/memory-cardsThe requester’s memory cards
GET/memory-cards/sharedCards other users have shared
GET/memory-cards/{id}, /{id}/contentOne card, and its image
GET/memory-cards/{id}/versions, /versions/{id}/contentVersion history, and one version’s image
POST/memory-cardsCreate a card, or upload your own
POST/memory-cards/{id}/versionsSnapshot the current state as a version
POST/memory-cards/deleteDelete cards in bulk
PUT/memory-cards/{id}, /{id}/visibilityRename a card, or share it

Streaming endpoints

MethodPathDescription
GET/streaming/containersThe configured containers and what they serve
GET/streaming/sessions/joinableSessions open to a second player
GET/streaming/sessions/{platform}/statusOne session’s live state
POST/streaming/desktopAdmin desktop session with no game running
POST/streaming/sessions/{platform}/joinJoin someone else’s session
POST/streaming/sessions/{platform}/heartbeatKeep a claim alive
POST/streaming/sessions/{platform}/swap-discMount another disc of a multi-disc game
POST/streaming/sessions/{platform}/state-frameAttach a thumbnail to a captured state

Other changes

New Contributors

Full Changelog: https://github.com/rommapp/romm/compare/5.2.0…5.3.0-alpha.1