Obsidian is an amazing tool, but as your vault grows, so does its size. Images can quickly eat up storage, especially if you’re using Obsidian Sync or Publish like me.
With Sync’s 1GB to 100GB limit and Publish’s 4 GB site limit, optimizing your media files isn’t just a good idea - it’s essential.
In this blog post, I’ll show you how simple it could be using WebP reducing image file sizes by 30% without sacrificing quality.
Why WebP
?
There are plenty of reasons to use WebP, but here’s my take:
- Standing on the Shoulders of Giants: WebP was developed by Google, and they’ve done the hard work of proving its superiority. Their detailed benchmark shows how much better WebP is compared to other formats.
- Compress and Decompress: you can compress images lossy/lossless to
.webp
. If you ever change your mind, you can always decompress them back to original.
Remark
Download the precompiled executables to automate the compression process.
How to Automate WebP Conversion in Obsidian?
📋Prerequisite
- download the executable including
cwebp
,dwebp
andgif2webp
. - install the Obsidian Plugin Templater.
Once you’ve downloaded the executable, add the path/to/libwebp
to your environment variables. Then, with a simple script in Templater, you can automatically convert all images in your working document to .webp
. The script scans your working note, finds image references (whether in format like ![[image.png]]
or 
), converts them to .webp
, and updates the links respectively.
<%*
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const vaultRootPath = app.vault.adapter.basePath;
const fileContent = await tp.file.content;
// Regex to match markdown image syntax: ![[image.png]] or 
const imageRegex = /!\[(.*?)\]\((.*?\.(?:png|jpg|jpeg))\)|!\[\[(.*?\.(?:png|jpg|jpeg))\]\]/g;
const images = [...fileContent.matchAll(imageRegex)].map(match => match[2] || match[3]);
if (images.length === 0) {
new Notice("No images found in the file.", 3000);
} else {
let updatedContent = fileContent;
for (const image of images) {
// Use Obsidian API to find the file in the vault
const imageFile = app.vault.getFiles().find(file => file.path.includes(path.basename(image)));
if (!imageFile) {
console.error(`Image not found in vault: ${image}`);
new Notice(`Image not found in vault: ${image}`, 5000);
continue;
}
// Resolve the full path to the image
const imagePath = path.join(vaultRootPath, imageFile.path);
const imageName = path.basename(image, path.extname(image));
const webpPath = path.join(path.dirname(imagePath), `${imageName}.webp`);
// Convert image to WebP using cwebp
try {
execSync(`cwebp "${imagePath}" -q 80 -o "${webpPath}"`); //👈tweak the arguments by your need
console.log(`Converted ${image} to WebP: ${webpPath}`);
// Replace the original image reference in the file content with the WebP version
updatedContent = updatedContent.replace(
new RegExp(`!\\[.*?\\]\\(${image.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)|!\\[\\[${image.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]\\]`, 'g'),
`![[${imageName}.webp]]`
);
} catch (error) {
console.error(`Failed to convert ${image} to WebP:`, error.message);
new Notice(`Failed to convert ${image}: ${error.message}`, 5000);
}
}
// Write the updated content back to the file
const filePath = path.join(vaultRootPath, tp.file.path(true));
fs.writeFileSync(filePath, updatedContent);
new Notice("All images have been converted to WebP and the file has been updated.", 5000);
}
%>
TipI add one line to remove the original image via:
fs.unlinkSync(imagePath);