The following applies to the Node.js edition of TiddlyWiki. Tested on TUXEDO OS (Ubuntu based OS).
I managed to get links to my files inside TiddlyWiki to be opened by using the following procedure.
Highlights of requirements:
- A script
- A .desktop entry
- A file association
- A macro
It appears to be a lot, but once you set it up it doesn’t require any maintenance!
Provenance
This text documents the creation of a custom twfile:// URL scheme handler for TiddlyWiki asset links under Linux (KDE Plasma). The goal was to allow links like twfile:///home/user/Downloads/example.txt to open directly in the appropriate application.
Bash Script
The handler script lives in ~/scripts/tiddlywiki_file_opener.sh and is marked executable (chmod +x). It strips the scheme, decodes URL-encoded characters, and opens files or directories with the default system handler.
Script: See below.
File Association
-
In KDE System Settings → Applications → File Associations, add a new type: * Type: x-scheme-handler/twfile
Name: TiddlyWiki File Opener (friendly label) -
Under the Applications tab, set TiddlyWiki File Opener as the preferred application.
-
Refresh caches if needed:
update-desktop-database ~/.local/share/applications
kbuildsycoca5 --noincremental
Verification
Check the association:
gio mime x-scheme-handler/twfile
Expected output:
Default application for “x-scheme-handler/twfile”: tiddlywiki-file-opener.desktop
Registered applications:
tiddlywiki-file-opener.desktop
Recommended applications:
tiddlywiki-file-opener.desktop
Testing
xdg-open "twfile:///home/user/Downloads/icedrive.txt"
- Files open in their default app.
- Directories open in the file manager.
- Invalid paths trigger a notification and fall back to ~/Documents.
Notes
This setup is cross-desktop (KDE, GNOME, etc.) since it uses the XDG MIME system.
The tiddlywiki_file_opener.sh is:
#!/bin/bash
# twfile:// protocol handler (robust normalization)
URL="$1"
LOGFILE="/tmp/twfile.log"
SAFE_FALLBACK="$HOME/Documents"
echo "Incoming URL: $URL" >> "$LOGFILE"
# 1) Strip scheme (handles twfile:/ and twfile://)
SCHEME_STRIPPED="${URL#twfile:}"
# 2) Parse optional //host/path form
# - If host is empty or 'localhost', treat as local.
# - If host is non-empty (e.g., 'home'), convert to '/home/...'.
if [[ "$SCHEME_STRIPPED" =~ ^//([^/]*)(/.*)?$ ]]; then
HOST="${BASH_REMATCH[1]}"
PATH_PART="${BASH_REMATCH[2]}"
# Empty PATH_PART means root
[[ -z "$PATH_PART" ]] && PATH_PART="/"
if [[ -z "$HOST" || "$HOST" == "localhost" ]]; then
FILE_PATH="$PATH_PART"
else
FILE_PATH="/$HOST$PATH_PART"
fi
else
# No host form; just use the remainder as path
FILE_PATH="$SCHEME_STRIPPED"
fi
# 3) Ensure leading slash for local absolute paths
[[ "$FILE_PATH" != /* ]] && FILE_PATH="/$FILE_PATH"
# 4) Collapse multiple slashes (///home -> /home)
FILE_PATH="$(echo "$FILE_PATH" | sed -E 's|/{2,}|/|g')"
# 5) Percent-decode (%20 -> space, etc.)
FILE_PATH="$(printf '%b' "${FILE_PATH//%/\\x}")"
echo "Normalized path: $FILE_PATH" >> "$LOGFILE"
# 6) Open directory or file; fallback if missing
if [ -d "$FILE_PATH" ]; then
xdg-open "$FILE_PATH"
echo "Opened directory: $FILE_PATH" >> "$LOGFILE"
elif [ -f "$FILE_PATH" ]; then
xdg-open "$FILE_PATH"
echo "Opened file: $FILE_PATH" >> "$LOGFILE"
else
notify-send "TiddlyWiki File Opener" \
"Invalid path: $FILE_PATH — opening fallback: $SAFE_FALLBACK"
echo "Invalid path: $FILE_PATH — fallback to $SAFE_FALLBACK" >> "$LOGFILE"
xdg-open "$SAFE_FALLBACK"
fi
Global macro
Steps
- Create the macro tiddler
- Title:
$:/macros/twfile - Type:
text/vnd.tiddlywiki - Tag:
$:/tags/Macro
- Macro definition
\define twfile(path, label:"$path$")
<html><a href="twfile:///$path$">$label$</a></html>
\end
Important: Tag it $:/tags/Macro
- Usage
Call the macro in any other tiddler:
Example:
<<twfile "/home/user/Downloads/example.txt" "example.txt">>
If you omit the label, it defaults to the path:
<<twfile "/home/example/Downloads/example.txt">>
- Behaviour
- Renders as a clickable link labelled example.txt.
- Points to
twfile:///home/user/Downloads/example.txt, which is handled by the system protocol handler.
Lessons Learned
- Macros defined with
\defineonly become global if the tiddler is tagged$:/tags/Macro. - The defining tiddler won’t render its own macro; calls must be in other tiddlers.
- Wrapping the
<a>in<html>…</html>ensures the parser doesn’t strip the custom scheme.

