qutebrowser A keyboard-driven browser.

Configuring qutebrowser

qutebrowser’s configuration system was completely rewritten in September 2017. This information is not applicable to older releases, and older information elsewhere might be outdated.

qutebrowser’s config files

qutebrowser releases before v1.0.0 had a qutebrowser.conf and keys.conf file. Those are not used anymore since that release - see "Migrating older configurations" for information on how to migrate to the new config.

When using :set and :bind, changes are saved to an autoconfig.yml file automatically. If you don’t want to have a config file which is curated by hand, you can simply use those - see "Configuring qutebrowser via the user interface" for details.

For more advanced configuration, you can write a config.py file - see "Configuring qutebrowser via config.py". When a config.py exists, the autoconfig.yml file is not read anymore by default. You need to load it from config.py if you want settings changed via :set/:bind to persist between restarts.

Configuring qutebrowser via the user interface

The easy (but less flexible) way to configure qutebrowser is using its user interface or command line. Changes you make this way are immediately active (with the exception of a few settings, where this is pointed out in the documentation) and are persisted in an autoconfig.yml file.

The autoconfig.yml file is located in the "config" folder listed on the qute://version page. On macOS, the "auto config" folder is used, which is different from where hand-written config files are kept.

However, do not edit autoconfig.yml by hand. Instead, see the next section.

If you want to customize many settings, you can open the qute://settings page by running :set without any arguments, where all settings are listed and customizable.

Using the :set command and command completion, you can quickly set settings interactively, for example :set tabs.position left.

Some settings are also customizable for a given URL pattern by doing e.g. :set --pattern=*://example.com/ content.images false.

To get more help about a setting, use e.g. :help tabs.position.

To bind and unbind keys, you can use the :bind and :unbind commands:

  • Binding the key chain ,v to the :spawn mpv {url} command: :bind ,v spawn mpv {url}

  • Unbinding the same key chain: :unbind ,v

Key chains starting with a comma are ideal for custom bindings, as the comma key will never be used in a default keybinding.

See the help pages linked above (or :help :bind, :help :unbind) for more information.

Other useful commands for config manipulation are :config-unset to reset a value to its default, :config-clear to reset the entire configuration, and :config-cycle to cycle a setting between different values.

Configuring qutebrowser via config.py

For more powerful configuration possibilities, you can create a config.py file. Since it’s a Python file, you have much more flexibility for configuration. Note that qutebrowser will never touch this file - this means you’ll be responsible for updating it when upgrading to a newer qutebrowser version.

You can run :config-edit inside qutebrowser to open the file in your editor, :config-source to reload the file (:config-edit does this automatically), or :config-write-py --defaults to write a template file to work with.

The file should be located in the "config" location listed on qute://version, which is typically ~/.config/qutebrowser/config.py on Linux, ~/.qutebrowser/config.py on macOS, and %APPDATA%/qutebrowser/config/config.py on Windows.

Two global objects are pre-defined when running config.py: c and config.

Changing settings

While you can set settings using the config.set() method (which is explained in the next section), it’s easier to use the c shorthand object to easily set settings like this:

c.tabs.position = "left"
c.completion.shrink = True

Note that qutebrowser does some Python magic so it’s able to warn you about mistyped config settings. As an example, if you do c.tabs.possition = "left", you’ll get an error when starting.

See the settings help page for all available settings. The accepted values depend on the type of the option. Commonly used are:

  • Strings: c.tabs.position = "left"

  • Booleans: c.completion.shrink = True

  • Integers: c.messages.timeout = 5000

  • Dictionaries:

    • c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'} to override any other values in the dictionary.

    • c.aliases['foo'] = 'message-info foo' to add a single value.

  • Lists:

    • c.url.start_pages = ["https://www.qutebrowser.org/"] to override any previous elements.

    • c.url.start_pages.append("https://www.python.org/") to add a new value.

Any other config types (e.g. a color) are specified as a string. The only exception is the Regex type, which can take either a string (with an r prefix to preserve backslashes) or a Python regex object:

  • c.hints.next_regexes.append(r'\bvor\b')

  • c.hints.prev_regexes.append(re.compile(r'\bzur├╝ck\b'))

If you want to read a setting, you can use the c object to do so as well: c.colors.tabs.even.bg = c.colors.tabs.odd.bg.

Using strings for setting names

If you want to set settings based on their name as a string, use the config.set method:

# Equivalent to:
# c.content.javascript.enabled = False
config.set('content.javascript.enabled', False)

To read a setting, use the config.get method:

# Equivalent to:
# color = c.colors.completion.fg
color = config.get('colors.completion.fg')

Per-domain settings

Using config.set, some settings are also customizable for a given URL pattern:

config.set('content.images', False, '*://example.com/')

Alternatively, you can use with config.pattern(...) as p: to get a shortcut similar to c. which is scoped to the given domain:

with config.pattern('*://example.com/') as p:
    p.content.images = False

Binding keys

While it’s possible to change the bindings.commands setting to bind keys, it’s preferred to use the config.bind command. Doing so ensures the commands are valid and normalizes different expressions which map to the same key.

For details on how to specify keys and the available modes, see the documentation for the bindings.commands setting.

To bind a key:

config.bind('<Ctrl-v>', 'spawn mpv {url}')

