We create an endpoint in emacs that listens to commands from firefox (the same technique works in chrome or other browsers1). For this article, we focus on org-protocol
only2 on linux.
Here's a demo of of me writing the current url to a file using a custom org protocol (not capture, not store-link, not something built in!):
But of course, you could make emacs do anything, like take a screenshot through a shell command, create a todo item, send out an email, and more!
Head to Setup if you want to get setup right away!
Breakdown:
Just note, that
- I click a javascript bookmarklet, which attempts to GET a resource.
javascript:(function(){document.location.href="org-protocol://my-protocol?url=" + encodeURIComponent(location.href) + "&file-name=.txt";})();
- Firefox recognizes
org-protocol://my-protocol?url=https://17070415.xyz/support&file-name=delete-me.txt
and lets the system handle the request.
- My linux system looks table for handling the
org-protocol
protocol (the bit before the first:
)
Inside either
/usr/share/applications/mimeinfo.cache
or~/.config/mimeapps.list
, I havex-scheme-handler/org-protocol=emacsclient.desktop;
- emacsclient.desktop handles the call
The relevant part is the
Exec
key. The main focus isemacsclient %U
:Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec /usr/bin/emacsclient --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec emacsclient %U; fi" sh %F
The
%U
allows emacsclient to be called with an arg interpreted as a url, and not a file3. - emacsclient receives arguments
and is called like so, as if I was executing from a terminal:
emacsclient "org-protocol://my-protocol?url=https://17070415.xyz/support&file-name=delete-me.txt"
- the custom handler in emacs runs!
And of course, you can use the same technique to have emacs call anything on your system! Further, you can use arbitrary javascript to get a lot data from the webpages (currently selected text, author of an article, etc.).
Requirements
- You have emacsclient installed (ships with emacs)
- org mode (ships with emacs)
- firefox, but this also works on most major browsers
Setup
Setup in three parts:
- Load some code so emacs can listen for a request
- Configure firefox to open a org-protocol externally.
- Ensure your system recognizes the org-protocol to open emacsclient, allowing us to trigger code from 1.
1. In emacs, run this:
(require 'org-protocol) (server-start) (defun my-protocol-handler (url) (message "%s" url) ;; (:url https:/duckduckgo.com :title TITLE) (let* ((splitparts (org-protocol-parse-parameters url nil '(:url :file-name))) (uri (org-protocol-sanitize-uri (plist-get splitparts :url))) (file-name (plist-get splitparts :file-name)) (kill-p (plist-get splitparts :kill-p)) (buffer (find-file-noselect (file-name-concat "/tmp/delete-me.txt" file-name)))) (with-current-buffer buffer (goto-char (point-max)) (insert "\n") (insert uri) (save-buffer) (when kill-p (kill-current-buffer)))) nil) (with-eval-after-load 'org-protocol (setq org-protocol-protocol-alist '(("my-protocol" :protocol "my-protocol" :function my-protocol-handler))))
2. Configure firefox
Create a bookmark in firefox:
javascript:(function(){document.location.href="org-protocol://my-protocol?url=" + encodeURIComponent(location.href) + "&file-name=.txt";})();
And in firefox, goto about:config
and make sure you have these items set:
network.protocol-handler.expose.org-protocol
is set to truenetwork.protocol-handler.external.org-protocol
is set to true
(ornetwork.protocol-handler.external-default
is set to true, but this applies more broadly than just org-protocol)

3. Mime types
cp /usr/share/applications/emacsclient.desktop ~/.local/share/applications/emacsclient.desktop
and change the Exec in the .local
copy to:
Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec /usr/bin/emacsclient --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec emacsclient %U; fi" sh %F
Or change the Exec to just
emacsclient %U
Sometimes you need to ensure that this setting takes effect. On my system it was instant.
Run this to be sure:
update-desktop-database ~/.local/share/applications/
4. Try it!
In firefox, click the bookmark. You should have similar results as the demo.
Troubleshooting
Here are useful debugging steps to figure out what's not working
The handler works, but the webpage gets rewritten
For whatever reason, on my firefox version 133.0.3 javascript of this form
javascript:document.location.href="org-protocol://my-protocol?url=" + encodeURIComponent(location.href) + "&file-name=delete-me.txt";
will overwrite the current tab's state. What ended up working for me was wrapping the assignment in a function:
javascript:(function(){document.location.href="org-protocol://my-protocol?url=" + encodeURIComponent(location.href) + "&file-name=delete-me.txt";})();
Does emacsclient recognize the protocol?
Try calling in your terminal:
emacsclient "org-protocol://my-protocol?url=https://17070415.xyz&file-name=delete-me.txt"
If your handler doesn't fire, do step 1 again. Inspect org-protocol-protocol-alist
. It should look like
(("my-protocol" :protocol "my-protocol" :function my-protocol-handler))
If this variable doesn't exist, you forgot to (require 'org-protocol)
.
Firefox doesn't recognize the protocol
Your console (bring it up with F12
in your browser) will show
Prevented navigation to “org-protocol://my-protocol?url=https%3A%2F%2Fspecifications.freedesktop.org%2Fdesktop-entry-spec%2Flatest%2Fexec-variables.html&file-name=delete-me.txt” due to an unknown protocol.
This error shows up usually because you are missing some firefox config flags. And different config flags seem to change depending on your FF version or perhaps even how you installed FF.
- visit
about:config
in the URL bar. - ensure
network.protocol-handler.expose.org-protocol
is set to true - ensure
network.protocol-handler.external.org-protocol
is set to true
ornetwork.protocol-handler.external-default
is set to true
You'll know if this is working when you don't see the error message anymore when you run
(function(){document.location.href="org-protocol://my-protocol?url=" + encodeURIComponent(location.href) + "&file-name=.txt";})();
Emacs tries to open a file
Most likely in your emacsclient.desktop, emacsclient is called with %F
, not %U
.
%U A list of URLs. Each URL is passed as a separate argument to the executable program. Local files may either be passed as file: URLs or as file path.
You can verify correct behavior from steps in Does emacsclient recognize the protocol?
Doesn't work in some FF browsers?
If you use FF based browsers, like Tor Browser, I found that
~/.local/share/applications/emacsclient.desktop
wasn't used. Instead it uses explicitly,
/usr/share/applications/emacsclient.desktop
In this case, modify the emacsclient.desktop
file at the system level to call emacsclient %U
.
Something else?
See footnotes for sources for this article4. One of those links might be useful.
Wrap up
We've seen how to register our own "subprotocol" under org-protocol
, (in our example, the subprotocol is "my-protocol"). What if you wanted even more power?
- Emacs can open ports and listen to them allowing you to have a full request/response cycle. You can have your browser javascript make a GET/POST request on a local server served by emacs.
- This is how browser extensions like Atomic Chrome, and Edit With Emacs work.
Lastly if this article helped you please consider supporting my content and visit support. Thanks so much!
You'll want to make sure the browser opens an org-protocol link externally.
firefox also supports DBUS, but its interface is limited. I doubt you can send to dbus on firefox with javascript. If you're interested in using emacs with DBUS, check out my other article on using dbus in emacs.
See https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html. If your emacsclient is opening a new frame, emacsclient is likely treating the argument as a file.
Other links
- https://emacstil.com/til/2021/11/09/firefox-org-capture/
- https://kisaragi-hiu.com/org-protocol-linux
- https://cestlaz.github.io/post/using-emacs-70-org-protocol/
- https://redlib.catsarch.com/r/emacs/comments/ai7hf0/does_anyone_here_still_use_orgprotocol/
- https://orgmode.org/worg/org-contrib/org-protocol.html