# Universal Ctags: a maintained ctags for tag jumping in Vim, Neovim and editors

> Universal Ctags is a maintained implementation of ctags that indexes language objects into a tag file for editors and tools. It is a C program built with Autotools, licensed GPL-2.0, and the README states it does not load ~/.ctags or ./.ctags at startup.

**universal-ctags/ctags** — A maintained ctags implementation

- Repository: https://github.com/universal-ctags/ctags
- Website: https://ctags.io
- Stars: 7,289 · Forks: 671
- Language: C
- License: GPL-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/universal-ctags-ctags

## What Universal Ctags indexes and who needs the tag file

Universal Ctags generates an index, called a tag file, of language objects found in source files. Editors and other tools read that file to locate the indexed items. The README describes the project as a maintained implementation of ctags, abbreviated u-ctags, and states its objective is to continue the development of Exuberant Ctags, whose activity stalled.

The audience is narrow but real. If you edit code in Vim or Neovim and want to jump from a call site to a definition without a language server, the tag file is the mechanism. The same file is consumed by other tools that need a flat, greppable list of names and positions. The README also notes that Exuberant Ctags added multi-language support, a way for users to define new languages by regular expression (called optlib in Universal Ctags), and emacs-style TAGS output. Universal Ctags carries those forward.

This is not an analyzer. It does not type-check, resolve overloads or report diagnostics. It records where objects are defined, and the quality of that record depends on the parser for each language.

## How the parsers and the tag file fit together

The repository layout shows the mechanism plainly. Parsers live in parsers/, with a separate peg/ directory for PEG-based grammars; main/ holds the driver and the tag-writing code; optlib/ holds language definitions written in the optlib format; and libreadtags/ is a library for reading tag files. The dsl/ directory holds the domain-specific language used to describe parsers.

A run walks the files you name, dispatches each to a parser selected by language, and emits records into the tag file. Each record carries the name, the file, an address that lets the editor find the line, and a kind letter that classifies the object. The README warns that Universal Ctags is stricter than Exuberant Ctags about kind letters and kind names: the letter must be alphabetic with F reserved for the file kind, and a kind name must start with a letter followed by alphanumerics.

Two features change the shape of the tool. The optlib lets you define a new language parser from the command line, so a format the project does not ship can still be indexed. The README lists an interactive mode as experimental, which is worth knowing before building tooling around it.

## Installing Universal Ctags and running a first index

The README gives Autotools as the build path on GNU/Linux, OSX, or Windows 10 WSL. GNU make is assumed as the make command. The configure step accepts a prefix; without one it installs under /usr/local, and make install may need extra privileges depending on that location.

```bash
git clone https://github.com/universal-ctags/ctags.git
cd ctags
./autogen.sh
./configure
make
make install
```

If you would rather not build, the README points at daily builds for Windows in the ctags-win32 project, nightly tarballs in ctags-nightly-build for Unix-like systems, and a universal-ctags Homebrew formula on Mac. A snap path exists through the ctags-snap repository, which Snapcraft builds by fetching the source from GitHub.

Once the binary is on PATH, run it over a tree and it writes a tags file in the current directory. Then point the editor at that file. In Vim the usual setting is the tags option, and the README's own documentation entry point is the ctags(1) man page, which is where the full option list lives.

```bash
ctags -R .
```

The command above recurses from the current directory. The README does not document a rollback procedure for a tags file, so treat it as a generated artifact you can delete and rebuild rather than something to version.

## The configuration paths that break an Exuberant Ctags setup

