Library / SDK
ktr0731/go-fuzzyfinder avatar
ktr0731/go-fuzzyfinder

go-fuzzyfinder puts an fzf-style picker inside your Go program

fzf-like fuzzy-finder as a Go library

519 stars50 forksGoMIT

At a glance

What is it?
go-fuzzyfinder is a MIT licensed Go library that gives a command line tool an fzf-like terminal picker without requiring the user to install one, with a preview window callback and preselection that work over indexes into your own data rather than over rows of text. It suits Go authors building interactive CLIs, and it is still on a zero major version.
Who is it for?
Go authors building a command line tool whose main job is picking from a list, especially where the entries share names and something extra has to be shown to tell them apart, are the people go-fuzzyfinder is for. It is the wrong choice for a shell or Python tool, since it is a Go library, and it is the wrong choice for a non-interactive script where a full screen interface is the wrong shape.
Can I use it commercially?
Yes. MIT 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 3 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

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

Editorial analysis

A fuzzy finder you embed, rather than one you shell out to

go-fuzzyfinder is a Go library that provides fuzzy finding with an fzf-like terminal user interface. The name is the whole pitch, because the alternatives it names are all separate executables. The readme lists fzf, fzy and skim as the powerful fuzzy-finder command line tools for finding lines interactively, and then explains why embedding one is not always practical. The result is a library a Go program calls directly, which means the fuzzy selection UI becomes part of your tool rather than something the user has to install and configure first.

bash
$ go get github.com/ktr0731/go-fuzzyfinder

The package is published on pkg.go.dev under that path, the licence is MIT, and the newest release in the tag list is v0.9.0 from 2025-04-15, with v0.8.0 and v0.7.0 before it. The repository publishes no GitHub releases of its own, but the tree carries a goreleaser configuration, which is the usual sign that tagged builds are produced from tags rather than from a release page.

Two functions, and the multi one behaves like fzf -m

The API surface is two functions. Find selects a single item, and FindMulti can select multiple lines, which the readme compares to running fzf with the multi-selection flag. Both take your slice, a function that returns the text to match against for each index, and a list of options. The result is the index or indexes of what the user chose, plus an error, and the readme is clear that the execution result prints the selected item's indexes rather than the items themselves, which is what lets the caller go back to its own data.

The example in the readme shows a slice of track records and a matching function that returns the track name.

go
type Track struct {
    Name      string
    AlbumName string
    Artist    string
}

The call itself passes the slice, the matcher and a preview window option, and the printed result is the selection.

go
    fmt.Printf("selected: %v\n", idx)

Working in indexes rather than in copies of the data is the detail that makes the library pleasant to use with a struct type. Your matcher function is the only place you say what the searchable text is, so the library never needs to know how to render your type, and you can search on one field while showing another.

The preview window is the answer to rows that share the same text

The readme spends most of its motivation section on one problem, and the preview window feature is the answer to it. The first issue is that it is hard to distinguish between two or more entities that have the same text. The example given is from another project of the same author, where conflicting tracks can share a track name but have different artists, so you have to display the artist name alongside each track name. But that only pushes the problem one level down, since you can still have the same track name and the same artist on different albums. The readme says this problem is difficult to solve, and the reason is structural: pipes and filters are row-based mechanisms, and there are no ways to hold references that point at list entities.

go
        fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
            if i == -1 {
                return ""
            }
            return fmt.Sprintf("Track: %s (%s)\nAlbum: %s",
                tracks[i].Name,
                tracks[i].Artist,
                tracks[i].AlbumName)
        }))

The preview window callback receives the index and the width and height available, and returns the text to show. Because it receives the index rather than the row text, it can reach back into your own slice and render whatever distinguishes the entries, which is exactly what the row-based pipe model cannot do. The i == -1 guard is the convention for when there is no current item.

Preselecting works the same way in both modes

The WithPreselected option takes a predicate over the index and works in both Find and FindMulti, with different behaviour in each. In single selection mode the cursor is positioned on the first item that matches the predicate. In multi selection mode all items that match the predicate are selected initially.

go
// Single selection mode
// The cursor will be positioned on the first item that matches the predicate
idx, err := fuzzyfinder.Find(
    tracks,
    func(i int) string {
        return tracks[i].Name
    },
    fuzzyfinder.WithPreselected(func(i int) bool {
        return tracks[i].Name == "bar"
    }),
)

The multi version selects by a different field, in this case the artist.

go
// Multi selection mode
// All items that match the predicate will be selected initially
idxs, err := fuzzyfinder.FindMulti(
    tracks,
    func(i int) string {
        return tracks[i].Name
    },
    fuzzyfinder.WithPreselected(func(i int) bool {
        return tracks[i].Artist == "artist2"
    }),
)

The predicate takes an index rather than an item, which keeps the same convention as the matcher and the preview callback. All three callbacks receive an index into your slice, so a program using this library never has to give up its own types.

Removing the external dependency is the second half of the motivation

The second issue in the motivation section is about deployment rather than about display. The readme describes the case of incorporating a fuzzy finder as one of the main features in a command line tool, with enhancd and itunes-cli given as examples. Usually these tools require that a fuzzy finder has been installed as a precondition, and on top of that, to deal with the fuzzy finder, an environment variable configuration such as export TOOL_NAME_FINDER=fzf is required. The readme calls that a bother and complicated.

So there are two distinct costs being removed, and they are worth separating. The first is the installation precondition, where the user has to install a second tool before yours will work. The second is the configuration, where your tool has to pick a name and your user has to set an environment variable to tell it which finder to use. An embedded library removes both, because the finder ships inside your binary and there is nothing to point at.

The readme also credits the external search tools directly rather than pretending they are not the inspiration, and it links two write-ups about fuzzy finders as Go libraries, one in English and one in Japanese.

The dependency list explains the terminal handling

The go.mod file is a good map of how the library works, because the dependencies are few and each one is there for a reason. The main one is github.com/gdamore/tcell/v2 at version 2.6.0, which is the terminal cell library the user interface is drawn with. Alongside it sit github.com/ktr0731/go-ansisgr for colour handling, github.com/mattn/go-runewidth for calculating how wide a rune is, github.com/nsf/termbox-go at version 1.1.2, and github.com/pkg/errors for error wrapping. The module declares Go 1.24 with the toolchain pinned to go1.24.1.

The repository layout follows that structure. The main implementation is fuzzyfinder.go, with option.go holding the options, tcell.go the terminal handling and mock.go a mock for testing. The matching logic lives in its own matching directory and the scoring in a scoring directory, which is the separation that matters, since matching a query against a string and scoring the results are different concerns and are the two things you would most want to change. There are several test files, including fuzz_test.go and fuzzing_test.go, a helper_test.go, a testdata directory and an example directory containing a cli example, a hotreload example and the track example from the readme.

Where go-fuzzyfinder is the wrong choice

The first wrong case is a shell script or a Python tool, since this is a Go library and a Go program cannot call it without writing a Go program. The second is a tool whose users already have fzf installed and configured, because in that case the environment variable route the readme complains about is already in place and the effort of embedding a UI is wasted. The third is a non-interactive context, such as a script that needs to pick from a list without a human present, since a full-screen terminal interface is exactly the wrong shape for that. The fourth is a project that wants to reuse an existing fuzzy finder as a separate process and pipe its output, because the library's whole model is in-process callbacks over indexes and it offers no filter mode. The fifth is anyone expecting a stable API, since the release list shows v0.9.0 as the newest tag and the project has never left the zero major version, which in Go signals exactly that the interface may still move. The last is a team that wants to change the matching algorithm itself, since the matching and scoring packages are where that would happen and they are internal to the module rather than a documented extension point.

Editorial conclusion

Go authors building a command line tool whose main job is picking from a list, especially where the entries share names and something extra has to be shown to tell them apart, are the people go-fuzzyfinder is for. It is the wrong choice for a shell or Python tool, since it is a Go library, and it is the wrong choice for a non-interactive script where a full screen interface is the wrong shape. Check three things before you depend on it: that the interface stability suits you, since the newest release is v0.9.0 and the project has never left the zero major version, that your users do not already have a configured finder they prefer, because the readme's argument against requiring one is that it is an installation precondition and an environment variable, and how you want the preview window to behave when nothing is highlighted, since the example returns an empty string for the index of minus one. The licence is MIT, the module requires Go 1.24 with the toolchain pinned to go1.24.1, the terminal UI is drawn with tcell v2.6.0, and the last push was on 2026-10-20.

Frequently asked questions

How do I install go-fuzzyfinder?

With go get github.com/ktr0731/go-fuzzyfinder. The package is published on pkg.go.dev under that path, the licence is MIT, and the newest release is v0.9.0 from 2025-04-15.

What is the difference between Find and FindMulti?

Find selects a single item and FindMulti can select multiple lines, which the readme compares to running fzf with the multi-selection flag. Both take your slice, a function returning the searchable text for an index, and a list of options, and both return the indexes of the selection.

What does the preview window option do in go-fuzzyfinder?

The WithPreviewWindow option takes a callback that receives the index and the width and height available, and returns the text to display. Because it receives an index, it can render anything that distinguishes entries, which the readme describes as the answer to entities that share the same text.

How do I preselect items in go-fuzzyfinder?

With the WithPreselected option, which takes a predicate over the index and works in both modes. In single selection mode the cursor is positioned on the first item that matches, and in multi selection mode all matching items are selected initially.

Why would a tool embed go-fuzzyfinder instead of requiring fzf?

The readme says that tools incorporating a fuzzy finder usually require one to be installed as a precondition, and require an environment variable configuration such as export TOOL_NAME_FINDER=fzf, which it calls a bother and complicated. Embedding the finder removes both the installation step and the configuration.

Official sources

  1. Issues
  2. ktr0731/go-fuzzyfinder on GitHub
  3. License: MIT
  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/ktr0731-go-fuzzyfinder.svg)](https://hysenlabs.com/projects/ktr0731-go-fuzzyfinder)

Was this useful?