Always Twisted

Capturing URLs to Obsidian with a Bookmarklet

On This Page:

Many of us come across interesting articles, tools, or resources online and wish to save them for later reference. Over the last couple of years I've tried using an application to keep all of my 'notes' in one place and recently moved to Obsidian.

I wanted a bookmarklet that would help me save interesting web pages to my Obsidian Vault so I can access it at a later date. I could use the browsers native bookmarking tools but I'm often across browsers throughout my working day. I could use available third-party tools - but I want to try to keep 'everything' in markdown, in Obsidian. This article you're reading now started off in Obsidian.

In this post, I’ll walk you through the bookmarklet, how it works, and how you can use it to streamline your bookmarking workflow.

What the Bookmarklet Does

This bookmarklet allows you to:

The full code

Code languagejavascript
javascript:(function () {
const vault = 'everything'; // Your Obsidian vault name
const folder = 'bookmarks'; // Folder for all bookmarks
let title = prompt('Want to change the title?', document.title);
const askForTags = prompt('Enter any tags, comma separated');
let tags = askForTags ? askForTags.split(',') : [];
// Function to format tags: remove spaces, replace with hyphens, add #
function formatHashtags(arr) {
return arr.map((str) => '#' + str.trim().replace(/\s+/g, '-')).join(' ');
}
tags = formatHashtags(tags);
const url = document.location.href;
// Try to grab meta description or OG description
let description = '';
const metaDescription = document.querySelector('meta[name="description"]');
const ogDescription = document.querySelector('meta[property="og:description"]');
if (metaDescription) {
description = metaDescription.content;
} else if (ogDescription) {
description = ogDescription.content;
}
// Try to grab OG image, Twitter image, or other social image
let imageUrl = '';
const ogImage = document.querySelector('meta[property="og:image"]');
const twitterImage = document.querySelector('meta[name="twitter:image"]');
if (ogImage) {
imageUrl = ogImage.content;
} else if (twitterImage) {
imageUrl = twitterImage.content;
}
// Try to grab article:published_time
let publishedDate = '';
const publishedTime = document.querySelector('meta[property="article:published_time"]');
if (publishedTime) {
const pubDate = new Date(publishedTime.content);
const pubDay = String(pubDate.getDate()).padStart(2, '0');
const pubMonth = String(pubDate.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed
const pubYear = pubDate.getFullYear();
publishedDate = `Published on ${pubDay}-${pubMonth}-${pubYear}\n\n`; // Format as DD-MM-YYYY
}
// Sanitise title to remove invalid filename characters
title = title.replace(/[\\/:*?"<>|]/g, '-');
// Generate DD-MM-YYYY timestamp format
const now = new Date();
const day = String(now.getDate()).padStart(2, '0');
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed
const year = now.getFullYear();
const formattedDate = `${day}-${month}-${year}`; // DD-MM-YYYY format
const fileName = `${folder}/${title}`; // Unique file name
// Format content with title, date, image, description, URL, and tags
const imageMarkdown = imageUrl ? `![Image](${imageUrl})\n\n` : ''; // Add markdown image if URL exists
const content = `${publishedDate}Saved on ${formattedDate}\n\n${imageMarkdown}${description ? description + '\n\n' : ''}- [Visit the page](${url})\n\n${tags}\n`;
document.location.href = `obsidian://new?file=${encodeURIComponent(fileName)}&content=${encodeURIComponent(content)}&vault=${vault}`;
})();

Breaking Down the Code

The bookmarklet uses JavaScript to gather data from the current webpage and construct a new note in your Obsidian vault. Let’s break it into steps.

Setting Up the Vault and Folder

Code languagejavascript
const vault = 'everything'; // Your Obsidian vault name
const folder = 'bookmarks'; // Folder for bookmarks in your vault

These constants define where the captured notes will be stored. Replace everything and bookmarks with the name of your vault and the desired folder path.

Prompting for the Title and Tags

Code languagejavascript
let title = prompt('Want to change the title?', document.title);
const askForTags = prompt('Enter any tags, comma separated');
let tags = askForTags ? askForTags.split(',') : [];

Title: The script captures the webpage’s title (document.title) and gives you the option to modify it via a prompt. Tags: You can enter tags separated by commas to categorise the bookmark. If you skip this, the tags will remain empty.

Formatting Tags

Code languagejavascript
function formatHashtags(arr) {
return arr.map((str) => '#' + str.trim().replace(/\s+/g, '-')).join(' ');
}
tags = formatHashtags(tags);

This function:

Extracting Metadata

Description

Code languagejavascript
const metaDescription = document.querySelector('meta[name="description"]');
const ogDescription = document.querySelector('meta[property="og:description"]');
let description = metaDescription ? metaDescription.content : ogDescription ? ogDescription.content : '';

This retrieves the webpage’s description from tags.

Image

Code languagejavascript
const ogImage = document.querySelector('meta[property="og:image"]');
const twitterImage = document.querySelector('meta[name="twitter:image"]');
let imageUrl = ogImage ? ogImage.content : twitterImage ? twitterImage.content : '';

The script looks for Open Graph or Twitter image metadata to include a thumbnail.

Published Date

Code languagejavascript
const publishedTime = document.querySelector('meta[property="article:published_time"]');
let publishedDate = '';
if (publishedTime) {
const pubDate = new Date(publishedTime.content);
publishedDate = `Published on ${pubDate.toLocaleDateString()}\n\n`;
}

It checks for the publication date in the tags and formats it.

Sanitising the Title

Code languagejavascript
title = title.replace(/[\\/:*?"<>|]/g, '-');

This step ensures the title is valid for file names by removing characters that might cause issues.

Constructing the Note

Code languagejavascript
const imageMarkdown = imageUrl ? `![Image](${imageUrl})\n\n` : '';
const content = `${publishedDate}Saved on ${formattedDate}\n\n${imageMarkdown}${description ? description + '\n\n' : ''}- [Visit the page](${url})\n\n${tags}\n`;

The script then formats the note with the collected data:

Sending the Note to Obsidian

Code languagejavascript
document.location.href = `obsidian://new?file=${encodeURIComponent(fileName)}&content=${encodeURIComponent(content)}&vault=${vault}`;

Finally, the script redirects you to Obsidian’s URI scheme to create the note. This sends the note to the specified folder in your vault using Obsidian’s obsidian:// protocol.

How to Use the Bookmarklet

  1. Customise It:
  1. Create the Bookmarklet:
    • Create a new bookmark in Safari and paste the code into the URL field.
  2. Use It:

These steps should work in all modern browsers, I've only used this in Safari.

Hot Linking

When you save a url as a bookmark in Obsidian the image that is save d is the url, we are hot-linking to someone else's resource on their server. That's not nice, so I created a node script that would

Here's the script:

Code languagejavascript
const fs = require('fs');
const axios = require('axios');
const path = require('path');
const obsidianVaultPath = '/your/path/to/your/obsidian/vault';
const bookmarksFolderPath = path.join(obsidianVaultPath, 'your/bookmark/folder');
const imageSavePath = path.join(bookmarksFolderPath, 'your/images/folder');
if (!fs.existsSync(imageSavePath)) {
fs.mkdirSync(imageSavePath, { recursive: true });
}
// Download the image and save it locally
async function downloadImage(imageUrl, saveFolder) {
try {
const response = await axios.get(imageUrl, { responseType: 'arraybuffer' });
if (response.status === 200) {
const imageFilename = path.join(saveFolder, path.basename(imageUrl));
fs.writeFileSync(imageFilename, response.data);
console.log(`Image downloaded and saved as: ${imageFilename}`);
return imageFilename;
}
} catch (error) {
console.error('Error downloading image:', error);
}
}
// Process a note
async function processNote(noteFilePath) {
try {
let noteContent = fs.readFileSync(noteFilePath, 'utf-8');
const imageRegex = /!\[\]\((http[s]?:\/\/[^\)]+)\)/g;
const matches = [...noteContent.matchAll(imageRegex)];
if (matches.length > 0) {
for (const match of matches) {
const imageUrl = match[1];
console.log(`Found image URL: ${imageUrl}`);
const localImagePath = await downloadImage(imageUrl, imageSavePath);
if (localImagePath) {
const localImageFilename = path.basename(localImagePath);
noteContent = noteContent.replace(imageUrl, `../assets/${localImageFilename}`);
}
}
fs.writeFileSync(noteFilePath, noteContent, 'utf-8');
console.log(`Note updated: ${noteFilePath}`);
} else {
console.log(`No image URLs found in: ${noteFilePath}`);
}
} catch (error) {
console.error(`Error processing note: ${noteFilePath}`, error);
}
}
// Process all markdown files in the bookmarks folder
function processAllNotes() {
try {
const files = fs.readdirSync(bookmarksFolderPath);
const mdFiles = files.filter(file => file.endsWith('.md'));
mdFiles.forEach(file => {
const noteFilePath = path.join(bookmarksFolderPath, file);
processNote(noteFilePath);
});
} catch (error) {
console.error('Error processing files:', error);
}
}
processAllNotes();

You will need to update the obsidianVaultPath and bookmarksFolderPath and also install axios (npm install axios).

There's a few issues with this script that I want to address -- sometimes the OG image is not a png, it seems Obisdian doesn't support something like WebP. If the image returned isn't recognised you would get the text of the file (better than hot-linking in my opinion).

In the future, I plan to share this on GitHub to make it easier for others to explore and contribute. I might even refine it further, like converting all images to PNG for consistency.

Initially, I considered adding a ‘to be sorted’ folder to organise bookmarks, but I realised tags provide an equally efficient solution.

I hope this inspires you to craft a bookmarking system that fits your workflow, especially if you’re using Obsidian.

Are you struggling to define what your Design System should include to serve your team effectively?

I can audit your needs and create a tailored Design System roadmap.

get in touch!