Open-source project
emacs-elfeed/elfeed avatar
emacs-elfeed/elfeed

Elfeed keeps your feed database in a format it promises never to change

An Emacs web feeds client

1,796 stars145 forksEmacs LispUnlicense

At a glance

What is it?
Elfeed is an Emacs feed reader for Atom, RSS and JSON Feed that requires Emacs 29.1, installs from MELPA or NonGNU ELPA, and offers a search buffer with a query language over tags, dates and regular expressions. Its distinguishing commitments are a stable database format and curl-based fetching that is much faster than Emacs' own url-retrieve.
Who is it for?
Adopt Elfeed if you already live in Emacs and want feeds as entries in an ordinary buffer you can tag, filter and process with Elisp, and if you can install curl so updates are quick. Do not adopt it if you want a graphical feed reader, if your Emacs is older than 29.1, or if you need a mobile client, since the database file is the interchange format and the README says nothing about serving it to anything else.
Can I use it commercially?
Yes. Unlicense 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 13 days ago.
What is it written in?
Mainly Emacs Lisp, according to GitHub's language statistics.

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

Editorial analysis

Two requirements, and curl is the one people miss

Elfeed requires Emacs 29.1. That is a hard floor and it filters out anyone on an older Emacs without a package build.

The second requirement is softer but consequential. The README strongly recommends having curl installed, either in your PATH or configured through `elfeed-curl-program-name`. Elfeed prefers curl to Emacs' own URL-fetching mechanism, `url-retrieve`, for two reasons.

The first is Windows, where the README says `url-retrieve` is broken. That is not a preference, it is the only way the package works there.

The second is speed, and the phrasing is direct: updates using curl are significantly faster than the built-in method, both for you and the feed hosts. The difference between a fetcher that opens a process per request and one that does not can turn an update into a background job or an interruption.

If curl causes problems, `elfeed-use-curl` can be set to nil to fall back to the built-in mechanism. That is a documented escape hatch rather than a warning.

Installation is by `M-x package-install` from ELPA or MELPA, with NonGNU ELPA and NonGNU-devel ELPA among the listed archives. The README suggests binding the main entry point globally:

elisp
(keymap-global-set "C-x w" #'elfeed)

which makes the whole reader one keystroke from anywhere in Emacs.

The search buffer is the whole interface

Running `elfeed` opens a buffer named `*elfeed-search*` listing feed items, and that buffer is where everything happens.

The listing keys are worth holding in mind as two groups. `g` refreshes the view of the listing, `G` is different and fetches updates from the servers. That distinction is easy to get wrong once. Then filtering: `s` updates the search filter, `S` updates it statically, `c` clears it, and `@` adds the current date to the filter. `~` restricts to the current feed only and `=` excludes the current feed. Sorting is `o` for order by date or feed and `O` to reverse it.

Entry-level bindings follow the same split between viewing and marking. `RET` views the entry in a buffer, `b` opens it in the browser and `B` in the secondary browser, `w` copies the URL to the clipboard. Read state is `r` to tag as read and `u` as unread, each with a `C-u` prefix to apply to everything. Tags are added with `+` and removed with `-`, and there are two candidate commands, `m` to mark and `M` to unmark, the latter with `C-u M` for all.

There is also editing of titles, `t` for an entry and `T` for the feed itself, which is unusual for a feed reader and reflects the notmuch inspiration the README names for the UI.

Selection follows Emacs conventions: point over an entry selects it, and an active region selects several. Everything above then applies to the selection.

The filter language is where the power and the speed are

Filters are set with `elfeed-search-set-filter`, bound to `s`, and cleared with `elfeed-search-clear-filter`. Each leading character gives a component a different meaning.

`+` and `-` are tag requirements. `+` means the tag must be present, `-` means it must not be.

`@` introduces an age or a date range, and entries older than the age are filtered out. An age is a relative time expression or an absolute date, written in plain English with dashes instead of spaces: `@2-years-old`, `@3-days-ago`, `@2019-06-24`. The dashes and suffixes like `-old` are ignored, so the shorter `@2years` works too. A range is two ages separated by `--`, such as `@2019-06-20--2019-06-24` or `@5-days-ago--1-day-ago`, where an entry must be newer than the first and older than the second.

There is a performance point buried in that paragraph and it matters more than the syntax. The database is date-oriented, so filters that include an age restriction are significantly more efficient. A tag filter scans everything; a recent-days filter uses the index.

`!` is an inverse regular expression, so anything matching it is filtered out, and the expression starts after the character.

`#` limits the number of entries displayed, for example `#20`.

`~` matches against the entry's feed by title or URL, which is how `~` and `=` in the default view isolate a single feed.

You can combine them in one string, and they do compose: a common filter is a tag requirement plus a date range plus a count limit.

Tags are arbitrary symbols, with autotagging attached to the feed list

Tags in Elfeed are arbitrary symbols attached to an entry. Only one is special by default: `unread`, and unread entries appear in bold. Everything else is yours to define, which is what makes tag-based filtering workable for a reading system rather than a toy.

Autotagging is the feature that keeps the tag list from becoming manual work. `elfeed-feeds` is normally a list of strings, but an item can also be a list, and the extra elements become autotags applied to every entry discovered in that feed:

elisp
(setq elfeed-feeds
      '(("https://nullprogram.com/feed/" blog emacs)
        "https://sachachua.com/blog/category/emacs-news/feed/"
        ("https://nedroid.com/feed/" webcomic)))

The second entry is a plain string and is marked in the source as having no autotagging. So a feed tagged `blog` and `emacs` contributes entries already carrying both symbols, and `+blog +emacs` becomes a filter you never have to maintain by hand.

