How RomM Scans the Filesystem to Detect Platforms and ROMs

RomM initiates filesystem scans through either a real-time watcher or scheduled task, which triggers a multi-stage pipeline that identifies platform folders by their filesystem slugs, queries metadata providers to enrich platform data, and then hashes each ROM file to perform parallel metadata lookups before merging results according to user-defined priority settings.

RomM is a self-hosted ROM manager that continuously synchronizes your game library with rich metadata from multiple online sources. The platform's ability to scan the filesystem and detect platforms and ROMs operates through a coordinated pipeline involving filesystem monitoring, platform identification, and comprehensive ROM processing. This architecture ensures your library stays synchronized with disk changes while automatically enriching entries with artwork, descriptions, and game IDs from providers like IGDB and MobyGames.

The Three-Stage Scanning Architecture

RomM's detection process follows a strict pipeline that moves from filesystem observation to database persistence. The flow coordinates between the watcher subsystem, socket endpoints, and the core scan handler.

Stage 1: Initiation via Watcher or Scheduled Tasks

The scanning process begins through one of two entry points defined in the codebase:

Both paths ultimately emit a scan:scan_platforms event through the Socket.IO manager to begin the detection sequence.

Stage 2: Platform Discovery and Normalization

Once initiated, the socket endpoint [backend/endpoints/sockets/scan.py](https://github.com/rommapp/romm/blob/master/backend/endpoints/sockets/scan.py) orchestrates platform identification. For each folder slug found on disk, the system calls scan_platform in [backend/handler/scan_handler.py](https://github.com/rommapp/romm/blob/master/backend/handler/scan_handler.py).

The platform detection logic performs these operations:

  1. Slug normalization: It extracts the filesystem slug (folder name) and normalizes it against the user-defined configuration via config_manager.
  2. Metadata hydration: It queries multiple providers (IGDB, MobyGames, RetroAchievements, LaunchBox, etc.) in parallel to fetch platform IDs, logos, and official names.
  3. Database persistence: The resulting Platform model is stored via db_platform_handler, with logging output such as "Folder n64 identified as Nintendo 64".

Unknown folders are handled according to the scan type configuration, with options to ignore or create placeholder entries depending on the user's SCAN_METADATA_PRIORITY settings.

Stage 3: ROM Hashing and Metadata Enrichment

After platform registration, the system processes individual ROM files through the scan_rom function in [backend/handler/scan_handler.py](https://github.com/rommapp/romm/blob/master/backend/handler/scan_handler.py). This stage involves:

  • Filesystem enumeration: The [backend/handler/filesystem/roms_handler.py](https://github.com/rommapp/romm/blob/master/backend/handler/filesystem/roms_handler.py) returns a list of ROM files for the current platform.
  • Hash calculation: Each file is hashed to generate unique identifiers for database storage and matching.
  • Parallel metadata fetching: The handler runs concurrent lookups across all enabled providers (IGDB, MobyGames, Screenscraper, RetroAchievements, Hasheous, etc.).
  • Priority merging: Results are merged according to the SCAN_METADATA_PRIORITY and SCAN_ARTWORK_PRIORITY configuration arrays, with the first successful provider taking precedence for each field.
  • Asset scanning: Helper functions scan_save, scan_state, and scan_screenshot process related assets using the shared _scan_asset utility to compute file sizes and optional hashes.

The final Rom entity is persisted via db_rom_handler.add_rom, with the socket manager streaming progress events (scan:scanning_rom, scan:scanning_platform) to connected clients.

Triggering Scans Programmatically

You can initiate the scanning pipeline outside the web UI using Socket.IO clients or direct Python imports.

Trigger a full library rescan via Socket.IO:

import socketio
import asyncio

async def rescan_library():
    sio = socketio.AsyncClient()
    await sio.connect("http://localhost:3000")
    await sio.emit(
        "scan:scan_platforms",
        {
            "scan_type": "complete",  # Options: new_platforms, quick, update, unmatched, complete, hashes

            "metadata_sources": ["igdb", "ra", "ss", "launchbox"],
        },
    )
    await sio.disconnect()

asyncio.run(rescan_library())

Programmatic platform discovery (as used by the watcher):

from handler.scan_handler import scan_platform

async def detect_platform():
    platform = await scan_platform(
        fs_slug="n64", 
        fs_platforms=["n64", "snes", "genesis"]
    )
    print(f"Detected: {platform.name}")  # Output: "Nintendo 64"

Scanning a single ROM with metadata lookup:

from handler.scan_handler import ScanType, scan_rom
from handler.filesystem.roms_handler import get_fs_rom

async def process_single_rom():
    fs_rom = await get_fs_rom(
        platform_slug="n64", 
        rom_path="/roms/n64/Super Mario 64.n64"
    )
    
    rom = await scan_rom(
        scan_type=ScanType.NEW_PLATFORMS,
        platform=platform,  # Platform object from previous detection

        rom=Rom(),          # Empty placeholder for new entry

        fs_rom=fs_rom,
        metadata_sources=["igdb", "ra", "ss"],
        newly_added=True,
    )
    print(f"Enriched: {rom.name} (IGDB ID: {rom.igdb_id})")

Summary

Frequently Asked Questions

How does RomM handle folders that don't match known platforms?

When scan_platform encounters an unrecognized folder slug, it checks the config_manager for user-defined mappings. If no match exists and the scan type is set to complete or new_platforms, it attempts to fetch metadata using the slug as a search term. Unmatched folders can be configured to create placeholder entries or be ignored entirely based on the SCAN_METADATA_PRIORITY array settings.

What is the difference between a quick scan and a complete scan?

A quick scan (specified via ScanType.QUICK or the "quick" scan type string) only processes new files that don't exist in the database, skipping existing entries to save time. A complete scan (ScanType.COMPLETE) re-evaluates all platforms and ROMs, clearing previously stored IDs for disabled sources and re-downloading metadata even for existing entries. The update and unmatched scan types provide intermediate granularity for specific maintenance tasks.

How does RomM decide which metadata provider to use for a specific ROM?

The system queries all enabled providers in parallel but merges results according to the SCAN_METADATA_PRIORITY configuration list. Providers are checked in the order specified (e.g., ["igdb", "mobygames", "screenscraper"]), and the first successful response for each metadata field (name, description, cover art) is used. For artwork specifically, SCAN_ARTWORK_PRIORITY provides a separate ordering independent of metadata sources.

Can RomM scan ROMs without internet connectivity for metadata?

Yes, the core scan the filesystem and detect platforms and ROMs functionality works offline. The scan_rom function calculates file hashes and creates database entries regardless of connectivity. When offline, metadata enrichment steps simply return empty results, and the ROM remains in the library with basic file information (filename, size, hash). You can later trigger a metadata-only rescan when connectivity returns by using the update scan type with specific metadata sources enabled.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →