Christopher Orr

Creating HTTP links to Joplin notes

I’m a fan of Joplin for note-taking, with its Markdown-friendly editor, syncing across desktop and mobile clients, plus end-to-end encryption with various storage backends, allowing me to use my trusty Fastmail account to keep my notes safe.

The problem

I would like to link to a specific Joplin note from other tools. For example, I sometimes organise projects or tasks in Notion — which is great, but I do not fully trust them with my private data. So it would be handy if I could link to a Joplin note from a Notion page.

Joplin has a built-in “Copy external link” feature, but this results in URLs like this: joplin://x-callback-url/openNote?id=cf5b742e7b60423cb6cc3266513c736e

Accessing this URL will indeed open the Joplin app and display the relevant note, but most tools won’t recognise this as a clickable link.

The solution

The good news is that Joplin has plugins, and writing your own Joplin plugin is fairly straightforward.

So I now have a tiny plugin that shows a “Copy HTTP link” option when I right-click on a note in Joplin, which places a publicly available URL on my clipboard, such as: https://joplin.orr.dev/note/#cf5b742e7b60423cb6cc3266513c736e

The webservice

To make those joplin.orr.dev URLs work, I essentially created a tiny static page, which grabs the Joplin note ID from the URL, and instantly redirects to the joplin:// URL:

<html><head><script>
location.href = `joplin://x-callback-url/openNote?id=${location.hash.slice(1)}`
</script></head></html>

While Joplin note IDs don’t reveal any sensitive data, I wanted to take advantage of the fact that URL fragments are only processed by the browser, and not sent to the server.

This means that the note IDs don’t even appear in my logs, which is a bonus privacy preserving feature, especially since other Joplin users can make use of this service.

However, to allow for users with JavaScript disabled, I also support /note/<id>, which sends an HTTP redirect to the joplin:// URL, rather than serving the static page. This means that the note ID will be logged to my service, if somebody clicks on a URL in this format. But again, a note ID gives no info about the note, its contents, or its author. 👍

The plugin

This is the plugin implementation, adding the “Copy HTTP link” context menu action, along with a copy-as-Markdown option which will include the note’s title:

import joplin from 'api'
import { MenuItemLocation } from "../api/types"

joplin.plugins.register({
  onStart: async () => {
    await Promise.all([false, true].map(async (withMarkdown) => {
      const id = withMarkdown ? `copy-public-url-md` : 'copy-public-url'
      const name = `cmd/${id}`
      await joplin.commands.register({
        name, label: withMarkdown ? 'Copy HTTP link as Markdown' : 'Copy HTTP link',
        execute: async (noteIds: string[]) => copyPublicUrl(noteIds, withMarkdown),
      })
      await joplin.views.menuItems.create(id, name, MenuItemLocation.NoteListContextMenu)
    }))
  },
})

async function copyPublicUrl(noteIds: string[], withMarkdown: boolean = false) {
  const noteId = noteIds[0]
  const noteUrl = `https://joplin.orr.dev/note/#${noteId}`

  const note = await joplin.data.get(['notes', noteId])
  const text = withMarkdown ? `[${note.title}](${noteUrl})` : noteUrl
  await joplin.clipboard.writeText(text)
}

I’ve now published this to GitHub.

Usage & privacy

A few notes, for anybody who might want to use joplin.orr.dev.

By using the URLs below, you’re not publishing your note or sharing any other info apart from the note’s ID. Clicking one of these links justs sends a signal to open the Joplin app, if installed, which will then show you the note which has that ID — if it exists.

These are the supported URL formats. The first one is slightly preferable, as it means that I can’t see your note ID, but it relies on JavaScript being enabled in your browser:

  • https://joplin.orr.dev/note/#<note-id>
  • https://joplin.orr.dev/note/<note-id>
  • https://joplin.orr.dev/note/?id=<note-id>

You can also replace joplin.orr.dev with j.orr.dev for a shorter URL.

This service is hosted on Cloudflare Workers, which automatically logs every HTTP request made. This will include your IP address, browser, and Joplin note ID (depending on URL format) when you click on a link. However, aside from a casual interest in whether other people are using this, I don’t intend to check the logs regularly or make any attempt to identify people, or otherwise misuse the usage data for this service.

You’re always free to implement your own version of this service. As shown above, this can be done with essentially one line of JavaScript.

Let me know if you find this useful. Enjoy!