That combination, autotags plus a tag filter language, is the design that lets Elfeed substitute for a reader with folders. You do not subscribe into folders; you tag at the source and filter at read time.

There is a CSV module too. `elfeed-csv.el` is listed among the library files in the Makefile, so exporting a filtered view is part of the package rather than something users have written themselves.

Where the data lives, and how it gets there

Feeds are added to the `elfeed-feeds` list, and the first update is what populates the database. Until that happens, the search buffer is empty.

The README says the database format is stable and is never expected to change. That is an unusual and strong promise from a package, and it is the reason to consider the reader at all: your read state, tags and metadata sit in a file whose format will not move under you.

OPML import is handled by `M-x elfeed-load-opml`, which fills `elfeed-feeds` from an OPML file. Called interactively it saves the feed list into your customisation file, so the operation is one-time rather than something you repeat at every startup.

The initial update is slow when you have many feeds, because a large amount of information is written to the database at once. Later updates only write new or changed data. If updating slows Emacs down too much, the number of concurrent fetches can be reduced with `elfeed-set-max-connections`. If you get repeated `Queue timeout exceeded` errors, raise the fetch timeout with `elfeed-set-timeout` or by setting the underlying variable:

elisp
(setf url-queue-timeout 30)

The module split follows the concerns: `elfeed-db.el` for storage, `elfeed-curl.el` for fetching, `elfeed-search.el` for the buffer and filter language, `elfeed-show.el` for entry display, `elfeed-tree.el` for the feed tree, `elfeed-link.el` for opening links, `elfeed-lib.el` for shared functions and `elfeed-log.el` for logging.

There is also `xml-query.el` in the tree, which tells you the package does its own XML handling rather than depending on an external parser.

Unlicensed, tested in batch, and last tagged in 2024

The licence is the Unlicense, matching the UNLICENSE file at the repository root. That places the code in the public domain: no attribution required, no copyleft, no patent grant and no warranty. For a package you might read, copy a function out of, or bundle, that removes a question entirely. It also means there is no obligation on anyone maintaining it to publish changes, which is worth weighing against the stability promise.

The test setup is batch-mode Emacs. The Makefile runs `emacs -batch -Q -L. -L tests` and executes `ert-run-tests-batch-and-exit` over six test files covering the database, the library, the main entry points, the search buffer, curl and xml-query.

There is also a `virtual` target that builds the package and then runs it against a temporary HOME, copying the existing index out of `~/.elfeed` first and cleaning up with a trap. That is a way to try changes without touching your real database, and the target accepts an `ARGS` variable to pass Emacs arguments through.

`Makefile.deps` is generated rather than written by hand: the `deps` target greps every source file for `(require 'elfeed` forms and rewrites them into a dependency list that the Makefile includes. Automatic dependency tracking for a package with this many inter-file references saves a class of bug that only appears when one file changes.

Release history is where caution is warranted. The newest tag is 3.4.2 from 2024-12-02, and the two before it are 3.4.1 from 2021-02-26 and 3.4.0 from 2021-01-31. So there is a long gap between 3.4.0 and 3.4.1, and the latest listed release is 3.4.2 from 2024-12-02. The repository is not archived and the last push was on 2026-10-10, so development continues, and MELPA Snapshots is listed as an archive for people who want the unreleased version. Read that combination as: stable tagged releases are infrequent, current development happens between them, and the Emacs 29.1 floor suggests the package is being kept current for a newer Emacs than the one 3.4.2 shipped against.

Editorial conclusion

Adopt Elfeed if you already live in Emacs and want feeds as entries in an ordinary buffer you can tag, filter and process with Elisp, and if you can install curl so updates are quick. Do not adopt it if you want a graphical feed reader, if your Emacs is older than 29.1, or if you need a mobile client, since the database file is the interchange format and the README says nothing about serving it to anything else. Verify first that your Emacs version meets the floor, decide whether to rely on the ELPA archive or MELPA snapshots before adding it to your init file, and test one real feed before migrating an existing subscription list.

Frequently asked questions

What Emacs version does Elfeed require?

Elfeed requires Emacs 29.1. It is available for download from ELPA or MELPA, with NonGNU ELPA and NonGNU-devel ELPA among the listed archives.

Why does Elfeed need curl?

Elfeed prefers curl over Emacs' own url-retrieve because url-retrieve is broken on Windows, and because updates using curl are significantly faster for you and for feed hosts. You can disable it by setting elfeed-use-curl to nil, or point at a specific binary with elfeed-curl-program-name.

How do I add feeds to Elfeed?

Add them to the elfeed-feeds list and run M-x elfeed-update, or press G in the Elfeed buffer. You can also run M-x elfeed-load-opml to import from an OPML file, which interactively saves the resulting feed list to your customisation file.

How does Elfeed filter entries by date?

Use a component beginning with @ in the filter. An age can be relative like @2-years-old or @3-days-ago, or absolute like @2019-06-24, with dashes instead of spaces. A range such as @2019-06-20--2019-06-24 requires the entry to fall between the two. Filters with an age restriction are significantly more efficient because the database is date-oriented.

Is the Elfeed database format stable?

The README states that the database format is stable and is never expected to change.

What license is Elfeed under?

The Unlicense, with an UNLICENSE file at the repository root. The package places its code in the public domain with no attribution requirement.

Official sources

  1. emacs-elfeed/elfeed on GitHub
  2. License: Unlicense
  3. Project website
  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/emacs-elfeed-elfeed.svg)](https://hysenlabs.com/projects/emacs-elfeed-elfeed)

Was this useful?