# How RomM Scans the Filesystem to Detect Platforms and ROMs

> Discover how RomM scans your filesystem to detect platforms and ROMs with its multi-stage pipeline. Learn about real-time watching, metadata enrichment, and efficient file hashing for accurate results.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: how-to-guide
- Published: 2026-07-06

---

**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:

- **Real-time filesystem monitoring**: The [[`backend/watcher.py`](https://github.com/rommapp/romm/blob/main/backend/watcher.py)](https://github.com/rommapp/romm/blob/master/backend/watcher.py) component monitors the library root for changes. When it detects new folders or modifications, it creates a job that forwards to the `scan_platforms` socket endpoint.
- **Scheduled execution**: The [[`backend/tasks/scheduled/scan_library.py`](https://github.com/rommapp/romm/blob/main/backend/tasks/scheduled/scan_library.py)](https://github.com/rommapp/romm/blob/master/backend/tasks/scheduled/scan_library.py) task runs at startup (or on a configured schedule) and triggers the same scanning entry point for routine library maintenance.

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/main/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/main/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/main/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/main/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:**

```python
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):**

```python
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:**

```python
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

- **Dual initiation**: RomM supports both real-time filesystem watching via [[`backend/watcher.py`](https://github.com/rommapp/romm/blob/main/backend/watcher.py)](https://github.com/rommapp/romm/blob/master/backend/watcher.py) and scheduled scans through [[`backend/tasks/scheduled/scan_library.py`](https://github.com/rommapp/romm/blob/main/backend/tasks/scheduled/scan_library.py)](https://github.com/rommapp/romm/blob/master/backend/tasks/scheduled/scan_library.py).
- **Slug-based identification**: Platform folders are identified by normalized filesystem slugs matched against configuration and enriched via [`scan_platform`](https://github.com/rommapp/romm/blob/master/backend/handler/scan_handler.py).
- **Parallel metadata aggregation**: ROMs are hashed and processed through [`scan_rom`](https://github.com/rommapp/romm/blob/master/backend/handler/scan_handler.py), which queries multiple providers simultaneously and merges results according to priority settings.
- **Asset handling**: Save states, screenshots, and firmware receive dedicated scanning passes using shared utility functions.
- **Live progress**: The Socket.IO layer streams scan status to the frontend for real-time monitoring.

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