The Goal#
I wanted a frictionless, Hugo‑native image paste workflow inside VS Code. Nothing fancy — just the ability to:
copy an image from Firefox, Pinta, or GNOME Screenshot
paste it into a Markdown file
have it land inside the same Hugo page bundle
with a deterministic filename like:
my-first-garden-image-1.png
my-first-garden-image-2.png
Frustrations experienced:
No random UUIDs.
No absolute paths.
No broken links.
No “where did that file go” moments.
Just a clean, predictable workflow that respects Hugo’s page bundle model.
That was the goal.
And getting there was… a journey.
The First Roadblock: VS Code Remote SSH#
My initial workflow was editing the Hugo repo on a remote PVE Trixie LXC using VS Code Remote SSH from my daily driver, a Lenovo P52.
This is where the trouble began.
The moment I tried to paste an image:
- some extensions tried to write to the local machine
- others tried to write to the remote machine
- some wrote to /tmp on the wrong host
- some failed silently
- some produced absolute paths that Hugo couldn’t use
- some produced relative paths that pointed nowhere
And the worst part: none of them understood Hugo page bundles.
Even when the paste succeeded, the image ended up in:
~/.config/Code/User/markdown-images/
or
/tmp/vscode-paste-image/
or some other random location.
Remote SSH made the whole thing even more confusing because the clipboard lived on my daily driver, but the file operations happened on the remote.
Trying Every Extension Under the Sun#
I went through the usual suspects:
- Paste Image (mushan)
- Paste Image Plus (telesoho)
- Markdown Image (Hancel.Lin)
- Markdown Paste
- Paste to Markdown
- a handful of forks and half‑maintained clones
Some of them almost worked.
Some of them worked locally but broke over SSH.
Some of them worked over SSH but mangled the paths.
Some of them worked on X11 but not Wayland.
Some of them worked on Windows but not Linux.
And none of them respected Hugo’s page bundle structure.
The New VS Code API That Almost Solved It#
VS Code recently added a newer clipboard + file‑paste API that lets extensions:
- read image data directly
- write to the workspace
- return a Markdown link
This looked promising.
But the moment you introduce:
- Remote SSH
- Linux Wayland
- Hugo page bundles
- and the need for deterministic filenames
…it fell apart.
The API doesn’t know anything about Hugo’s directory model, and the extensions built on top of it assume a flat folder structure.
Close, but not enough.
What the VS Code API Actually Needs (But Doesn’t Have)#
This is the missing piece that caused most of the trouble.
To correctly save an image into a Hugo page bundle, an extension would need a variable like:
bundleBase = path.dirname(markdownFilePath)
or even better:
bundleName = path.basename(bundleBase)
But the VS Code API does not provide:
- the concept of a “bundle directory”
- the parent folder of the Markdown file
- the bundle name
- any Hugo‑specific context
- any way to detect whether the Markdown file is inside a page bundle
Extensions only receive:
- the clipboard image data
- the workspace root
- the Markdown file path (sometimes)
- a suggested default save directory
That’s not enough to compute:
content/posts/my-first-garden/index.md
→ bundle directory = content/posts/my-first-garden/
→ bundle name = my-first-garden
→ target filename = my-first-garden-image-1.png
Without that “bundleBase” concept, extensions guess — and they guess wrong.
This is why every extension failed in a different way.
The Closest I Got With the API#
This VS Code configuration actually got the filename right:
"markdown.copyFiles.destination": {
"**/*": "${documentDirName}-${fileName}"
},
"markdown.copyFiles.overwriteBehavior": "nameIncrementally",
"editor.pasteAs.enabled": true
But it saved the file one directory above the page bundle.
${documentDirName} gives you the name of the bundle, but not its path.
VS Code has no ${documentDirPath} or ${fileDirPath} variable, so the API always saves relative to the workspace root.
That produced links like:
../my-first-garden-image-1.png
Close, but unusable for Hugo.
This missing “bundle base” variable is exactly why I abandoned the API approach.
The Breakthrough: Markdown Image’s DIY Mode#
The Markdown Image extension by Hancel.Lin has a feature that most people overlook:
"markdown-image.base.uploadMethod": "DIY"
This tells the extension:
“Don’t handle the paste yourself.
Call my script instead.”
That was the missing piece.
DIY mode hands you:
- the temp file path
- the Markdown file path
- the workspace context
And from there, you can do anything you want.
So I wrote a small Node.js script that:
- Detects the page bundle directory s
- Extracts the bundle name
- Generates a deterministic filename
- Auto‑increments the counter
- Copies the image into the bundle
- Returns a clean relative Markdown link
Exactly the workflow I wanted.
The Final Solution: A Custom DIY Script#
Here’s the script I ended up with — clean, deterministic, and Hugo‑native. It works:
- locally
- over SSH
- on Wayland
- on X11
- with any clipboard source
- with any Hugo page bundle
And it never overwrites existing images.
I published it at https://github.com/gumshoenoir/MarkDownImageDiy hopefully as a submodule.
Why This Matters#
Hugo’s page bundle model is elegant, but most editors and extensions don’t understand it.
This little script bridges that gap and gives you a workflow that feels native:
- paste image
- image lands in the right bundle
- filename is predictable
- Markdown link is correct
- no surprises
It’s the workflow I wanted from the beginning — and now it’s reproducible, portable, and rock‑solid.
I used duck.ai, copilot and gemini while attempting to get the workflow I wanted. I used copilot to help write this blog post as I ain’t got gud grammer or speling. I find it hilarious how confident copilot has been with every dead end, maniac hallucination and hubris. Take this from the above line “…and rock‑solid.”
