Open-source project
lichess-org/chess-openings avatar
lichess-org/chess-openings

chess-openings: the opening-name dataset behind Lichess analysis

An aggregated data set of chess opening names

576 stars134 forksPythonCC0-1.0

At a glance

What is it?
The chess-openings repository is a small, deliberately plain data set: one table of opening names, their ECO classification, a PGN line and, in the built output, the UCI moves and the position itself. It is the reference that Lichess uses to name the openings a game reaches, published under CC0 so anyone can build on it, and its conventions for naming and classifying are the interesting part.
Who is it for?
Adopt chess-openings if you need a consistent, machine-readable way to name the opening a game reaches, whether you are building a Lichess-style analysis page, a database of opening names or a teaching tool, since the value here is the naming standard as much as the table.
Can I use it commercially?
Yes. CC0-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the dataset is, and what it is not

The repository is described in one line as an aggregated data set of chess opening names, and the README's title is simply Chess opening names. It is a data project rather than a software project, and the primary language is recorded as Python because the build script that turns the sources into the published files is Python.

What it contains is one table with a small number of fields, and the field table is the whole specification. There is eco, the Encyclopaedia of Chess Openings classification, present in both the root files and the built dist/ output. There is name, the opening name in English, also in both. There is pgn, a well known sequence of moves or the most common moves to reach the opening position based on master games, as PGN, also in both. And then two fields that exist only in the built output: uci, the same moves as pgn in UCI notation, and epd, the Extended Position Description, which is a FEN without move numbers, with the en passant field present only if it is legal.

So the root directory holds the source data and the dist/ directory holds the derived data, and the extra two fields are added by the build. That distinction matters for anyone consuming it, because the two shapes have different columns and the root files are what a contributor edits.

The dataset is also published in the Apache Parquet format, linked from the README, which is the shape most useful for anyone loading it into a dataframe or a columnar query engine rather than parsing TSV by hand.

The licensing is unusually clear. As a collection of facts, this data set is in the public domain, and the README notes that considerable effort was spent curating and cleaning the data, and that insofar as it qualifies for copyright, the work is released under the CC0 Public Domain Dedication. The primary language field says the same: CC0-1.0.

How the build works, and why there is a Makefile

Generating dist/ needs Python, then pip3 install chess, and then make. The alternative is to select the latest workflow run and download the build artifacts, which is the path for anyone who does not want to run it locally.

The Makefile is short and shows the whole structure of the build:

make
PYTHON = python3

sources = a.tsv b.tsv c.tsv d.tsv e.tsv

all: dist/all.tsv $(sources:%=dist/%)

dist/all.tsv: $(sources) bin/gen.py
	mkdir -p dist
	$(PYTHON) bin/gen.py $(sources) > $@

dist/%.tsv: %.tsv bin/gen.py
	mkdir -p dist
	$(PYTHON) bin/gen.py $< > $@

.PHONY: clean
clean:
	rm -f dist/all.tsv $(sources:%=dist/%)

The variables at the top are the source list, one TSV per ECO volume, and the default target builds the aggregate file and each per-volume file. The recipe is the same in both cases: the generator script is run with the sources and its standard output redirected into the target file, with the dist/ directory created first.

The dependency on bin/gen.py in every rule is the useful detail. Because the generator is listed as a prerequisite, changing the generator invalidates every output, so the Makefile cannot silently serve a stale dist/ after the logic changes.

The chess library being required is the other clue worth following. The python-chess package can parse PGN and FEN, which is what a generator needs to turn a move sequence into an EPI position and to render the same moves in UCI notation. It also explains the en passant condition in the README, since whether the en passant field is legal depends on the position and on the rules of the chess library.

The clean target removes the aggregate file and the per-volume ones, which is the ordinary thing for a Makefile to do and is the reason a stale dist/ never survives a rebuild.

The naming conventions, which are the actual contribution

A table of opening names is easy to produce. Producing one that thousands of games can be classified against consistently is harder, and the conventions section is where the real work of the project is described.

Three rules. Title case is used for opening names. Names are structured like Opening family: Variation, Subvariation, and so on, with the example given being Sicilian Defense: Najdorf Variation, English Attack. And each name has a unique shortest line, so if necessary a distinguishing move is appended, with the example King's Gambit Accepted: Schurig Gambit, with Bb5.

That last rule is the subtle one, and it exists to solve a real problem. Many opening names describe the same position, reached by different move orders, so a naive dataset would have dozens of near-duplicate rows. Requiring each name to have a unique shortest line gives every name a canonical representative, and the appended distinguishing move is the tie-breaker when even the shortest lines are not unique.

The classification method is also specified, and it is a rule rather than a lookup: the suggested way to classify games is to play moves backwards until a named position is found. To make this work well with common transpositions, multiple entries for a single opening may be added.

Playing backwards is the right direction for this dataset because the classification happens against a table of positions, and transpositions mean the same position can be reached several ways. The multiple entries allowance then means the table is a set of anchors rather than a single tree, which is what a transposition-heavy opening tree requires.

The title case rule and the family: variation structure together mean a name is not a free-form string but a position in a taxonomy, which is what lets a tool display a tree of openings rather than a flat list.

How the data is contributed, and how it reaches users

