I’ve been using Zen Browser for over 6 months now. And I think it’s time I switch back to Brave. The initial motivation to use Zen Browser was because https://lobste.rs/ boycotted Brave and blocked access from Brave. Now that the block seems to have been removed, I’m eager to get back to Brave. Vertical tabs feature is also supported in Brave as well as tab group, like space in Zen Browser, so I don’t think there’s really a reason for me to continue using Zen Browser.
Some dislikes and pain points I encountered using Zen Browser:
- Hassle to set up a bunch of ad blockers that are built-in in Brave.
- Chrome extensions are more widely available compared to Firefox counterpart.
- Occasional UI breaking changes.
I wasn’t able to find a feature to export bookmarks from Zen Browser so querying the local SQLite database was the only option. I’ll share a few useful SQL snippets.
Connecting to places.sqlite
Firefox, or Zen Browser, keeps its bookmarks in places.sqlite. On macOS, the
path to this file is:
ls ~/Library/Application\ Support/Zen/Profiles/*/places.sqlite
You can either use sqlite3 or a SQL client like DBeaver to connect to the
database using the path.
Getting all bookmarks with folder names, titles and URLs
Run this query to select all bookmarks:
SELECT
b.id,
parent.title AS folder_name,
b.title AS bookmark_title,
p.url
FROM moz_bookmarks b
JOIN moz_places p ON b.fk = p.id
LEFT JOIN moz_bookmarks parent ON b.parent = parent.id
WHERE b.type = 1
ORDER BY folder_name, bookmark_title
Result:
| id | folder_name | bookmark_title | url |
|----+-------------+----------------+----------------------------|
| 18 | Blogs | Lobsters | https://lobste.rs/ |
| 20 | Blogs | Alex MacCaw | https://alexmaccaw.com/ |
| 26 | Blogs | Paul Graham | http://www.paulgraham.com/ |
- NOTE:
moz_bookmarks.type = 1are bookmarksmoz_bookmarks.type = 2are folders
Finding any duplicates (by URL)
There could be some duplicates that might be worth looking into:
SELECT
p.url,
COUNT(*) AS count
FROM moz_bookmarks b
JOIN moz_places p ON b.fk = p.id
WHERE b.type = 1
GROUP BY p.url
HAVING COUNT(*) > 1
ORDER BY count DESC;
Exporting as importable HTML
Save this query as a file export-bookmarks.sql:
WITH RECURSIVE walk AS (
SELECT
b.id,
b.title,
b.parent,
b.type,
p.url,
b.position,
1 AS level,
printf('%06d', b.position) AS path,
'enter' AS phase
FROM moz_bookmarks b
LEFT JOIN moz_places p ON b.fk = p.id
WHERE b.parent IN (2, 3, 4)
UNION ALL
SELECT
b.id,
b.title,
b.parent,
b.type,
p.url,
b.position,
w.level + 1,
w.path || '.' || printf('%06d', b.position),
'enter'
FROM moz_bookmarks b
LEFT JOIN moz_places p ON b.fk = p.id
JOIN walk w ON b.parent = w.id
WHERE w.phase = 'enter'
UNION ALL
SELECT
id,
title,
parent,
type,
url,
position,
level,
path || '.zzzzzz',
'exit'
FROM walk
WHERE phase = 'enter' AND type = 2
)
SELECT
CASE
WHEN phase = 'enter' AND type = 2 THEN
printf(
'%s<DT><H3>%s</H3>%s%s<DL><p>',
printf('%*s', level * 2, ''),
COALESCE(title, ''),
char(10),
printf('%*s', level * 2, '')
)
WHEN phase = 'enter' AND type = 1 THEN
printf(
'%s<DT><A HREF="%s">%s</A>',
printf('%*s', level * 2, ''),
url,
COALESCE(title, '')
)
WHEN phase = 'exit' THEN
printf(
'%s</DL><p>',
printf('%*s', (level - 1) * 2, '')
)
END AS html
FROM walk
WHERE type IN (1, 2)
ORDER BY path;
Run the query and generate an HTML file for import:
sqlite3 -batch -noheader \
"$HOME/Library/Application Support/Zen/Profiles/f16znh1m.Default (release)/places.sqlite" \
< export-bookmarks.sql \
| sed '1i\
<!DOCTYPE NETSCAPE-Bookmark-file-1>\
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">\
<TITLE>Bookmarks</TITLE>\
<H1>Bookmarks</H1>\
<DL><p>' \
> ~/Desktop/bookmarks.html
echo "</DL><p>" >> ~/Desktop/bookmarks.html