Many people store (and view) bookmarks in a DEVONthink database. Often these have the generic bookmark icon which makes it more difficult to visually identify the site they’re from. However, with a little automation, you can use the site’s favicon.
If you have existing bookmarks, you can use a batch process to select and update their thumbnails to a favicon.
Select your database in the Navigate sidebar. Then create a new local smart group via Data > New > Smart Group, with a criterion Kind is Bookmark.
Double-click the smart group in the item list to show all the matches.
Select one or more bookmarks. If there are hundreds of them (or more), it’s best to select smaller batches, e.g., 50 or so, as this process has to make a query for the favicon for each bookmark.
Choose Tools > Batch Process > Batch Process or press ⌃⌘B.
Press the plus (+) button to create a new configuration.
Set the action to: Apply Script > External > Add Favicons and press Apply.
If you’d like to use the favicon for future bookmarks, you can create a smart rule.
Select your database in the Navigate sidebar. At the bottom of the sidebar, press the plus (+) button and choose New Smart Rule.
The Search in popup will target the current database. You can change this to Databases but make sure you want this to affect all bookmarks added to all databases before you do.
Add criteria: Kind is Bookmark and Item does not contain Thumbnail.
Add the On Import event trigger.
Set the Apply Script > External > Add Favicons action and press OK.
Add a bookmark to the database and the smart rule will attempt to change the thumbnail to the site’s favicon. But note: Not every site will return its favicon so this isn’t a 100% solution.
I’ve been using DEVONthink since early 2020. The excellent Add Favicon script by @cgrunenberg was one of my most-used scripts. I unfortunately find this script fails to download favicons for most of my bookmarks. Having browsed all the forum posts here on favicons, I noticed this shortcoming was a common issue. So, despite having no AppleScript experience, with the onset of sophisticated generative AI I sought to upgrade the script using Claude. The resulting script includes multiple new sources, and has been tested with one hundred bookmarks with all one hundred bookmarks succeeding. And @jooz, YouTube favicons are confirmed to work.
I’m sharing this script in case anybody else finds it useful!
Kind regards ,
Matthew
Overview Of The Script
Claude’s explanation of the new script
The original script has a few key weaknesses:
Only tries 2 sources: the <link rel="icon"> tag and /favicon.ico — many sites use other paths
No fallback chain: if the first attempt yields a bad/empty result, it gives up
No Google Favicon API fallback: a highly reliable last resort that the script never uses
Feed URL handling is incomplete: only converts feed:// but misses other edge cases
Silent failures: the try block swallows all errors with no logging
Here’s what changed and why it’s more reliable:
6-stage fallback chain — the original tried 2 sources and gave up. This version works through six in sequence, stopping as soon as one succeeds:
<link rel="icon"> in page HTML — same as before, but now properly resolves protocol-relative (//cdn.example.com/icon.png) and root-relative (/static/favicon.ico) paths, which the original script would have passed verbatim and failed silently
/favicon.ico — now verified with a real HTTP status check before setting, so 404s don’t result in broken thumbnails
/favicon.png — many modern sites skip .ico entirely in favour of .png
/apple-touch-icon.png — high-res (typically 180×180), widely used, especially on Apple-centric sites
Google Favicon API — extremely reliable CDN; uses a content-length check (>100 bytes) since Google returns 200 even for unknowns
DuckDuckGo Favicon API — privacy-friendly fallback with its own large icon cache
Better URL handling — feed:// conversion was already there; the upgrade also ensures the base URL always has a trailing slash, preventing malformed paths like https://example.comfavicon.ico.
Failure summary — instead of silent errors, you get a notification when everything succeeds, or a dialog listing any records where all 6 stages failed, so you know exactly what to investigate.
View the script’s code
– Download Favicons (Upgraded)
– Original by Christian Grunenberg, Jun 02 2020.
– Upgraded with multi-stage fallback chain for higher reliability.
– Fallback order per record:
– 1. Parse / from the page HTML
– 2. Try /favicon.ico at the root of the domain
– 3. Try /favicon.png at the root of the domain
– 4. Try /apple-touch-icon.png (often high-res, widely supported)
– 5. Google Favicon API (https://www.google.com/s2/favicons?domain=…&sz=64)
– 6. DuckDuckGo Favicon API (https://icons.duckduckgo.com/ip3/….ico)
– Any step that returns a non-empty image sets the thumbnail and skips the rest.
on performSmartRule(theRecords)
tell application id “DNtp”
if (count of theRecords) is 0 then return
set failedRecords to {}
show progress indicator "Downloading Favicons…" steps (count of theRecords) with cancel button
repeat with theRecord in theRecords
step progress indicator ((name of theRecord) as string)
set didSet to false
try
-- ── Normalise the URL ──────────────────────────────────────────
set theURL to URL of theRecord
if theURL is missing value or theURL is "" then error "No URL"
-- Convert feed:// → https://
if theURL begins with "feed://" then
set theURL to "https" & (characters 5 thru -1 of theURL) as string
end if
-- Strip query strings and fragments for cleaner base URL parsing
-- (leave theURL intact for HTML download; build theBaseURL separately)
if theURL begins with "https://" or theURL begins with "http://" then
-- ── Build base URL (scheme + host + trailing slash) ───────
set slashes to 0
set theBaseURL to theURL
repeat with i from 1 to length of theURL
if character i of theURL is "/" then
set slashes to slashes + 1
if slashes > 2 then
set theBaseURL to (characters 1 thru i of theURL) as string
exit repeat
end if
end if
end repeat
-- Ensure trailing slash
if theBaseURL does not end with "/" then set theBaseURL to theBaseURL & "/"
-- For feed records, fetch the site root rather than the feed URL
set fetchURL to theURL
if type of theRecord is feed then set fetchURL to theBaseURL
-- ── Stage 1: Parse <link rel="icon"> from page HTML ───────
if not didSet then
try
set theHTML to download markup from fetchURL
set theFavicon to get favicon of theHTML
if theFavicon is not missing value and theFavicon is not "" then
-- Resolve relative paths
if theFavicon begins with "//" then
-- Protocol-relative
if theURL begins with "https" then
set theFavicon to "https:" & theFavicon
else
set theFavicon to "http:" & theFavicon
end if
else if theFavicon begins with "/" then
-- Root-relative: prepend scheme + host (strip trailing slash then re-add path)
set theHost to characters 1 thru -2 of theBaseURL as string -- remove trailing slash
set theFavicon to theHost & theFavicon
end if
set thumbnail of theRecord to theFavicon
set didSet to true
end if
end try
end if
-- ── Stage 2: /favicon.ico ─────────────────────────────────
if not didSet then
try
set candidateURL to theBaseURL & "favicon.ico"
set testResult to do shell script "curl -s -o /dev/null -w '%{http_code}' --max-time 5 --location " & quoted form of candidateURL
if testResult is "200" then
set thumbnail of theRecord to candidateURL
set didSet to true
end if
end try
end if
-- ── Stage 3: /favicon.png ─────────────────────────────────
if not didSet then
try
set candidateURL to theBaseURL & "favicon.png"
set testResult to do shell script "curl -s -o /dev/null -w '%{http_code}' --max-time 5 --location " & quoted form of candidateURL
if testResult is "200" then
set thumbnail of theRecord to candidateURL
set didSet to true
end if
end try
end if
-- ── Stage 4: /apple-touch-icon.png ────────────────────────
if not didSet then
try
set candidateURL to theBaseURL & "apple-touch-icon.png"
set testResult to do shell script "curl -s -o /dev/null -w '%{http_code}' --max-time 5 --location " & quoted form of candidateURL
if testResult is "200" then
set thumbnail of theRecord to candidateURL
set didSet to true
end if
end try
end if
-- ── Stage 5: Google Favicon API ───────────────────────────
if not didSet then
try
-- Extract bare domain (strip scheme and trailing slash)
set theDomain to theBaseURL
if theDomain begins with "https://" then set theDomain to (characters 9 thru -1 of theDomain) as string
if theDomain begins with "http://" then set theDomain to (characters 8 thru -1 of theDomain) as string
if theDomain ends with "/" then set theDomain to (characters 1 thru -2 of theDomain) as string
set googleURL to "https://www.google.com/s2/favicons?domain=" & theDomain & "&sz=64"
-- Google always returns 200 even for unknowns; check content length > 100 bytes
set contentLength to do shell script "curl -s -o /dev/null -w '%{size_download}' --max-time 5 " & quoted form of googleURL
if (contentLength as integer) > 100 then
set thumbnail of theRecord to googleURL
set didSet to true
end if
end try
end if
-- ── Stage 6: DuckDuckGo Favicon API ──────────────────────
if not didSet then
try
if theDomain is not "" then
set ddgURL to "https://icons.duckduckgo.com/ip3/" & theDomain & ".ico"
set contentLength to do shell script "curl -s -o /dev/null -w '%{size_download}' --max-time 5 --location " & quoted form of ddgURL
if (contentLength as integer) > 100 then
set thumbnail of theRecord to ddgURL
set didSet to true
end if
end if
end try
end if
-- ── Track failures for summary ────────────────────────────
if not didSet then
set end of failedRecords to (name of theRecord as string)
end if
end if -- http/https check
on error errMsg
-- Record had no URL or an unhandled error; add to failed list
try
set end of failedRecords to (name of theRecord as string)
end try
end try
if cancelled progress then exit repeat
end repeat
hide progress indicator
-- ── Summary notification ──────────────────────────────────────────────
set totalCount to count of theRecords
set failCount to count of failedRecords
set successCount to totalCount - failCount
if failCount is 0 then
display notification "✓ Favicons set for all " & successCount & " record(s)." with title "Download Favicons"
else
set failList to ""
repeat with n in failedRecords
set failList to failList & "• " & n & return
end repeat
display dialog "Favicons downloaded: " & successCount & " of " & totalCount & return & return & "Could not find favicons for:" & return & failList buttons {"OK"} default button "OK" with title "Download Favicons"
end if
end tell
end performSmartRule
How To Install
Installation instructions
Replace the existing script at: ~/Library/Application Scripts/com.devon-technologies.think4/Menu/. You can safely delete the old script, or preserve it if you want.