Opening data lives in five files, a.tsv through e.tsv, with the filenames corresponding to the ECO volumes. The ECO, the Encyclopaedia of Chess Openings, is the five-volume classification of chess openings, and splitting the source by volume means a change to the Queen's Gambit area touches one file rather than a single large one.

Contributions are welcome, and the README asks for concrete suggestions as pull requests, with the instruction to keep each PR to a single logical change, one opening per PR, so it can be accepted or rejected as a whole. That is a reasonable review policy for a dataset, since it makes a large pull request reviewable as a sequence of small independent facts.

The publication path is the part that shows how widely this is used. The changes will be live on lichess.org after the next update of scalachess, the Lichess server, with no fixed schedule, and on the opening explorer, whose link is to lila-openingexplorer, which updates daily.

So there are two consumers with two different cadences. The server picks up changes when it is next updated, and the opening explorer refreshes every day. The explorer is the more visible of the two, since it is the tool players use to look up where a position comes from and what the continuations are.

The repository layout backs up the data-centric nature of the project. Alongside the five TSV files there is a Makefile, a bin/ directory holding the generator, a CONTRIBUTING.md, a COPYING.txt, and a .github/ directory for the workflow that produces the downloadable build artifacts. There is no source code beyond the generator and no library, no API and no service.

The single release tells the rest of the story. There is one tagged release, dated 2026.04.16 and labelled Initial Release, and the last push was on 2026-10-06 with the project not archived. So the data is maintained continuously while the version history has barely started, which is exactly the right shape for a living dataset.

How to use it, and the questions to ask first

There are three practical questions for anyone considering this dataset.

First, which shape you want. If you are writing a tool that classifies a game, you want the dist/ files with the uci and epd columns, because the classification method the README describes, playing moves backwards until a named position is found, needs the position data. If you are maintaining the data or doing simple lookups by ECO code, the root TSV files with eco, name and pgn are enough. The Parquet version is the one to reach for if your pipeline is columnar.

Second, the size of the table. The description is an aggregated data set, and the aggregation is by name with a unique shortest line per name, so the table is one row per distinct opening rather than one row per opening line. If you were expecting an exhaustive tree of move sequences, this is not that, and the shortest-line rule is why.

Third, the update cadence. If your application needs current opening names, the daily opening explorer update is the faster route, and the server picks up changes only on its own schedule. If you vendor the data yourself, expect to refresh it periodically, and note that a dataset whose rows are curated facts changes rarely, so a vendored copy does not go stale in the way a code dependency does.

The licensing question is the easiest one. CC0 means you can use it without attribution or restriction, and the README's careful phrasing is worth reading: as a collection of facts, the data is in the public domain, and insofar as the curation and cleaning qualifies for copyright, that work is released under CC0. So both the facts and the effort of assembling them are free to reuse.

What the project is really providing is a naming standard as much as a table. The conventions for title case, for the family: variation structure, and for a unique shortest line per name are the parts a project would otherwise have to invent, and Lichess has been using them long enough that they have become a de facto standard that other tools quietly follow.

Editorial conclusion

Adopt chess-openings if you need a consistent, machine-readable way to name the opening a game reaches, whether you are building a Lichess-style analysis page, a database of opening names or a teaching tool, since the value here is the naming standard as much as the table. Do not expect an exhaustive move tree, because the aggregation rule gives one row per distinct opening with a unique shortest line, and do not expect the data to update at a fixed schedule, since the server picks up changes only when scalachess is next updated. Before you commit, decide whether you want the root TSV files with eco, name and pgn or the dist/ output with the added uci and epd columns, use the Parquet version if your pipeline is columnar, and note that the licensing is CC0, so attribution is a courtesy rather than an obligation, though the README asks that the curation work be credited where you can.

Frequently asked questions

What is the lichess-org/chess-openings repository?

It is an aggregated data set of chess opening names, published under CC0 as a public domain dedication. It contains ECO classification codes, English opening names and PGN lines, and the built dist/ output adds the same moves in UCI notation plus the EPD position of the opening.

How do I build the dist/ output?

Install Python, run pip3 install chess to get the python-chess library, and then run make. The Makefile creates dist/ and runs bin/gen.py over the five source files. Alternatively, select the latest workflow run and download the build artifacts.

What naming conventions does the dataset follow?

Opening names use title case, are structured as Opening family: Variation, Subvariation and so on, for example Sicilian Defense: Najdorf Variation, English Attack. Each name has a unique shortest line, and a distinguishing move is appended when necessary, for example King's Gambit Accepted: Schurig Gambit, with Bb5.

How is a game classified against this dataset?

The suggested method is to play moves backwards until a named position is found. To make this work with common transpositions, multiple entries for a single opening may be added, so the table is a set of position anchors rather than a single tree.

Where do I contribute a new opening?

The opening data lives in a.tsv through e.tsv, one file per ECO volume. The README asks for concrete suggestions as pull requests and asks that each pull request be a single logical change, one opening per pull request, so it can be accepted or rejected as a whole.

How quickly do changes reach users?

The changes will be live on lichess.org after the next update of scalachess, with no fixed schedule, and on the opening explorer, which updates daily. The dataset is also published in the Apache Parquet format for columnar tooling.

Official sources

  1. Issues
  2. License: CC0-1.0
  3. lichess-org/chess-openings on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lichess-org-chess-openings.svg)](https://hysenlabs.com/projects/lichess-org-chess-openings)

Was this useful?