1. Project Overview & Quickstart (rommapp/romm)
romm
A beautiful, powerful, self-hosted rom manager and player.
Repository Details
- Repository: rommapp/romm
- GitHub Stars: โญ 11,912
- Primary Language: Python
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (rommapp/docs)
File: docs/about/brand-guidelines.md
title: Brand Guidelines
Brand Guidelines
In this context, "RomM", "The RomM Project", "the project", "we", "us", and "our" refer to the RomM project and organization.
The logo
The logo should always be used in its standard colors:
Do these things
- Use our logo to link to any page or site operated by the project.
- Use our logo in a blog post or news article about the project.
- Use our logo to inform others that your project integrates with RomM.
- Always use our logo in the colors provided.
- Always use our name in a way that makes clear you are not affiliated with the project.
If you're building something that integrates with RomM and would like to use/remix the logo, please reach out first via Discord. We'd love to hear about it.
Please don't
- Use our name or logo in any way that suggests you are us, are endorsed by us, or are part of the project.
- Use our name or logo in a way that implies partnership, sponsorship, or endorsement.
- Use our name or logo as the name or logo for your project, product, service, social media account, company, or website.
- Use our name or logo to promote, advertise, or sell any private business, closed-source software, commercial product, or paid service.
Downloadables
The logo assets live at rommapp/romm/tree/master/frontend/assets/:
isotipo.svg/isotipo.png: the mark (circular logo)logotipo.svg/logotipo.png: the wordmarksocial_preview.png: GitHub social preview
Questions
Ask on Discord or open an issue.
File: docs/about/credits.md
title: Credits
Credits
This project exists because a lot of people contributed code, designs, translations, ideas, and, most importantly, running bug reports back to the project. Thanks to every one of them.
Contributors
Every contributor to rommapp/romm, plus contributors to the surrounding repos:
Translators
The 19 locales exist because individual community members took the time to translate the UI. See each locale folder's commit history at rommapp/romm/tree/master/frontend/src/locales for per-locale credit.
Community apps
These apps are built by the community, not the RomM team. The full list is in the Community section in the RomM README.
Financial supporters
Donors via Open Collective make continued development possible, and the project wouldn't exist without you. Thank you! โค๏ธ
Upstream projects
This stack depends on a large amount of open-source work. In rough order of "how visible they are to users":
In-browser emulation
Metadata sources
- IGDB
- ScreenScraper
- MobyGames
- RetroAchievements
- SteamGridDB
- Hasheous
- Playmatch
- LaunchBox Games Database
- TheGamesDB
- Flashpoint Archive
- HowLongToBeat
- Libretro
Backend stack
- FastAPI + Starlette
- SQLAlchemy + Alembic
- RQ
- MariaDB/PostgreSQL/MySQL
- Redis/Valkey
- nginx with
mod_zip - uv
Frontend stack
Docs stack
- MkDocs + Material for MkDocs
- mike for docs versioning
- asciinema for terminal recordings
Missing?
Open a PR against this page. Credit is cheap, and we want to give it where it's due.
See also
File: docs/about/faqs.md
title: FAQs
FAQs
What is RomM?
RomM (ROM Manager) allows you to scan your library, pull metadata, browse a clean UI, play in the browser, sync to handhelds, and run it all on your own hardware.
!!! note "RomM is server software you run on a homelab box or NAS"
It is not a desktop app, and there's no .exe or .appimage. You'll need basic Linux + Docker skills to set it up. Games and copyrighted material aren't provided.
Is it free?
Yes! Licensed under AGPL-3.0, the core will always be free. Other repos under our umbrella use equally permissive licenses, and there's no tracking or upsells.
How does it compare to [X other manager]?
The emphasis here is self-hosted + multi-user + in-browser-play + the companion-app ecosystem. If those matter, try it, but if you just want a local Windows app that scans a folder, tools like LaunchBox may fit better.
Do I need metadata API keys?
It runs without any, but games won't match against a metadata source, so you won't get covers, descriptions, or ratings.
Is RomM legal?
The software is legal but what you put in it depends on your jurisdiction. We don't ship ROMs or firmware, don't help you find them, and can't give legal advice.
Can I run RomM on [X]?
Probably! Supported deployment paths:
- Docker Compose (Linux, macOS, Windows with WSL2)
- Unraid/Synology/TrueNAS SCALE
- Kubernetes
See Install & Deploy.
Not supported:
- Bare metal without containers (undocumented but should work)
- TrueNAS CORE (FreeBSD)
- Windows without WSL
How much RAM/CPU does it need?
- Minimum (small library, 1 user): 512 MB RAM, any modest CPU
- Comfortable (thousands of ROMs, a few users, occasional scans): 2 GB RAM, 2 cores
Heaviest CPU usage is during scans, as hashing and network-bound metadata calls may cause spikes.
How do I update?
docker compose pull rommapp/romm
docker compose up -d rommRead the upstream release notes before minor or major version upgrades.
Why can't I see a specific platform?
The platform folder name probably matches neither a known slug nor one of the folder name aliases. Check Supported Platforms, and fix it by either renaming the folder or updating the system.platforms binding in config.yml.
Why are my ROMs unmatched after a scan?
Most common reasons:
- No metadata providers configured
- Filename too generic (no tags, unusual naming)
- Wrong platform detection
Full troubleshooting steps can be found in Scanning Troubleshooting.
My scan finds platforms but no games inside them
This is almost always a mount-depth issue. The scanner expects the parent of your roms/ folder mounted to /romm/library, not the roms/ folder itself. If your files live at /opt/romm/library/roms/gbc/game.gbc, mount /opt/romm/library to /romm/library, then re-scan.
See Folder Structure and Scanning Troubleshooting for the full layout and common mount mistakes.
Why is my metadata wrong or incomplete?
RomM doesn't own metadata. It pulls it from third parties like IGDB and ScreenScraper. If a field is missing or wrong, the fix has to happen upstream on the provider's site. Cross-check against another provider if one is consistently off for your library.
Why am I getting a "Configuration file not Mounted!" error?
A config.yml is read from /config at startup. If the mount/file is missing or unreadable, startup bails with this error (see Configuration File for the schema and an example you can drop in).
Can I play PS3 or newer consoles in the browser?
No. In-browser emulation handles 4th-gen and earlier systems well. Saturn, PS1, and N64 are hit-or-miss. PSP and newer are mostly unplayable. Browser play is a bonus, as this is a library manager first, and standalone emulators remain the right tool for modern systems (see Supported Platforms for the current list).
Why is browser emulation laggy or not loading?
A few common causes:
- Browser: use a Chromium-based browser (Chrome, Edge, Brave). Firefox and Safari might run EmulatorJS poorly.
- HTTPS: PSP and DOS cores require the site served over
https://. Accessing by IP won't work, so you need a Reverse Proxy with TLS. - Hardware: EmulatorJS runs entirely in the browser, so CPU-heavy cores need a capable (modern) browser.
Full troubleshooting steps can be found in In-Browser Play.
Can I share my library with friends?
Add them as users via the invite flow, then either:
- Expose your instance publicly behind a Reverse Proxy with TLS.
- Keep it private and share access over a VPN or Tailscale.
See Invitations & Registration.
Can guests browse without an account?
Yes. Set KIOSK_MODE=true in your environment variables and anonymous visitors get read-only access (see Authentication โ Kiosk mode).
How do I back up?
mysqldump the DB + rsync the /romm/assets and /romm/config volumes nightly. The full procedure and test-restore protocol are in Backup & Restore.
Can I use RomM without the internet?
You need internet for:
- First-time scan with metadata providers (they're online APIs)
- Pulling the Docker image on install or upgrade
- OIDC login (if you use a cloud IdP)
After the initial setup, browsing and playing can work offline. In-browser play downloads the emulator bundle on first launch, then caches it.
Why is scan X slow?
Several possibilities, in rough order of likelihood:
- Hashing large files on spinning disks.
- Metadata providers rate-limiting (mostly ScreenScraper).
- Many files on a network mount with high latency.
See Scanning Troubleshooting โ Hash calculations are slow.
When will [feature X] be added?
We don't give ETAs on individual features. Open (or upvote) an issue at rommapp/romm to make the interest visible.
I found a bug, or I need help
For bugs, open an issue at rommapp/romm. For questions, ask in #romm-support on Discord. Either way, include:
- RomM version
- Deployment (Docker Compose/Unraid/K8s/etc.)
- Your
docker-compose.ymlwith credentials and API keys redacted - Container logs (
docker logs romm) - Exact reproduction steps
Who runs RomM?
A small team of maintainers plus a group of active community contributors. Support the project via Open Collective if you'd like!
Where can I find you?
- Discord: discord.gg/romm
- GitHub: rommapp/romm
- Website: romm.app
- Demo: demo.romm.app
See also
- Glossary if the vocabulary is new
- Troubleshooting if something's broken
File: docs/administration/oidc/authelia.md
title: OIDC with Authelia
OIDC with Authelia
Authelia is a lightweight open-source authentication and authorisation server with two-factor auth and SSO. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
Authelia installed and running, with its OIDC provider enabled:
2. Add a claims policy
In Authelia's configuration.yml under identity_providers.oidc.claims_policies, add a policy that emits the claims RomM needs (name it whatever you like):
identity_providers:
oidc:
claims_policies:
with_email:
id_token:
- email
- email_verified
- groups
- alt_emails
- preferred_username
- nameBackground on why this is needed: Authelia claims parameter restoration.
3. Register the RomM client
Under identity_providers.oidc.clients, add:
identity_providers:
oidc:
clients:
- client_id: "<random>" # see note below
client_name: "RomM"
client_secret: "$pbkdf2-sha512$<random>" # see note below
public: false
authorization_policy: "two_factor" # or one_factor
grant_types:
- authorization_code
redirect_uris:
- "https://demo.romm.app/api/oauth/openid"
claims_policy: "with_email" # must match the policy name above
scopes:
- openid
- email
- profile
- groups
userinfo_signed_response_alg: "none"
token_endpoint_auth_method: "client_secret_basic"To generate IDs and secrets, see Authelia's FAQ. The full client schema is available in the Authelia clients reference.
4. Configure RomM
In the romm service environment:
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=authelia
- OIDC_CLIENT_ID=<the client_id you picked>
- OIDC_CLIENT_SECRET=<the plaintext client secret>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com
- ROMM_BASE_URL=https://demo.romm.appOIDC_REDIRECT_URI must match what you put in redirect_uris exactly (scheme, host, path, no trailing slash). For role mapping from Authelia groups, see OIDC Setup โ Role mapping.
5. Set your email
In RomM's Profile, set your email to exactly the same address Authelia has for you. RomM matches an existing account by email on its first OIDC login, then by the Authelia user it linked, so a later email change in Authelia carries over.
6. Test
Restart, navigate to /login and click the Login with OIDC button. RomM redirects you to Authelia, and after you authenticate it sends you back signed in.
If it doesn't work, head to Authentication Troubleshooting.
File: docs/administration/oidc/authentik.md
title: OIDC with Authentik
OIDC with Authentik
Authentik is a full-featured open-source IdP with MFA, flows, and a sizeable audit/admin surface. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
Authentik installed and running via their install guide.
Log in as admin and open Admin Interface.
2. Create a property mapping (Authentik 2025.10+)
!!! important "Authentik 2025.10 breaking change"
In version 2025.10, Authentik changed the default of email_verified from true to false. A verified email is required, so without this property mapping, authentication silently fails.
In Customization โ Property Mappings โ Create โ Scope Mapping:
- Name:
RomM Email Verification - Scope name:
email - Expression:
return {
"email": user.email,
"email_verified": True,
}Click Create (see Authentik scope mappings upstream).
3. Create a provider
Admin โ Providers โ Create.
Choose OAuth2/OpenID Provider.
Configure:
- Name:
RomM OIDC Provider - Authorization flow: implicit consent
- Redirect URIs:
https://demo.romm.app/api/oauth/openid - Scopes: Under "Advanced protocol settings", move the property mapping you created above from "Available Scopes" to "Selected Scopes". Also disable any existing mappings of
emailoremail_verified. Authentik ships anemailmapping by default, so check for it and remove it if it's present.
Copy the generated Client ID and Client Secret. You'll use them as OIDC_CLIENT_ID/OIDC_CLIENT_SECRET on the app side.
Click Create.
4. Register the application
Admin โ Applications โ Create.
- Name:
RomM - Slug:
romm - Provider: the
RomM OIDC Provideryou just made
Click Create.
5. Configure
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=authentik
- OIDC_CLIENT_ID=<from Authentik>
- OIDC_CLIENT_SECRET=<from Authentik>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com/application/o/romm
- ROMM_BASE_URL=https://demo.romm.appNote that OIDC_SERVER_APPLICATION_URL points at the per-application URL (/application/o/<slug>), not the Authentik root.
For role mapping from Authentik groups, see OIDC Setup โ Role mapping.
6. Set your email
In Profile, set your email to exactly the same address Authentik has for you.
7. Test
Restart, navigate to /login and click the Login with OIDC button. RomM redirects you to Authentik, and after you authenticate it sends you back signed in.
If it doesn't work, head to Authentication Troubleshooting.
File: docs/administration/oidc/index.md
title: OIDC Setup
OIDC Setup
OpenID Connect (OIDC) lets users sign in through an external identity provider: Authelia, Authentik, Keycloak, PocketID, Zitadel, Okta, Auth0, VoidAuth, or anything standards-compliant. You get single sign-on across your homelab and centralised MFA, with no app-specific password to manage, and you can map OIDC groups or claims to roles.
!!! note "OIDC is optional"
The local user system works fine without OIDC. Enable OIDC when you already run an IdP and want auth to follow suit, or when you want to unify user management across multiple apps.
How it works
- User clicks the OIDC login button on
/login. - They're redirected to your provider.
- They authenticate (password, passkey, MFA, whatever your provider enforces).
- Provider redirects back to
{ROMM_BASE_URL}/api/oauth/openidwith an authorisation code. - The code is exchanged for an ID token and the user's email, username and role claims are read. Claims the ID token leaves out are fetched from the provider's UserInfo endpoint.
- The matching local user is logged in (see Account matching), or a new one is created on the fly unless you've turned off registration.
If the provider returns an error, or the code exchange or ID token fails validation, RomM logs the reason on an OIDC callback rejected line and sends the browser to /login?bypass_autologin=true, where the bypass flag keeps autologin from looping straight back to the provider. A sign-in RomM refuses after that, such as for a disabled account or with registration turned off, returns an error instead of redirecting.
Provider guides
Pick your provider and follow the step-by-step instructions. Every guide ends with the same set of app-side env vars and differs only in how you register the app and where you find the client ID and secret.
Not listed? Most standards-compliant OIDC providers work: Okta, Auth0, Google Workspace, Microsoft Entra, etc. Use one of the above as a template and consult your provider's docs for the registration side.
Minimum config
Whichever provider you pick, set these in the romm service's environment:
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=<authelia|authentik|keycloak|pocket-id|zitadel|voidauth|generic>
- OIDC_CLIENT_ID=<from your provider>
- OIDC_CLIENT_SECRET=<from your provider>
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- ROMM_BASE_URL=https://demo.romm.app # must match your reverse-proxy URLOIDC_REDIRECT_URI must exactly match what you register at the provider (same scheme, host, path, no trailing slash).
Private certificate authority
If your provider's HTTPS certificate comes from a private CA, such as a homelab step-ca or an internal Active Directory CA, it can't be verified out of the box. Mount the CA certificate into the container and point OIDC_TLS_CACERTFILE at it:
environment:
- OIDC_TLS_CACERTFILE=/romm/config/ca/homelab-root.pem
volumes:
- ./ca:/romm/config/ca:roThe path can be a single bundle file or a directory, in which case every file in it is read, and PEM, DER and PKCS#7 (.p7b) certificates all work. These certificates are trusted alongside the system CAs, so public providers keep working. A path that doesn't exist, or a file with no certificate in it, is logged and skipped.
Auto-provisioning
By default, the first successful OIDC login for an email that has no matching account creates a local account automatically. To require accounts to exist beforehand (so only pre-provisioned users can sign in via OIDC), turn registration off:
environment:
- OIDC_ALLOW_REGISTRATION=false # default: trueWith it disabled, an unknown user is rejected at login instead of getting a fresh account. New accounts land in the default permission group unless a role claim maps them to Admin.
Role mapping
RomM has only two roles: User and Admin (see Users & Roles). New OIDC users are provisioned as Users. To let your IdP promote someone to Admin based on group membership, set:
environment:
- OIDC_CLAIM_ROLES=groups # which claim to read
- OIDC_ROLE_ADMIN=romm-admin,platform-admins # group values โ AdminOn every login, the claim named by OIDC_CLAIM_ROLES is read (often groups, or realm_access.roles on Keycloak, so check your provider's ID token or UserInfo response). If a value matches OIDC_ROLE_ADMIN, the user becomes an Admin.
Roles are re-evaluated on every login, so demoting someone on the IdP side takes effect the next time they sign in.
!!! warning "Once OIDC_CLAIM_ROLES is set, users must match a mapped group"
As soon as OIDC_CLAIM_ROLES is configured, RomM expects every user to match at least one mapped role group. A user whose claim matches none of the configured groups is rejected at login with:
```json
{"detail":"User has not been granted any roles for this application."}
```
To let non-admin users in, map their group to the **User** role with `OIDC_ROLE_VIEWER` (or `OIDC_ROLE_EDITOR`):
```yaml
environment:
- OIDC_CLAIM_ROLES=groups
- OIDC_ROLE_ADMIN=romm-admin,platform-admins # โ Admin
- OIDC_ROLE_VIEWER=platform-users # non-admins โ User (grants access)
```
`OIDC_ROLE_VIEWER` and `OIDC_ROLE_EDITOR` no longer map to distinct roles (matching users all resolve to **User**), but they're still how you grant those users access when role claims are enabled. Point them at a group that all your non-admin users belong to. Only `OIDC_ROLE_ADMIN` changes the role, so use [permission groups](../users-and-roles.md#permission-groups) for finer-grained access.
If you don't set `OIDC_CLAIM_ROLES` at all, role mapping is skipped entirely and everyone is provisioned as a **User** in the default permission group.Account matching
The first OIDC login for an existing local account matches it by email, so set the account's email to exactly the address your provider has for the user. That login links the account to the user's identity at the provider (the token's iss issuer and sub subject). Every later login matches on that identity:
- Email changes at the provider carry over. The user still signs into the same account, and RomM stores the new email, unless another account already uses it.
- Switching providers keeps accounts. A login from a new issuer matches by email again and relinks the account to the new provider.
- A new subject for a linked email is refused. When the same provider sends a different subject for an email that is already linked, RomM rejects the login with a 403 rather than hand the account to someone else. This happens when the provider reassigns the email or recreates the user. See Authentication Troubleshooting to relink it.
Autologin
To bypass the login page entirely and redirect straight to the IdP, so RomM feels like a native part of your SSO stack:
environment:
- OIDC_AUTOLOGIN=trueCombine with DISABLE_USERPASS_LOGIN=true to lock out local accounts entirely.
!!! warning "Keep one local admin"
Don't set DISABLE_USERPASS_LOGIN=true without first confirming an admin account exists on the IdP side and can log in. If OIDC breaks and you've disabled local login, you're locked out until you fix the container env.
RP-Initiated Logout
When set, hitting "Sign out" in RomM also signs the user out at the IdP:
environment:
- OIDC_RP_INITIATED_LOGOUT=true
- OIDC_END_SESSION_ENDPOINT=https://auth.example.com/application/o/end-session/The endpoint URL is provider-specific, so check the per-provider guides or your IdP's docs.
Username source
By default the local part of the email (the bit before @) becomes the username, but you can override it with:
environment:
- OIDC_USERNAME_ATTRIBUTE=preferred_usernameWhatever that attribute holds gets sanitised before it becomes a username to prevent invalid characters from being used. RomM also sends a PKCE challenge on every authorization request, so it's safe to mark PKCE required for the client if your IdP offers that.
Important notes
- Email must match between OIDC and an existing local account on its first OIDC login, otherwise OIDC creates a new account alongside the old one. After that, the account follows the user's identity at the provider (see Account matching).
- HTTPS is required in production, as OIDC will refuse to redirect to a plain-HTTP
ROMM_BASE_URL. - Large drift between the RomM host and IdP will lead to clock skew and cause ID-token validation to fail.
Troubleshooting
Common failures and fixes live in Authentication Troubleshooting. Two of the usual suspects:
redirect_uri_mismatch:OIDC_REDIRECT_URIdiffers from what's registered at the provider. A trailing slash alone is enough to trigger it.- User created but not made Admin: check
OIDC_CLAIM_ROLESpoints at a claim that actually exists in the ID token or the UserInfo response, and that the group values matchOIDC_ROLE_ADMINexactly (case-sensitive).
File: docs/administration/oidc/keycloak.md
title: OIDC with Keycloak
OIDC with Keycloak
Keycloak is the heavyweight open-source IAM standard. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
Keycloak installed and running via their getting started guide.
Log into the Admin Console and either create a new realm for the app or reuse an existing one.
2. Add a client
In the Admin Console, select your realm โ Clients โ Create client.
- Client type:
OpenID Connect. - Client ID:
romm(or something unique). - Click Next.
- On the capability page:
- Enable Client authentication
- Leave only Standard flow enabled
- Click Next
- Set URLs:
- Root URL:
https://demo.romm.app - Valid Redirect URIs:
https://demo.romm.app/api/oauth/openid - Web origins:
https://demo.romm.app
- Root URL:
- Save, then head to the Credentials tab and copy the Client Secret.
3. Configure
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=keycloak
- OIDC_CLIENT_ID=romm
- OIDC_CLIENT_SECRET=<from Keycloak Credentials tab>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://keycloak.example.com/realms/<realm-name>
- ROMM_BASE_URL=https://demo.romm.appOIDC_SERVER_APPLICATION_URL must include the realm (.../realms/<realm-name>), not just the Keycloak root.
4. Set email + verify in Keycloak
In Profile, set your email to the same address Keycloak has for you.
On the Keycloak side, go to Admin Console โ Users and mark each user's email as verified. Users with unverified emails will be rejected on login.
5. Test
Restart, navigate to /login and click the Login with OIDC button. RomM redirects you to Keycloak, and after you authenticate it sends you back signed in.
If a local user already exists with a matching email, they're signed into that account. Otherwise a new account is created as a regular User in the default permission group.
If it doesn't work, head to Authentication Troubleshooting.
6. (Optional) Disable local password logins
Force users through Keycloak:
environment:
- DISABLE_USERPASS_LOGIN=true!!! warning
Before flipping this, confirm at least one Admin account can sign in via OIDC, otherwise a broken OIDC flow locks you out.
7. (Optional) Role mapping
By default, new OIDC users come in as regular Users. To let Keycloak promote someone to Admin based on a role/group:
environment:
- OIDC_CLAIM_ROLES=groups # or realm_access.roles, depending on your token
- OIDC_ROLE_ADMIN=romm-adminConfigure Keycloak's client to include the role/group claim in the ID token (usually via a Group Membership or Realm Role client scope mapper). Values in the claim are compared against OIDC_ROLE_ADMIN on every login, so demoting in Keycloak takes effect on the user's next sign-in (see OIDC Setup โ Role mapping for the generic version).
File: docs/administration/oidc/pocketid.md
title: OIDC with PocketID
OIDC with PocketID
PocketID is a minimalist OIDC provider that only supports passkey authentication, with no passwords. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
PocketID installed, running, and your admin passkey already registered via their PocketID setup guide.
2. Add the client
In PocketID admin:
- Application Configuration: make sure Emails Verified is ticked, because RomM requires verified emails.
- Go to OIDC Client โ Add OIDC Client.
- Fill in:
- Name:
RomM - Callback URLs:
https://demo.romm.app/api/oauth/openid
- Name:
- Save. Stay on this page, because the client secret is displayed only once.
- Copy both the Client ID and Client Secret now.
3. Configure RomM
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=pocket-id
- OIDC_CLIENT_ID=<from PocketID>
- OIDC_CLIENT_SECRET=<from PocketID>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://id.example.com
- ROMM_BASE_URL=https://demo.romm.appOIDC_SERVER_APPLICATION_URL is the root URL of your PocketID instance.
4. Set your email
In RomM's Profile, set your email to exactly the same address PocketID has for you.
5. Test
Restart, navigate to /login and click the Login with OIDC button. RomM redirects you to PocketID, and after you authenticate it sends you back signed in.
If it doesn't work, head to Authentication Troubleshooting.
File: docs/administration/oidc/voidauth.md
title: OIDC with VoidAuth
OIDC with VoidAuth
VoidAuth is an open-source SSO authentication and user management provider that sits in front of your self-hosted applications. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
VoidAuth installed and running via their self-hosted deployment docs. We'll use the APP_URL from the example compose.yml in those docs as https://auth.example.com.
Your RomM instance will be referenced as https://demo.romm.app.
2. Create a new app
Log in as an admin in the VoidAuth web interface. Create a new OIDC app (e.g. RomM).
- Name:
RomM - Home Page URL:
https://demo.romm.app - Logo URL:
https://docs.romm.app/resources/romm/isotipo.png - Group: You could add a group that the user must belong to in order to access to your RomM instance. If left empty, any user created in your VoidAuth instance will be allowed.
- Skip Consent and MFA Required: These options can be enabled or left disabled as you prefer.
- Client ID: Generate an ID using the button.
- Auth Method:
Client Secret Basic - Client Secret: Generate a secret using the button.
- Redirect URLs: add
https://demo.romm.app/api/oauth/openid - Response Types: check
code - Grant Types: check
authorization_codeandrefresh_token - Post Logout URL:
https://demo.romm.app/
Click the Create button to validate your app.
3. Configure
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=voidauth
- OIDC_CLIENT_ID=<from VoidAuth>
- OIDC_CLIENT_SECRET=<from VoidAuth>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com
- OIDC_SERVER_METADATA_URL=https://auth.example.com/oidc/.well-known/openid-configuration
- ROMM_BASE_URL=https://demo.romm.appVoidAuth's OIDC discovery URL can be found at the top of your OIDC App page, by clicking on OIDC Endpoints.
For role mapping from VoidAuth, see OIDC Setup โ Role mapping.
4. Test
Restart, navigate to /login and click the Login with VoidAuth button. RomM redirects you to VoidAuth, and after you authenticate it sends you back signed in.
If it doesn't work, head to Authentication Troubleshooting.
File: docs/administration/oidc/zitadel.md
title: OIDC with Zitadel
OIDC with Zitadel
Zitadel is an enterprise-grade open-source IAM platform supporting OAuth2, OIDC, SAML, and passwordless. Before starting, read the OIDC Setup overview, as it covers the RomM-side settings common to every provider.
1. Prerequisites
Zitadel installed and running via their self-hosted deployment docs. Change the default organization password before you go further.
2. Create a project
Create a new project (e.g. RomM). This holds the client and its auth settings. On the General tab, Check authorization on Authentication is recommended. If turned off, anyone who can register in Zitadel can sign into RomM (as a regular User). Turn this on if Zitadel registration is open.
2.5 (Optional) Grant users to the project
If you enabled Check authorization on Authentication:
- Authorization tab โ New.
- Select user(s) โ Continue.
- "No role has been created yet" is fine, just Save.
- The user appears in the authorization list with no roles.
3. Create the application
On the project's General tab, under Applications, click New. Tick I'm a pro. Skip this wizard for the fast path.
- Name:
RomM - Application Type:
Web - Grant Types:
Authorization Code - Response Types:
Code - Authentication Method:
Basic - Redirect URIs:
https://demo.romm.app/api/oauth/openid - Post Logout URIs:
https://demo.romm.app/
Click Create. Zitadel shows the client secret only once, so copy it now.
4. Enable claims in the ID Token
RomM fetches claims the ID token leaves out from Zitadel's UserInfo endpoint, and putting them in the ID token avoids that extra request. It also rules out "Email is missing from token" when the UserInfo response doesn't carry the email. On the application's Token Settings tab, tick User Info inside ID Token and Save.
5. Configure
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=zitadel
- OIDC_CLIENT_ID=<from Zitadel>
- OIDC_CLIENT_SECRET=<from Zitadel>
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
- OIDC_SERVER_APPLICATION_URL=https://zitadel.example.com
- ROMM_BASE_URL=https://demo.romm.appZitadel's OIDC discovery URL is at <OIDC_SERVER_APPLICATION_URL>/.well-known/openid-configuration, which is handy for debugging.
For role mapping from Zitadel, see OIDC Setup โ Role mapping.
6. Set email + Zitadel
In RomM's Profile, set your email to exactly the same address your Zitadel user has.
7. Test
Restart, navigate to /login and click the Login with OIDC button. RomM redirects you to Zitadel, and after you authenticate it sends you back signed in.
If it doesn't work, head to Authentication Troubleshooting.
File: docs/administration/authentication.md
title: Authentication
Authentication
This page is the operator-side authentication reference: the settings you change on the server to control how people sign in. The client-side reference ("how do I actually authenticate an API call?") is in API Authentication.
Authentication flows RomM supports:
- Username + password (default): local account, bcrypt-hashed, stored in the DB
- OIDC: single sign-on via an external IdP
- Client API Tokens: long-lived per-user tokens for companion apps and scripts
- Device pairing: short codes for bootstrapping a token onto a handheld
- Kiosk mode: unauthenticated read-only access, handy for public demos
Session config
Sessions are cookie-based and stored in Redis. Relevant env vars:
| Variable | Default | What it controls |
|---|---|---|
ROMM_AUTH_SECRET_KEY |
(required) | HS256 signing key for session tokens and JWTs. Generate with openssl rand -hex 32. Never rotate this casually, because it invalidates every active session and every outstanding invite link. |
ROMM_AUTH_SECRET_KEY_FILE |
Read the secret from a file (e.g. via Docker secrets) instead of an env var. | |
SESSION_MAX_AGE_SECONDS |
86400 (24 h) | How long a session cookie lives before the user has to sign in again. |
OAUTH_ACCESS_TOKEN_EXPIRE_SECONDS |
900 (15 min) | Short-lived OAuth2 access token TTL. |
OAUTH_REFRESH_TOKEN_EXPIRE_SECONDS |
2592000 (30 d) | Refresh token TTL for OAuth2. |
DISABLE_CSRF_PROTECTION |
false |
Disable CSRF middleware. Only do this behind a trusted reverse proxy that strips unwanted cross-origin traffic. |
Local (username + password)
Local accounts are created via invitations, registration, admin setup, or the Setup Wizard, and passwords are bcrypt-hashed.
Disable local password login entirely (force OIDC-only):
environment:
- DISABLE_USERPASS_LOGIN=true!!! warning "Keep a way in"
Before setting DISABLE_USERPASS_LOGIN=true, confirm that at least one Admin account can sign in via OIDC. If OIDC breaks and you've already disabled local login, your only way in is editing the container env.
Password reset
A user who forgot their password can ask for a reset link from the sign-in page (POST /api/forgot-password), which works once within 10 minutes. Where it goes depends on the server:
- With email set up,
ROMM_BASE_URLpointing at a real host, and an email address on the account, the link goes to that address, at most once a minute per user. - Otherwise, or when the email can't be sent, the link is written to the container log for an admin to pass on.
The response is the same whether or not the username exists, so the form can't be used to find out which accounts exist. Requests for an existing account and completed resets are both recorded in the audit log.
Admins can also set any user's password directly, and the new password applies from the next login while existing sessions stay valid until they expire.
OIDC
See OIDC Setup for the full walkthrough. One-liner config sketch:
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=keycloak
- OIDC_CLIENT_ID=...
- OIDC_CLIENT_SECRET=...
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openidWhen OIDC is configured, an OIDC sign-in option is offered alongside username/password. Set OIDC_AUTOLOGIN=true to redirect straight to the IdP without the user having to choose. By default the first login for an unknown email auto-creates an account; set OIDC_ALLOW_REGISTRATION=false to allow only pre-provisioned users in (see OIDC Setup for role mapping and auto-provisioning).
Client API Tokens
For anything long-lived (a companion app, a cron job, a script) use Client API Tokens instead of storing a password. Each token:
- Belongs to a specific user
- Carries a subset of that user's scopes (you choose which at creation time)
- Has an optional expiry (no expiry = never expires until manually revoked)
- Can be "paired" to a device via a short code
Each user gets up to 25 active tokens. The API side ("how do I send this thing in a request?") lives in API Authentication.
Kiosk mode
Kiosk mode grants unauthenticated, read-only access to nearly every GET endpoint. Anyone reaching the instance can browse, but only a logged-in admin can write, scan, upload, or manage users.
environment:
- KIOSK_MODE=trueAppropriate for:
- Shared-terminal demos
- Public-facing "display" instances
demo.romm.app
Authenticated users (when you do sign in) will still see the full UI with all actions available to their role and permission group.
Download-endpoint auth bypass
environment:
- DISABLE_DOWNLOAD_ENDPOINT_AUTH=trueSkips auth on GET /api/roms/{id}/content/โฆ and the firmware download endpoint. It exists so third-party apps that can't carry a bearer header (like dumb emulators loading a ROM by URL) can still pull files. Only enable this when the public internet can't reach RomM directly, i.e. there's auth or an IP allowlist at the reverse-proxy layer. Otherwise you've just made your library world-downloadable.
Revoking access
To fully cut a user off, an admin needs to:
- Revoke all of the user's Client API Tokens.
- Disable or delete the user account.
Disabling preserves the account row (useful for audit/future re-enable). Deletion removes the user record and personal data, with the caveats covered in Users & Roles โ Editing and deleting users.
File: docs/administration/firmware-management.md
title: Firmware Management
Firmware Management
Many emulated platforms require BIOS or firmware to boot, or to reach a certain level of stability. RomM tracks firmware files per platform, stores them on disk, and serves them to in-browser players (EmulatorJS/Ruffle) and companion apps that request them.
Firmware is not ROM. Keep the two separate:
/romm/library/roms/: game ROMs, homebrew, demos, etc./romm/library/bios/: system firmware
!!! important "Legality varies by jurisdiction"
RomM does not ship games or firmware, and the team cannot help you obtain BIOS files. Always check your local laws and the emulator's documentation for guidance on what you can legally use.
Ingesting firmware
- Put the file in the right
bios/folder (see Folder Structure) - Run a scan, and firmware is picked up alongside ROMs
- The platform's firmware is then listed on that platform's page, and offered to the in-browser player
Where bios/ lives is up to you. The default is bios/{platform} at the library root, and you can move it with the filesystem.structure.firmware template.
Missing firmware
Delete a file from bios/ and the next scan flags it missing instead of dropping it from the database. Put the file back and the next scan clears the flag. If you're never replacing it, the Cleanup missing firmware task deletes every flagged row in one go (see Scheduled Tasks).
Platform-specific firmware
Every emulator has its own requirements: which files it needs, specific hashes, naming conventions. The Supported Platforms table flags which platforms need firmware for EmulatorJS playback.
Common examples:
| Platform | Typical firmware file | Where to put it |
|---|---|---|
| PlayStation (PSX) | scph1001.bin, scph5501.bin, scph5502.bin |
bios/ps/ |
| Game Boy Advance | gba_bios.bin |
bios/gba/ |
| Sega CD | bios_CD_U.bin, bios_CD_E.bin, bios_CD_J.bin |
bios/segacd/ |
| Saturn | saturn_bios.bin, mpr-17933.bin |
bios/saturn/ |
| Nintendo DS | firmware.bin, bios9.bin, bios7.bin |
bios/nds/ |
File naming matters because emulators look for specific filenames. Double-check against the emulator core's documentation if something won't boot.
Integration with companion apps
Handheld syncers (Grout, Argosy, DeckRommSync) can pull firmware alongside ROMs. They use the same Client API Token auth as for ROMs, so scope the token to include firmware.read.
Backups
Firmware files are user-owned data, so back them up! If you're backing up the /romm/library volume, firmware is already covered (see Backup & Restore).