How I track books and reading with Obsidian

📘 Books

I've been using Obsidian for all my notes for some time now, and I've fairly recently started tracking my book reading in Obsidian as well.

I never really got into Goodreads, and I used BookWyrm for some time, but I don't need the social features of those types of sites. However, if you are looking for a Goodreads replacement with social functionality, then either Bookwyrm or Hardcover are excellent choices. If you just want a personal reading log, Obsidian will work great.

I'm definitely not the first to figure out this kind of system and I've built it from many other sources, but hopefully it's helpful for me to compile my method here.

My system is somewhat unique in that it allows for rereads.

Required Community plugins

Install the above plugins, then let's do some setup.

Doing some setup

Configure the Book Search plugin:

First, set a folder where all your book notes will live. Second, create a new note to serve as a template for when you are adding book notes to your vault. Here is my template:

---
title: ""
author: 
series: 
seriesnumber: 
rating: 
readdates:
- started: 
  finished: 
shelf: toread
list: 
publisher: 
publish: 
pages: 
isbn:  
cover: <%=book.coverUrl ? `https://books.google.com/books/publisher/content/images/frontcover/${[...book.coverUrl.split("&")[0].matchAll(/id.?(.*)/g)][0][1]}?fife=w600-h900&source=gbs_api` : ''%>
dateCreated: 
---

![cover|150]()

## 

### Description


A few notes on the template:

readdates:
- started: 2023-08-10
  finished: 2023-08-15
- started: 2023-09-05
  finished: 2023-09-12

... and so on. Unfortunately this doesn't play well with the Obsidian Properties UI that was released recently in 1.4, so you might want to switch to source view while editing these dates.

Configure Dataview

Make sure that DataviewJS queries are enabled.

Start using it: Add a few books

Visualize your books

OK, here's the dataview dump! I use separate notes in my vault for each of these queries. For each query I'll indicate whether it's a dataview or dataviewjs code block.

Bookshelf: to read dataview

TABLE WITHOUT ID
"![cover|80](" + cover + ")" AS "Cover",
title,
author, series
FROM "books"
WHERE shelf="toread"
SORT title ASC

Bookshelf: currently reading dataview

TABLE WITHOUT ID
"![cover|80](" + cover + ")" AS "Cover",
title,
author, series
FROM "books"
WHERE shelf="reading"
SORT started ASC

Bookshelf: stopped reading dataview

TABLE WITHOUT ID
"![cover|80](" + cover + ")" AS "Cover",
title,
author, series
FROM "books"
WHERE shelf="stopped"
SORT title ASC

When stopping a book, don't put a date in "finished" because it'll make your book display in this next query:

Bookshelf: Read in 2023 dataviewjs

function renderReadDates(readdates) {
	let str = '';
	for(var i=0; i<readdates.length; i++) {
	    if(new Date(readdates[i].finished).getFullYear() === 2023) {
			str += '* ';
			str += new Date(readdates[i].started).toLocaleDateString('en-us', { month:"long", day:"numeric", year: "numeric"});
			str += ' - ';
			str += new Date(readdates[i].finished).toLocaleDateString('en-us', { month:"long", day:"numeric", year: "numeric"});
			str += "\n";
		}
	}
	return str;
}

dv.table(
    ["cover", "title", "author", "series", "read", "rating"],
	dv.pages('"books"')
	    .filter(b => {
	        let ret = false;
	        if(b.readdates) {
		        b.readdates.map(r => {
			        if(r.finished && r.finished.toString().includes("2023")) {
			            ret = true;
			        }
			        return r;
		        });
	        }
	        return ret;
	    })
		.sort(b => b.readdates[b.readdates.length-1].finished)
	    .map(b => [
		    "![" + b.cover + "|80](" + b.cover + ")",
		    b.title,
		    b.author,
		    b.series,
		    renderReadDates(b.readdates),
		    "⭐".repeat(b.rating)
		])
)

OK, here's where things get interesting. We're using DataviewJS to create a fancier query.

See Books Read in 2023.

List the 5-star books from 2023 dataview

LIST WITHOUT ID
title + " (" + author + ")"
FROM "books"
WHERE rating=5 AND contains(string(readdates.finished), "2023")

Show how many books you have read in 2023 dataview

LIST WITHOUT ID
"So far in 2023, I've read " + length(rows) + " books."
FROM "books"
WHERE contains(string(readdates.finished), "2023")
GROUP BY dateformat(finished, "yyyy")

Display covers of books grouped by series (excludes books not in a series) dataviewjs

let groups = dv.pages('"books"')
    .filter(b => b.series)
	.groupBy(b => b.series)
	.sort(b => b.series);

for(let group of groups) {
    dv.header(3, group.key)
    let rows = group.rows.sort(b => b.seriesnumber, 'asc')
        .map(b => `<img src="${b.cover}" style="height: 160px; margin-right: 6px; margin-bottom: 6px; border-radius: 4px;" />`)
        .join("");
    dv.el('div', rows);
}

From all these examples, I hope you can build other queries you might want.

To extend this system, you can use the Air Quotes plugin to take notes and quote from your book. You'll need an ebook copy of your book, but this plugin makes it easy to find and insert quotes from your book.