The most consequential difference for anyone migrating is where configuration is read from. The README states that Universal Ctags does not load ~/.ctags or ./.ctags at startup. It loads ~/.ctags.d/*.ctags and ./.ctags.d/*.ctags instead. A user who moves the binary and keeps the old dotfile will see their custom language definitions silently ignored, with no error, because the file is simply never opened.

The second trap is the stricter kind rules. If you reuse a .ctags written for Exuberant Ctags, the README says you must review kind letters and names defined with --regex-<LANG>= options, and it suggests using --kinddef-<LANG>= when updating them. A kind letter outside the allowed set, or a kind name that does not start with a letter, will not behave as before.

This is a deliberate trade-off. Stricter rules make kinds consistent across parsers, and the project links a pull request explaining the background. The cost lands on long-lived personal configurations that were never validated.

## Where a tag file is the wrong tool

A tag file is a static index. It knows nothing about scope, overload resolution or which of three same-named methods your cursor actually means. In a heavily overloaded C++ codebase the jump list will offer candidates and leave the choice to you, while a language server answers with the resolved definition. The search phrase ctags vs lsp captures a real decision, and for navigation inside a single project with a working language server, the server usually wins.

The index also goes stale. Nothing in the repository watches your edits; you regenerate the file. Large trees make that regeneration slow enough that people wire it to a git hook or an editor save event, and the README does not describe an incremental mode for the tag file itself.

Finally, Universal Ctags indexes what its parsers understand. A language without a shipped parser needs an optlib definition you write and maintain, and a PEG grammar you author is your own code to debug. If your work is mostly in one language with a mature parser, this is cheap. If it spans several niche formats, you are signing up to write parsers.

## Universal Ctags against Exuberant Ctags and etags

The obvious alternative is Exuberant Ctags, the ancestor project. The README states that its activity stalled, which is the reason Universal Ctags exists. Choosing Exuberant Ctags means accepting a frozen parser set and a configuration format that Universal Ctags deliberately diverges from. Choosing Universal Ctags means accepting the migration work described above. The README provides ctags-incompatibilities(7) and an introduced-changes page for the full list, so the differences are documented rather than left to be discovered.

A second alternative is etags, which produces emacs-style TAGS files. Exuberant Ctags could generate those too, and the README lists that capability among its ancestor's improvements. The difference is the consumer format: a tags file and a TAGS file are read by different tooling, so the choice is usually settled by which editor you use, not by index quality.

For teams that need cross-references rather than definitions, cscope is the other name that appears alongside ctags in searches. The two are commonly run together on the same tree, and the README does not position Universal Ctags as a replacement for it.

## Maintenance, releases and the GPL-2.0 licence

The project is not archived, and the last push to the default branch was on 2026-09-08. Releases are not frequent: v6.1.0 landed on 2023-12-28, v6.2.0 on 2025-06-01, and v6.2.1 on 2025-10-25. Between releases the master branch moves, which is why the README points at nightly tarballs and a Homebrew formula for people who want recent parsers without waiting for a tag.

The licence is GPL-2.0. That matters most if you link against the code rather than run the binary. The repository ships libreadtags/, a library for reading tag files, so a tool that links it inherits GPL-2.0 obligations. Running the ctags binary to produce a tags file is a different situation from incorporating its source into your product, and the COPYING file at the repository root is the authoritative text. This is a description of the licence, not legal advice.

Upgrade cost is mostly configuration drift. Each release can change parser behaviour, and the introduced-changes page is where those changes are recorded. If you pin a distribution package, you are also pinning its parser set.

## Conclusion

Adopt Universal Ctags if you want a maintained ctags that keeps Exuberant Ctags working while adding more language parsers and an optlib for defining languages from the command line. Do not adopt it merely to read symbols inside one file; a language server covers that better. Before committing, verify that your existing .ctags files are read from ~/.ctags.d or ./.ctags.d, because the README states the old ~/.ctags and ./.ctags paths are not loaded, and check your --regex-<LANG> kind letters against the stricter rules.

## FAQ

### What is the difference between ctags and Universal Ctags?

Universal Ctags is a maintained implementation of ctags whose stated objective is to continue the development of Exuberant Ctags, the ancestor project whose activity stalled. It keeps multi-language support and optlib while adding more improved language support and an experimental interactive mode.

### How do ctags work?

ctags generates an index, or tag file, of language objects found in source files. Editors and other tools read that index to locate the indexed items, using the name, file and address recorded for each object.

### How do I use ctags in Vim?

Run ctags over your tree to produce a tags file, then point Vim's tags option at it. The README directs users to the ctags(1) man page for the option list rather than repeating it.

### How do I install ctags in Ubuntu?

The README's build path is Autotools on GNU/Linux: clone the repository, run ./autogen.sh, ./configure, make, and make install. The configure step defaults to /usr/local, and make install may require extra privileges depending on where you install.

### How do I install ctags in Windows?

The README points at daily builds in the ctags-win32 project, where zip packages are published on its releases page, and at docs/windows.rst for building. Building is also possible under Windows 10 WSL with the Autotools steps.

## Sources

- [License: GPL-2.0](https://github.com/universal-ctags/ctags/blob/master/LICENSE)
- [Project website](https://ctags.io)
- [README](https://github.com/universal-ctags/ctags/blob/master/README.md)
- [Releases](https://github.com/universal-ctags/ctags/releases)
- [universal-ctags/ctags on GitHub](https://github.com/universal-ctags/ctags)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/universal-ctags-ctags