To bind a key in a mode other than 'normal', add a mode argument:

config.bind('<Ctrl-y>', 'prompt-yes', mode='prompt')

To unbind a key (either a key which has been bound before, or a default binding):

config.unbind('<Ctrl-v>', mode='normal')

To bind keys without modifiers, specify a key chain to bind as a string. Key chains starting with a comma are ideal for custom bindings, as the comma key will never be used in a default keybinding.

config.bind(',v', 'spawn mpv {url}')

To suppress loading of any default keybindings, you can set c.bindings.default = {}.

Loading autoconfig.yml

All customization done via the UI (:set, :bind and :unbind) is stored in the autoconfig.yml file. When a config.py file exists, autoconfig.yml is not loaded automatically. To load autoconfig.yml automatically, add the following snippet to config.py:


You can configure which file overrides the other by the location of the above code snippet. Place the snippet at the top to allow config.py to override autoconfig.yml. Place the snippet at the bottom for the opposite effect.

Importing other modules

You can import any module from the Python standard library (e.g. import os.path), as well as any module installed in the environment qutebrowser is run with.

If you have an utils.py file in your qutebrowser config folder, you can import that via import utils as well.

While it’s in some cases possible to import code from the qutebrowser installation, doing so is unsupported and discouraged.

To read config data from a different file with c and config available, you can use config.source('otherfile.py') in your config.py.

Getting the config directory

If you need to get the qutebrowser config directory, you can do so by reading config.configdir. Similarly, you can get the qutebrowser data directory via config.datadir.

This gives you a pathlib.Path object, on which you can use / to add more directory parts, or str(...) to get a string:

print(str(config.configdir / 'config.py'))

Handling errors

If there are errors in your config.py, qutebrowser will try to apply as much of it as possible, and show an error dialog before starting.

qutebrowser tries to display errors which are easy to understand even for people who are not used to writing Python. If you see a config error which you find confusing or you think qutebrowser could handle better, please open an issue!


Reading a YAML file

To read a YAML config like this:

tabs.position: left
tabs.show: switching

You can use:

import yaml

with (config.configdir / 'config.yml').open() as f:
    yaml_data = yaml.safe_load(f)

for k, v in yaml_data.items():
    config.set(k, v)

Reading a nested YAML file

To read a YAML file with nested values like this:

      bg: lime
      fg: black
      fg: red

You can use:

import yaml

with (config.configdir / 'colors.yml').open() as f:
    yaml_data = yaml.safe_load(f)

def dict_attrs(obj, path=''):
    if isinstance(obj, dict):
        for k, v in obj.items():
            yield from dict_attrs(v, '{}.{}'.format(path, k) if path else k)
        yield path, obj

for k, v in dict_attrs(yaml_data):
    config.set(k, v)

Note that this won’t work for values which are dictionaries.

Binding chained commands

If you have a lot of chained commands you want to bind, you can write a helper to do so:

def bind_chained(key, *commands):
    config.bind(key, ' ;; '.join(commands))

bind_chained('<Escape>', 'clear-keychain', 'search')

Reading colors from Xresources

You can use something like this to read colors from an ~/.Xresources file:

import subprocess

def read_xresources(prefix):
    props = {}
    x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
    lines = x.stdout.decode().split('\n')
    for line in filter(lambda l : l.startswith(prefix), lines):
        prop, _, value = line.partition(':\t')
        props[prop] = value
    return props

xresources = read_xresources('*')
c.colors.statusbar.normal.bg = xresources['*.background']

Pre-built colorschemes

Avoiding flake8 errors

If you use an editor with flake8 and pylint integration, it may have some complaints about invalid names, undefined variables, or missing docstrings. You can silence those with:

# pylint: disable=C0111
c = c  # noqa: F821 pylint: disable=E0602,C0103
config = config  # noqa: F821 pylint: disable=E0602,C0103

For type annotation support (note that those imports aren’t guaranteed to be stable across qutebrowser versions):

# pylint: disable=C0111
from qutebrowser.config.configfiles import ConfigAPI  # noqa: F401
from qutebrowser.config.config import ConfigContainer  # noqa: F401
config = config  # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
c = c  # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103

emacs-like config

Various emacs/conkeror-like keybinding configs exist:

It’s also mostly possible to get rid of modal keybindings by setting input.insert_mode.auto_enter to false, and input.forward_unbound_keys to all.

Migrating older configurations

qutebrowser does no automatic migration for the new configuration. However, there’s a special configdiff page (qute://configdiff/old) in qutebrowser, which will show you the changes you did in your old configuration, compared to the old defaults.

Other changes in default settings:

  • In v1.1.x and newer, <Up> and <Down> navigate through command history if no text was entered yet. With v1.0.x, they always navigate through command history instead of selecting completion items. Use <Tab>/<Shift-Tab> to cycle through the completion instead. You can get back the old behavior by doing:

    :bind -m command <Up> completion-item-focus prev
    :bind -m command <Down> completion-item-focus next

    or always navigate through command history with

    :bind -m command <Up> command-history-prev
    :bind -m command <Down> command-history-next
  • The default for completion.web_history.max_items is now set to -1, showing an unlimited number of items in the completion for :open as the new sqlite-based completion is much faster. If the :open completion is too slow on your machine, set an appropriate limit again.