How to Implement SteamGridDB Artwork Fetching in RomM: A Complete Guide

RomM fetches artwork from SteamGridDB through a layered architecture where SGDBBaseHandler coordinates with SteamGridDBService to query the SGDB API using your STEAMGRIDDB_API_KEY, retrieving cover art during ROM scans and storing URLs in the ROM model.

SteamGridDB provides high-quality game artwork for emulation frontends, and RomM integrates this service directly into its metadata pipeline. Implementing SteamGridDB artwork fetching in RomM requires understanding the handler-service pattern used throughout the codebase. This guide explains the complete implementation flow from environment configuration to scan-time integration, referencing the actual source code from the rommapp/romm repository.

Prerequisites and Configuration

Before fetching artwork, you must configure your API key. According to the RomM source code in config.py, the SGDB client activates only when STEAMGRIDDB_API_KEY is present in your environment variables. The handler exposes this state through SGDBBaseHandler.is_enabled()【sgdb_handler.py†L33-L36】, which returns True only when a valid key is detected, preventing unnecessary API calls.

The Service Layer Architecture

The SteamGridDBService class in backend/adapters/services/steamgriddb.py encapsulates all HTTP communication with the official SGDB API. It constructs URLs using yarl.URL and injects the required Authorization: Bearer <API-key> header via aio-http middleware【steamgriddb.py†L29-L42】. The service returns typed data structures defined in steamgriddb_types.py, ensuring type safety across the async boundary when handling paginated grid resources【steamgriddb.py†L49-L85】.

Metadata Handler Implementation

The SGDBBaseHandler in backend/handler/metadata/sgdb_handler.py extends MetadataHandler and provides the primary interface for artwork retrieval. It implements three public async methods that coordinate with the service layer to fetch game data and cover images.

Health Checks and Validation

The heartbeat() method performs a quick connectivity check by calling get_game_by_id(1). This verifies that your API key is valid and the SGDB service is reachable before attempting bulk artwork operations.

Fetching Covers by ID

To retrieve artwork for a known game, use get_rom_by_id(sgdb_id). This method first fetches the game record, then calls _get_game_covers() to collect all grid resources, returning the first valid URL found【sgdb_handler.py†L49-L77】. This is the fastest path when you already know the SGDB identifier.

Searching Games by Name

When the SGDB ID is unknown, get_details(search_term) searches SGDB by name, then fetches cover grids for each candidate using iter_grids_for_game. It returns a list of SGDBResult objects containing the game name and all matching resources【sgdb_handler.py†L81-L96】, allowing you to select the best match programmatically.

Cover Retrieval Logic and Image Types

The _get_game_covers() method implements sophisticated image selection logic. It builds a preference list for dimensions including STEAM_VERTICAL, GOG_GALAXY_TILE, and other formats, alongside grid types (STATIC and ANIMATED). The method iterates through paginated SGDB endpoints via iter_grids_for_game, extracting both thumbnail and full-size URLs while normalizing the type to static or animated【sgdb_handler.py†L47-L62】【sgdb_handler.py†L71-L87】.

from backend.handler.metadata.sgdb_handler import sgdb_handler

async def fetch_cover(sgdb_id: int):
    rom = await sgdb_handler.get_rom_by_id(sgdb_id)
    return rom.get("url_cover")

Integration with the Scan Handler

During ROM scanning, the system invokes the SGDB handler in backend/handler/scan_handler.py. When processing metadata, the scan handler checks if the handler is enabled via is_enabled(), then calls get_rom_by_id(sgdb_id) to fetch cover URLs【scan_handler.py†L1015-L1030】. The resulting artwork URL is attached directly to the ROM model. Additionally, the ROM endpoint schema in backend/endpoints/roms/__init__.py includes an optional sgdb_id field to persist the identifier for future reuse【endpoints/roms/init.py†L135-L138】.


# inside backend/handler/scan_handler.py

if SGDBHandler.is_enabled():
    sgdb_data = await sgdb_handler.get_rom_by_id(sgdb_id)
    if sgdb_data.get("url_cover"):
        rom.cover_url = sgdb_data["url_cover"]

Error Handling and API Key Validation

All SGDB calls are wrapped in try/except blocks to ensure scan stability. HTTP 401 responses trigger a custom SGDBInvalidAPIKeyException, while other failures log warnings and return empty results【steamgriddb.py†L64-L71】. This design prevents a single metadata failure from crashing the entire scan process, allowing RomM to fall back to other artwork sources gracefully.

from backend.handler.metadata.sgdb_handler import sgdb_handler

async def best_cover(search_name: str):
    results = await sgdb_handler.get_details(search_name)
    if not results:
        return None
    # pick the first result's first resource

    first = results[0]["resources"][0]
    return first["url"]

Summary

  • Configuration requires the STEAMGRIDDB_API_KEY environment variable to enable the handler via is_enabled()
  • Service Layer uses SteamGridDBService to manage HTTP requests with Bearer token authentication and yarl.URL construction
  • Handler Methods include heartbeat(), get_rom_by_id(), and get_details() for health checks and artwork retrieval
  • Cover Logic prioritizes specific dimensions like STEAM_VERTICAL and supports both static and animated grid types
  • Integration occurs during ROM scanning via scan_handler.py, with SGDB IDs stored in the ROM schema for reuse
  • Error Handling uses specific exceptions for invalid keys and graceful degradation for API failures

Frequently Asked Questions

What do I need to configure before using SteamGridDB in RomM?

You must set the STEAMGRIDDB_API_KEY environment variable in your configuration. The handler automatically checks for this key via SGDBBaseHandler.is_enabled() and will only attempt API calls when the key is present, as implemented in sgdb_handler.py.

How does RomM handle different image types from SteamGridDB?

The _get_game_covers() method requests both STATIC and ANIMATED grid types, preferring specific dimensions like STEAM_VERTICAL and GOG_GALAXY_TILE. It iterates through paginated results and returns the first matching resource URL, normalizing the type to static or animated for consistency.

Where does the artwork fetching happen during a ROM scan?

The scan handler in backend/handler/scan_handler.py invokes get_rom_by_id() when metadata is being refreshed, typically around lines 1015-1030. The fetched cover URL is then assigned to the ROM model's cover field, with the sgdb_id stored in the database schema for future lookups.

What happens if my SteamGridDB API key is invalid?

RomM raises SGDBInvalidAPIKeyException for HTTP 401 responses and logs warnings for other failures in steamgriddb.py. The handler returns empty results rather than crashing, allowing the scan to continue with other metadata sources while alerting you to authentication issues.

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 →