Before installing Freeplay, you need:
- A directory containing your ROM files, organized by console (e.g.
roms/nes/,roms/snes/) - A
freeplay.tomlconfiguration file (see Configuration below)
Freeplay is published to the GitHub Container Registry at
ghcr.io/chrisallenlane/freeplay.
Create a directory that will hold your config, ROMs, and (optionally) BIOS files:
/path/to/data/
freeplay.toml
roms/
nes/
Mega Man.zip
Super Mario Bros.zip
snes/
Chrono Trigger.zip
bios/ # only needed for consoles that require BIOS files
SCPH1001.BIN
Copy the example config into your data directory and edit it:
cp freeplay.example.toml /path/to/data/freeplay.tomlSee Configuration for details.
Create a docker-compose.yml that points to your data directory:
services:
freeplay:
image: ghcr.io/chrisallenlane/freeplay:latest
ports:
- "8080:8080"
volumes:
- /path/to/data:/data
environment:
LOG_LEVEL: info # debug | info (default) | warn | error
restart: unless-stoppedLogs are written to stdout in slog text format; Docker's log driver
handles capture and rotation. Set LOG_LEVEL=debug for verbose output
when troubleshooting.
docker compose up -dFreeplay will be available at http://localhost:8080.
Freeplay requires Go 1.26 or later. Install it from https://go.dev/dl/.
git clone https://github.com/chrisallenlane/freeplay.git
cd freeplay
make buildThe binary will be written to dist/freeplay.
Set up a data directory as described in the Docker section above. Copy and edit the example config:
cp freeplay.example.toml /path/to/data/freeplay.toml./dist/freeplay -data /path/to/dataThe -data flag defaults to /data if omitted (which is only useful inside
the Docker container).
To override the listen port from the config file, pass -port:
./dist/freeplay -data /path/to/data -port 9090Useful when running multiple instances or when the config-file port conflicts
with another service. Omit the flag (or pass -port 0) to use the value from
freeplay.toml.
Logs go to stdout. Set the LOG_LEVEL environment variable to debug,
info (default), warn, or error to control verbosity:
LOG_LEVEL=debug ./dist/freeplay -data /path/to/dataFreeplay reads freeplay.toml from the root of the data directory. A fully
annotated example is provided in freeplay.example.toml.
| Key | Default | Description |
|---|---|---|
port |
8080 |
HTTP listen port |
cover_art_api |
(empty) | Cover art provider ("igdb" or empty to disable) |
cover_art_api_key |
(empty) | API credentials in client_id:client_secret format |
Each [roms.<Name>] section maps a console display name to a directory and
an EmulatorJS core:
[roms.NES]
path = "roms/nes"
core = "fceumm"
[roms."Super Nintendo"]
path = "roms/snes"
core = "snes9x"The <Name> is both the display name in the UI and the console identifier.
This lets you choose whether to merge regional variants or keep them separate.
For example, you could combine NES and Famicom ROMs under a single [roms.NES]
entry, or create separate [roms.NES] and [roms.Famicom] entries if you
prefer to browse them independently. The same applies to Genesis/Mega Drive,
SNES/Super Famicom, and so on.
Paths may be absolute or relative to the data directory. Common cores:
| Console | Core |
|---|---|
| NES | fceumm |
| SNES | snes9x |
| Genesis | genesis_plus_gx |
| Game Boy Advance | mgba |
| Game Boy / Game Boy Color | gambatte |
| Nintendo 64 | mupen64plus_next |
| PlayStation | pcsx_rearmed |
| Arcade | fbneo |
| Atari 2600 | stella2014 |
Some consoles (e.g. PlayStation) require BIOS files. Specify the path to the BIOS file directly on the ROM entry:
[roms.PlayStation]
path = "roms/ps1"
core = "pcsx_rearmed"
bios = "bios/SCPH1001.BIN"Paths are relative to the data directory unless absolute.
Freeplay can automatically fetch cover art from IGDB, which is powered by the Twitch API. This is optional -- Freeplay works without it, but games will be displayed without cover images.
- Log in (or create an account) at https://dev.twitch.tv/console
- Click Register Your Application
- Fill in the form:
- Name: anything (e.g. "freeplay")
- OAuth Redirect URLs:
http://localhost(this value doesn't matter for server-to-server auth, but the field is required) - Category: choose any
- Client Type: Confidential
- Click Create
- On the application management page, click Manage on your new app
- Note the Client ID
- Click New Secret to generate a Client Secret and note it
Add the following to your freeplay.toml:
cover_art_api = "igdb"
cover_art_api_key = "your_client_id:your_client_secret"Replace your_client_id and your_client_secret with the values from your
Twitch application.
To improve cover art search accuracy, you can specify IGDB platform IDs for each console. This narrows search results to the correct platform:
[roms.NES]
path = "roms/nes"
core = "fceumm"
igdb_platform_ids = [18]This is especially useful when merging regional variants into a single console
entry. For example, if you combine NES and Famicom ROMs under [roms.NES],
you can specify both platform IDs so cover art is found for titles from either
region:
[roms.NES]
path = "roms/nes"
core = "fceumm"
igdb_platform_ids = [18, 99] # NES and FamicomIGDB platform IDs can be found via the IGDB API documentation or by browsing platform pages on https://www.igdb.com/.
When cover art is configured, Freeplay fetches covers automatically after each
ROM scan. Images are cached as PNG files under <data>/covers/<Console>/ and
are not re-fetched once present. To force a re-fetch for a specific game,
delete its cover file and trigger a rescan via POST /api/rescan.