Paste Clipboard Images into Markdown (Zed + Hyprland)
Zed editor on Wayland/Hyprland does not natively support pasting images into Markdown files (zed-industries/zed#18112). This workaround uses a shell script and a Hyprland keybinding to:
- Save the clipboard image to an
assets/directory next to the current Markdown file - Insert the Markdown image reference at the cursor position
Prerequisites
| Package | Purpose |
|---|---|
wl-clipboard | Read clipboard contents (wl-paste, wl-copy) |
wtype | Simulate keyboard input on Wayland |
hyprctl | Query active window info from Hyprland |
python3 | Parse JSON output |
notify-send | Desktop notifications for feedback |
Install
On Arch Linux (Omarchy):
sudo pacman -S wl-clipboard wtype libnotifySetup
1. Create the script
Save to ~/.local/bin/md-paste-image:
#!/bin/bash
# md-paste-image: Save clipboard image to assets/ and type Markdown reference
# Designed for use with Zed editor on Hyprland/Wayland
set -euo pipefail
# Check if clipboard has an image
MIME=$(wl-paste --list-types 2>/dev/null | grep -E '^image/' | head -1)
if [[ -z "$MIME" ]]; then
notify-send -u normal -t 3000 "Paste Image" "No image in clipboard"
exit 1
fi
# Determine file extension from mime type
case "$MIME" in
image/png) EXT="png" ;;
image/jpeg) EXT="jpg" ;;
image/webp) EXT="webp" ;;
image/gif) EXT="gif" ;;
image/bmp) EXT="bmp" ;;
*) EXT="png" ;;
esac
# Get active window info from Hyprland
WINDOW_JSON=$(hyprctl activewindow -j 2>/dev/null)
WINDOW_CLASS=$(echo "$WINDOW_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('class',''))" 2>/dev/null)
WINDOW_TITLE=$(echo "$WINDOW_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('title',''))" 2>/dev/null)
if [[ "$WINDOW_CLASS" != *"zed"* && "$WINDOW_CLASS" != *"Zed"* ]]; then
notify-send -u normal -t 3000 "Paste Image" "Not in Zed editor (window: $WINDOW_CLASS)"
exit 1
fi
# Parse Zed title: "project — file.md"
PROJECT_NAME=$(echo "$WINDOW_TITLE" | sed 's/ — .*//')
FILE_PART=$(echo "$WINDOW_TITLE" | sed 's/^[^—]*— //')
if [[ -z "$PROJECT_NAME" || -z "$FILE_PART" ]]; then
notify-send -u normal -t 3000 "Paste Image" "Could not parse Zed window title"
exit 1
fi
# Find the project directory
PROJECT_DIR=""
for base in "$HOME/Development" "$HOME/Projects" "$HOME/Code" "$HOME/Documents" "$HOME"; do
if [[ -d "$base" ]]; then
FOUND=$(find "$base" -maxdepth 5 -type d -name "$PROJECT_NAME" 2>/dev/null | head -1)
if [[ -n "$FOUND" ]]; then
PROJECT_DIR="$FOUND"
break
fi
fi
done
if [[ -z "$PROJECT_DIR" ]]; then
notify-send -u normal -t 3000 "Paste Image" "Could not find project: $PROJECT_NAME"
exit 1
fi
# Resolve the markdown file path - prefer shortest path
MD_FILE="$PROJECT_DIR/$FILE_PART"
if [[ ! -f "$MD_FILE" ]]; then
MD_FILE=$(find "$PROJECT_DIR" -type f -name "$(basename "$FILE_PART")" 2>/dev/null | awk '{ print length, $0 }' | sort -n | head -1 | cut -d' ' -f2-)
fi
if [[ -z "$MD_FILE" || ! -f "$MD_FILE" ]]; then
notify-send -u normal -t 3000 "Paste Image" "Could not find file: $FILE_PART"
exit 1
fi
# Create assets directory next to the markdown file
MD_DIR=$(dirname "$MD_FILE")
ASSETS_DIR="$MD_DIR/assets"
mkdir -p "$ASSETS_DIR"
# Generate filename with timestamp
TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
IMG_NAME="${TIMESTAMP}.${EXT}"
IMG_PATH="$ASSETS_DIR/$IMG_NAME"
# Save clipboard image to file
wl-paste --type "$MIME" > "$IMG_PATH"
if [[ ! -s "$IMG_PATH" ]]; then
rm -f "$IMG_PATH"
notify-send -u normal -t 3000 "Paste Image" "Failed to save image"
exit 1
fi
# Put markdown reference into clipboard, then simulate paste
MD_REF=""
echo -n "$MD_REF" | wl-copy
# Wait for modifier keys to be released
sleep 0.5
wtype -M ctrl -k v -m ctrl
notify-send -u low -t 2000 "Paste Image" "Saved: assets/${IMG_NAME}"Make it executable:
chmod +x ~/.local/bin/md-paste-image2. Add the Hyprland keybinding
Add to ~/.config/hypr/bindings.conf:
# Paste clipboard image into Markdown (saves to assets/ and types reference)
bindd = SHIFT ALT, V, Paste image to Markdown, exec, /home/<user>/.local/bin/md-paste-imageFull path required
Hyprland's exec does not include ~/.local/bin in its PATH. You must use the full absolute path to the script.
Hyprland auto-reloads on config save -- no restart needed.
Usage
- Copy an image to clipboard (screenshot, browser image, etc.)
- Open a Markdown file in Zed and place your cursor where you want the image
- Press Shift + Alt + V
The script will:
- Create an
assets/directory next to the Markdown file (if it doesn't exist) - Save the image as
assets/YYYY-MM-DD_HHMMSS.png - Insert
at the cursor position - Show a desktop notification confirming the save
How it works
Shift+Alt+V pressed
-> Hyprland exec runs md-paste-image
-> wl-paste --list-types checks for image in clipboard
-> hyprctl activewindow -j gets Zed window title
-> Title parsed: "project-name — filename.md"
-> find locates the project directory and file
-> mkdir -p assets/ next to the markdown file
-> wl-paste --type image/png saves the image
-> wl-copy puts the markdown reference in clipboard
-> sleep 0.5 (wait for Shift+Alt to release)
-> wtype simulates Ctrl+V to paste the reference
-> notify-send shows confirmationSupported image formats
The script detects the clipboard MIME type and saves with the correct extension:
| MIME Type | Extension |
|---|---|
image/png | .png |
image/jpeg | .jpg |
image/webp | .webp |
image/gif | .gif |
image/bmp | .bmp |
Project directory search paths
The script searches for the project directory (from the Zed window title) under these locations in order:
~/Development~/Projects~/Code~/Documents~/(home directory)
If your projects live elsewhere, add the path to the for base in ... loop in the script.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| No notification at all | Keybinding not firing | Use full path in binding, check hyprctl binds |
| "No image in clipboard" | Clipboard has text, not an image | Copy an actual image first |
| "Not in Zed editor" | Focus was on another window | Make sure Zed is focused when pressing the shortcut |
| "Could not find project" | Project dir not under search paths | Add your path to the script's search loop |
| "Could not find file" | Duplicate filenames resolved wrong | The script picks the shortest path; rename if needed |
| Image saved but no text pasted | wtype failed or keys still held | Increase the sleep value (try 0.8 or 1.0) |
Limitations
- Relies on Zed's window title format (
project — file.md) to locate the file. If Zed changes this format, the script will need updating. - When multiple files share the same name, the script picks the one with the shortest path. This is usually correct but not guaranteed.
- The
sleep 0.5delay beforewtypeis needed to wait for modifier key release. If paste doesn't work, increase this value.