qutebrowser Userscripts on Windows
Updated 2021-July-4 🎇️🎆️

Page contents


2021-June-28  qutebrowser v2.3.0 released. To keep up with qutebrowser releases, see github.com/qutebrowser/qutebrowser/releases, lists.schokokeks.org/pipermail/qutebrowser/, or old.reddit.com/r/qutebrowser/.

2021-March-28  Published this evolving⁠[1] article.


About qutebrowser userscripts

qutebrowser’s config, data, and userscripts directories

Since a default qutebrowser installation does not include a userscripts directory, you will need to create one in either qutebrowser’s config or data directory.⁠[2] More about this is in 1. Create a userscripts directory below.

To find out the location of your data and config directories, run the :version command from within qutebrowser. On a default Windows installation, this displays something like the following (with USERNAME replaced with your user name).

config: C:\Users\USERNAME\AppData\Roaming\qutebrowser\config
data: C:\Users\USERNAME\AppData\Roaming\qutebrowser\data


Sidenote: greasemonkey directories

Greasemonkey scripts are entirely different from qutebrowser userscripts, but I mention them here because a default installation of qutebrowser v2.0.0+ automatically includes empty greasemonkey directories in the data and config directories. To learn about greasemonkey scripts, which are written in JavaScript, see wikipedia.org/wiki/Greasemonkey.

Here is a greasemonkey script that I use:

// ==UserScript==
// @name         reddit to teddit
// @namespace    https://gist.github.com/bitraid/d1901de54382532a03b9b22a207f0417
// @version      1.0
// @description  reddit to teddit
// @match        *://*.reddit.com/*
// @match        *://reddit.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
	'use strict';
	top.location.hostname = "teddit.net";


Tip: Create a shortcut to your qutebrowser config directory

When configuring qutebrowser, I often do things in my C:\Users\USERNAME\AppData\Roaming\qutebrowser\config\ directory. To make it easy to get there, I have a shortcut to this directory on my Windows Desktop. Details about creating and using this shortcut are in Infinite Ink’s Windows Desktop Shortcuts Give me Quick Access to Everything.


Five steps to create a simple Windows userscript

1. Create a userscripts directory

Use either Windows File Explorer or the command line to create a userscripts subdirectory in either your qutebrowser config or data directory.


If you are using qutebrowser v2.0.0+, I recommend creating config\userscripts\ because then your qutebrowser userscripts and configuration file(s) will all be located in the config directory.

If you are using qutebrowser v1.14.1 or earlier, you need to use data\userscripts\ (because config\userscripts\ was introduced in v2.0.0).


2. Create qb-env.cmd batch file

Create a plain text file named qb-env.cmd in the userscripts directory that you created in the previous step that contains this:

@echo off

rem created: 2021-07-04
rem filename: qb-env.cmd

echo Hello from qb-env.cmd!

echo Working directory is...

echo Active code page is...

echo Environment variables starting with QUTE are...
set QUTE

echo Environment variables starting with PATH are...
set PATH


qb-env means qutebrowser environment and the above batch file displays the working directory, active code page, and some environment variables.

If you are wondering why this batch file has a .cmd rather than .bat file extension, see stackoverflow.com/questions/148968/windows-batch-files-bat-vs-cmd.


3. Create a key binding in config.py

In your config\config.py configuration file, add this line:

config.bind(',qenv', 'spawn -u -o "qb-env.cmd"')

If you do not have a config.py, which is qutebrowser’s configuration file, see Infinite Ink’s Getting Started with qutebrowser.


4. :config-source

To tell qutebrowser about this new ,qenv key binding, run the following from within qutebrowser:



5. Launch qb-env.cmd from within qutebrowser

In qutebrowser, make sure you are in command mode by pressing the Esc key and then type:


This will run qb-env.cmd and its output will be displayed in a new qutebrowser tab (thanks to the -⁠o argument specified in step 3 above).


  • The -u spawn argument is equivalent to --userscript.

  • The -o spawn argument is equivalent to --output.

  • In qutebrowser v2.2.0+, you can use the :process command to display the full output of a spawn command in the current qutebrowser window.


Limitations of userscripts on Windows

On Windows, only userscripts with .bat, .cmd, .com, and .exe extensions will be launched by qutebrowser. This means that if you want to run a bash or python script from within Windows qutebrowser, you need to create a .bat, .cmd, .com, or .exe wrapper for it.


Calling a WSL bash script from a Windows userscript

I’d rather write a bash shell script than a Windows batch file and, thanks to WSL (Windows’ Subsystem for Linux),⁠[3] most of my Windows qutebrowser scripting is actually bash scripting. In this section, I briefly describe my tumblelog-wrapper.cmd Windows userscript and my tumblelog.sh bash script that it calls.


Key binding in config.py

I put the following line in my config.py.

config.bind(',t', 'spawn -u tumblelog-wrapper.cmd')

This makes it possible to use ,t to launch my tumblelog-wrapper.cmd Windows userscript, which adds qutebrowser’s current web page to Infinite Ink’s #tumblelog Portal.



When the argument to spawn -u is a relative path, it is relative to qutebrowser’s data\userscripts\ or config\userscripts\[2] directory. To put a userscript anywhere on your system, use an absolute path, for example:

config.bind(',t', 'spawn -u "C:\\Users\\USERNAME\\Sync\\qb\\tumblelog-wrapper.cmd"')


tumblelog-wrapper.cmd Windows userscript

In the Windows file system, my qutebrowser data\ directory⁠[4] contains userscripts\tumblelog-wrapper.cmd, which comprises the following five lines.

@REM change code page so UTF-8 characters work
chcp 65001

@REM pass six QUTE_ variables to the bash script


  • WSLUSERNAME is my WSL user name.

  • This tumblelog-wrapper.cmd Windows userscript does not work if any tumblelog.sh argument, for example %QUTE_TITLE%, contains an ASCII double quotation mark (").⁠[5]


tumblelog.sh bash script

In the WSL file system, my ~/Scripts/tumblelog.sh starts out like this:


QHTMLPATH=`wslpath $5`
QTEXTPATH=`wslpath $6`

# below here, I use a heredoc to create a Hugo leaf bundle

I hope this is enough to get you started using a WSL bash script with qutebrowser.




Calling a Git Bash script from a Windows userscript

Instead of calling a WSL script, you can call a Git Bash script by replacing the tumblelog-wrapper.cmd batch file in the previous section with something like the following.

@REM The sh.exe below is part of Git For Windows
"C:\Program Files\Git\bin\sh.exe" -l "C:\full\path\to\tumblelog.sh"

If both sh.exe and tumblelog.sh are on your path, you can use the following.

pathless tumblelog-wrapper.cmd
@REM IMPORTANT: Make sure the sh.exe below is not a trojan!
"sh.exe" -l "tumblelog.sh"



See also


1. Many Infinite Ink articles, including this one, are evergreen and regularly updated.
2. In qutebrowser v1.14.1 and earlier, userscripts are in data/userscripts/. In v.2.0.0+, userscripts are in either data/userscripts/ or config/userscripts/ (or both).
3. It would probably make more sense if WSL were called LSW (Linux Subsystem for/of/on Windows), but it is not because, according to Rich Turner in this tweet, Microsoft “cannot name something leading with a trademark owned by someone else.” Rich’s tweet has inspired me to interpret the WSL acronym as meaning Windows Subsystem for Linux (notice the apostrophe ()).
4. To find out your qutebrowser config and data directories, run the :version command from within qutebrowser.
5. An ASCII quotation mark is also known as a dumb, neutral, vertical, straight, or typewriter quotation mark.

Comments 👍 📝 😱

Please comment so I know I'm not alone in an infinite void.