Version 3.11.6

Nyxt manual

This manual first includes the tutorial, then covers the configuration of Nyxt.

Core concepts


Keybindings and commands


Commands are invoked by pressing specific keys or from the execute-command (Ctrl+space).

Keybindings are represented like this: 'C-x'. In this example, 'C' is a shortcut for the modifier 'control', and 'x' represents the character 'x'. To input the 'C-x' keybinding you would keep 'control' pressed and then hit 'x'. Multiple key presses can be chained: in 'C-x C-s', you would have to press 'C-x', and then press 'C-s'.

Modifier keys legend:

  • control ( C): Control key
  • super ( S): Windows key, Command key
  • meta ( M): Alt key, Option key
  • shift ( s): Shift key

Modifiers can be remapped, see the modifier-translator slot of the gtk-browser class.

Quickstart keys




Nyxt uses the concept of buffers instead of tabs. Unlike tabs, buffers are fully separated, each buffer having its own behavior and settings.



Each buffer has its own list of modes, ordered by priority. A mode is a set of functions, hooks, keybindings and other facilities that may modify the behavior of a buffer. For example, 'blocker-mode' can be used for domain-based ad-blocking while 'no-script-mode' disables JavaScript.

Each buffer has separate instances of modes, which means that altering the settings of a mode in a buffer does not impact other buffers. Mode specific functions/commands are only available when a mode is enabled for the current buffer.

Each mode has an associated mode toggler which is a command of the same name that toggles the mode for the current buffer.

Prompt buffer


The prompt buffer is a menu that will appear when a command requests user input. For example, when invoking the set-url command, you must supply the URL you would like to navigate to. The prompt buffer can provide suggestions. The list of suggestions will automatically narrow down to those matching your input as you type.

Some commands support marks, for instance delete-buffer can delete all selected buffers at once. When the input is changed and the suggestions are re-filtered, the marks are not altered even if the marked suggestions aren't visible.

When at least one suggestion is marked, only the marked suggestions are processed upon return. The suggestion under the cursor is not processed if not marked.

Message area


The message area represents a space (typically at the bottom of a window) where Nyxt outputs messages back to you. To view the history of all messages, invoke the command list-messages (unbound).

Status buffer


The status buffer is where information about the state of that buffer is printed (typically at the bottom of a window). By default, this includes the active modes, the URL, and the title of the current buffer.

Basic controls


Moving within a buffer


To move within a buffer, several commands are provided:

Setting the URL


When ambiguous URLs are inputted, Nyxt will attempt the best guess it can. If you do not supply a protocol in a URL, HTTPS will be assumed. To visit a site supporting only the less secure HTTP, you must explicitly type the full URL including the 'http://' prefix.

Switching buffers


Copy and paste


Unlike other web browsers, Nyxt provides powerful ways of copying and pasting content via different commands. Starting from:

Passing through webpage's data:

Leveraging password managers:

And more:

Using the buffer history


History is represented as a tree that you can traverse: when you go back in history, then follow a new URL, it effectively creates a new branch without deleting the old path. The tree makes sure you never lose track of where you've been.

You can also view a full tree of the history for a given buffer by invoking the command 'buffer-history-tree'.



The bookmark file is made to be human readable and editable. Bookmarks can have the following settings:

  • :url: The URL of the bookmark.
  • :title: The title of the bookmark.
  • :tags: A list of strings. Useful to categorize and filter bookmarks.

Bookmark-related commands



Annotations can have the following settings:

  • snippet: The snippet which was highlighted by the user.
  • url: The URL of the annotation.
  • page-title: The title of the annotation.
  • data: The comment about the highlighted snippet or the URL.
  • tags: A list of strings. Useful to categorize and filter annotations.

Annotate-related commands

Passthrough mode


The command passthrough-mode forwards all keys to the renderer. For instance, using the default binding of Nyxt ( web-cua-map) the keybinding C-i executes autofill. Suppose a user is using their email client which also uses C-i for the italic command. Thus, after executing passthrough-mode the C-i binding is associated with the webpage's italic command instead of autofill. Finally, the user can return to their configuration just by executing passthrough-mode again.

Enable, disable, and toggle multiple modes


The command enable-modes (unbound) allows the user to apply multiple modes (such as nosound-mode and dark-mode) to multiple buffers at once. Conversely, it is possible to revert this action by executing disable-modes (unbound) while choosing exactly the same buffers and modes previously selected. Finally, toggle-mode also allows activation and deactivation of multiple modes, but only for the current buffer.

Light navigation


Reduce bandwidth usage via:

It is possible to enable these three modes at once with: reduce-bandwidth-mode.

Structural navigation


It is possible to navigate through headings:

And navigate to interconnected files:

Spelling check


Several commands are provided to spell check words. The default is English but it is possible to change the slot spell-check-language for other languages:

Visual mode


Select text without a mouse. Nyxt's visual-mode imitates Vim's visual mode (and comes with the CUA and Emacs-like keybindings out of the box, too). Activate it with the visual-mode (unbound) command.

Visual mode provides the following commands:

Commands designed to ease the use for CUA users (but available to all users):

A note for emacs-mode users: unlike in Emacs, in Nyxt the command toggle-mark (unbound) is bound to Shift-space, as C-space is bound to 'execute-command, overriding any mode keybinding. If you want to toggle mark with C-space, you'll need to set your own override-map such that C-space is not bound. An example:

      (define-configuration input-buffer
    (let ((map (make-keymap "override-map")))
      (define-key map "M-x" 'execute-command)))))



Nyxt has many facilities for automation. For instance, it is possible to automate the reading experience:

Symmetrically, it is possible to automate the filling of forms:

In addition, it is possible to automate actions over time:

Or even automate actions based on conditions:

Nyxt also offers a no-code interface to build automation via Common Lisp macros:

Lastly, the process-mode must be highlighted:

process-mode is actually a building block for other modes previously mentioned, such as repeat-mode. The extension relationship goes further, since cruise-control-mode is in its turn an extension and a composition of repeat-mode and scroll-down (keypaddown). Further extensions and compositions can be creatively tailor-made by users to automate their own use of Nyxt.



The Nyxt help system


Nyxt provides introspective and help capabilities. All commands, classes, slots, variables, functions and bindings can be inspected for definition and documentation.

A good starting point is to study the documentation of the classes browser, window, buffer and prompt-buffer.



Nyxt is written in the Common Lisp programming language which offers a great perk: everything in the browser can be customized by the user, even while it's running!

To get started with Common Lisp, we recommend checking out our web page: Learn Lisp. It contains numerous pointers to other resources, including free books both for beginners and seasoned programmers.

Nyxt provides a mechanism for new users unfamiliar with Lisp to customize Nyxt. Start by invoking the commands describe-class (f1 C) or describe-slot (f1 s). You can press the button marked 'Configure' to change the value of a setting. The settings will be applied immediately and saved for future sessions. Please note that these settings will not alter existing object instances.

Settings created by Nyxt are stored in /home/doe/.config/nyxt/auto-config.3.lisp.

Any settings can be overridden manually by /home/doe/.config/nyxt/config.lisp.

The following section assumes knowledge of basic Common Lisp or a similar programming language.

The user needs to manually create the Nyxt configuration file, and the parent folders if necessary.


    (define-configuration web-buffer
    (pushnew 'nyxt/mode/no-script:no-script-mode %slot-value%))))

The above turns on the 'no-script-mode' (disables JavaScript) by default for every buffer.

The define-configuration macro can be used to customize the slots of classes like the browser, buffers, windows, etc. Refer to the class and slot documentation for the individual details.

To find out about all modes known to Nyxt, run describe-command (f1 c) and type 'mode'.

Slot configuration


Slot values can be queried and tweaked, enabling many customization possibilities. For instance, slot zoom-ratio-default has the default value of 1.0. Follow the steps below to tweak it:

  1. Execute command describe-slot (f1 s);
  2. Type zoom-ratio-default to select the one from document-buffer;
  3. Press the Configure button;
  4. Type the desired value, for instance, 1.3.
  5. After restarting Nyxt, every page will be zoomed accordingly.

There are plenty of customizable slots and these can be discovered by inspecting classes via describe-class (f1 C).

Different types of buffers


There are multiple buffer classes, such as document-buffer (for structured documents) and input-buffer (for buffers that can receive user input). A web-buffer class is used for web pages, prompt-buffer for, well, the prompt buffer. Some buffer classes may inherit from multiple other classes. For instance web-buffer and prompt-buffer both inherit from input-buffer.

You can configure one of the parent buffer classes slots and the new values will automatically cascade down as a new default for all child classes- unless this slot is specialized by these child classes. For instance if you configure the override-map slot in input-buffer, both panel-buffer and web-buffer classes will inherit from the new value.

Keybinding configuration


Nyxt supports multiple bindings schemes such as CUA (the default), Emacs or vi. Changing scheme is as simple as setting the corresponding mode as default, e.g. emacs-mode. To make the change persistent across sessions, add the following to your configuration:

You can create new scheme names with make-keyscheme. Also see the define-keyscheme-map macro.

To extend the bindings of a specific mode, you can configure the mode with define-configuration and extend its keyscheme-map with define-keyscheme-map. For example:

    (define-configuration base-mode
  "Note the :import part of the define-keyscheme-map.
It re-uses the other keymap (in this case, the one that was slot value before
the configuration) and merely adds/modifies it."
    (define-keyscheme-map "my-base" (list :import %slot-value%)
                          (list "g b"
                                (lambda-command switch-buffer*
                                  (switch-buffer :current-is-last-p

The override-map is a keymap that has priority over all other keymaps. By default, it has few bindings like the one for execute-command (Ctrl+space). You can use it to set keys globally:

    (define-configuration input-buffer
    (let ((map (make-keymap "override-map")))
      (define-key map "M-x" 'execute-command "C-space" 'nothing)))))

The nothing (unbound) command is useful to override bindings to do nothing. Note that it's possible to bind any command, including those of disabled modes that are not listed in execute-command (Ctrl+space). Binding to nothing (unbound) and binding to NIL means different things (see the documentation of define-key for details):

nothing (unbound)
Binds the key to a command that does nothing. Still discovers the key and recognizes it as pressed.
Un-binds the key, removing all the bindings that it had in a given mode/keyscheme-map. If you press the un-bound key, the bindings that used to be there will not be found anymore, and the key will be forwarded to the renderer.
Any other symbol/command
Replaces the command that was there before, with the new one. When the key is pressed, the new command will fire instead of the old one.

In addition, a more flexible approach is to create your own mode with your custom keybindings. When this mode is added first to the buffer mode list, its keybindings have priorities over the other modes. Note that this kind of global keymaps also have priority over regular character insertion, so you should probably not bind anything without modifiers in such a keymap.

    (defvar *my-keymap* (make-keymap "my-map"))

(define-key *my-keymap* "C-f" 'nyxt/mode/history:history-forwards
            "C-b" 'nyxt/mode/history:history-backwards)

(define-mode my-mode
  "Dummy mode for the custom key bindings in *my-keymap*."
    (nkeymaps/core:make-keyscheme-map keyscheme:cua *my-keymap*
                                      keyscheme:emacs *my-keymap*

(define-configuration web-buffer
  "Enable this mode by default."
  ((default-modes (pushnew 'my-mode %slot-value%))))

Bindings are subject to various translations as per *translator*. By default if it fails to find a binding it tries again with inverted shifts. For instance if C-x C-F fails to match anything C-x C-f is tried.See the default value of *translator* to learn how to customize it or set it to nil to disable all forms of translation.

Search engines


See the search-engines buffer slot documentation. Bookmarks can also be used as search engines, see the corresponding section.

Nyxt comes with default search engines for,, The following example shows one way to add new search engines.

    (defvar *my-search-engines*
   '("google" "" "")
   '("python3" ""
   '("doi" "" ""))
  "List of search engines.")

(define-configuration context-buffer
  "Go through the search engines above and make-search-engine out of them."
     (mapcar (lambda (engine) (apply 'make-search-engine engine))

Note that the last search engine is the default one. For example, in order to make python3 the default, the above code can be slightly modified as follows.

    (defvar *my-search-engines*
   '("google" "" "")
   '("doi" "" "")
   '("python3" ""
  "List of search engines.")

(define-configuration context-buffer
  "Go through the search engines above and make-search-engine out of them."
    (append %slot-default%
             (lambda (engine) (apply 'make-search-engine engine))

If you don't want to use make-search-engine and want to try building the engines yourself, you can always make new search-engine and add it to search-engines list:

    (define-configuration context-buffer
  "Add a single search engine manually."
     (make-instance 'search-engine :name "Reddit" :shortcut "r"
                    :search-url ""
                    :fallback-url "")



Nyxt history model is a tree whose nodes are URLs. It branches out through all the buffers. If you create a new buffer (via follow-hint-new-buffer (unbound) or make-buffer (unbound)), it becomes a new history branch originating from the branch of the previous buffer.

History can be navigated with the arrow keys in the status buffer, or with commands like history-backwards (unbound) and history-forwards (unbound) (which the arrows are bound to).

If the beyond-buffer-boundaries behavior sounds like too much to you, or you prefer the behavior of Nyxt 2, where the history was still a tree, but was not spilling across the buffers, then configure global-history-p to be NIL:

     (define-configuration :context-buffer
  (global-history-p nil))
This would make all buffers to have their own history, not connected to the other buffers at all. All the history commands (like history-backwards (unbound) and history-forwards (unbound)) will only work inside the buffer history then.

Nyxt supra-buffer history has benefits, though: it optimizes browsing patterns into more intuitive and productive structures. One particular pattern Nyxt history optimizes is hub-and-spoke search, where you keep returning to a certain hub to start your search/navigation from a familiar point. You can enable the optimization (merely going back in history to the hub page, instead of creating a new history node) for this strategy by configuring backtrack-to-hubs-p to T.

Another useful side to Nyxt tree-like history are braching-aware history commands, like history-forwards-query (unbound), allowing one to choose which branch of history they are going to visit, if there are several. If there's only one branch, then this command behaves much like regular history-forwards (unbound).

There are commands that allow to move across all the history before or after the current node:

If you need to know more: most of the optimizations and data structures are in history-tree library, while most of the Nyxt-specific interface is in nyxt/mode/history-tree.



See the list-downloads (Ctrl+Shift+Y) command and the download-path buffer slot documentation.

Proxy and Tor


See the proxy-mode documentation.

Blocker mode


This mode blocks access to websites related to specific hosts. To see all hosts being blocked, execute command describe-variable, choose variable NYXT/MODE/BLOCKER:*DEFAULT-HOSTLIST*, and read data on nyxt/mode/blocker:url-body slot. To customize host blocking, read the blocker-mode documentation.



You can configure which actions to take depending on the URL to be loaded. For instance, you can configure which Torrent program to start to load magnet links. See the url-dispatching-handler function documentation.

Auto rules


Auto-rules toggle modes when the URL satisfies the given conditions. URL-dispatchers can also be used for this, but it is simpler to use an auto-rule. Given that Nyxt's functionality is mode-based, the consequences are far reaching.

These can be used in the following ways:

All rules are stored at /home/doe/.local/share/nyxt/auto-rules.lisp, which is meant to be human-readable and human-writable. You can find instructions at the top of it. The gist is that rules are mere Lisp lists which start with a condition that checks the URL. When conditions are met, modes are toggled. Besides user-defined conditions, the following are often useful:

By default, apply-all-matching-auto-rules-p is nil meaning that only the most specific rules are honored.

Auto-rules can also be defined for custom use-cases via define-auto-rule and un-defined with undefine-auto-rule.

Custom commands


Creating your own invocable commands is similar to creating a Common Lisp function, except the form is define-command instead of defun. If you want this command to be invocable outside of the context of a mode, use define-command-global.


    (define-command-global my-bookmark-url
  "Query which URL to bookmark."
  (let ((url
         (prompt :prompt "Bookmark URL" :sources
    (nyxt/mode/bookmark:bookmark-add url)))

See the prompt-buffer class documentation for how to write custom prompt buffers.

You can also create your own context menu entries binding those to Lisp commands, using ffi-add-context-menu-command function. You can bind the bookmark-url like this:

    (ffi-add-context-menu-command 'my-bookmark-url "Bookmark URL")

Currently, context menu commands don't have access to the renderer objects (and shouldn't hope to). Commands you bind to context menu actions should deduce most of the information from their surroundings, using JavaScript and Lisp functions Nyxt provides. For example, one can use the url-at-point to get thep URL currently under pointer.

With this, one can improve the bookmarking using url-at-point:

 (lambda ()
   (nyxt/mode/bookmark:bookmark-add (url-at-point (current-buffer))))
 "Bookmark Link")

Custom URL schemes


If there's a scheme that Nyxt doesn't support, but you want it to, you can always define the handler for this scheme so that it's Nyxt-openable.

As a totally hypothetical example, you can define a nonsense scheme bleep to generate a page with random text:

    (define-internal-scheme "bleep"
                        (lambda (url buffer)
                             (:h1 "Bleep bloop?")
                              (loop repeat (parse-integer
                                             (url url))
                                            :junk-allowed t)
                                    collect (:li
                                             (elt '("bleep" "bloop")
                                                  (random 2))))))
                        :local-p t)

What this piece of code does is

  • Define a new scheme.
  • Make a handler for it that takes the URL (as a string) and a buffer it's being opened in.
  • Read the path (the part after the bleep:) of the URL and interpret it as a number.
    • (Note that you need to wrap the URL into a url call so that it turns into a uri for the convenience of path (and other elements) fetching.)
  • Generate a random list of "bleep" and "bloop".
  • Return it as a text/html content.

The next time you run Nyxt and open bleep:20, you'll see a list of twenty bleeps and bloops.

Internal schemes can return any type of content (both strings and arrays of bytes are recognized), and they are capable of being CORS-enabled, protected, and are in general capable of whatever the renderer-provided schemes do.

nyxt: URLs and internal pages


You can create pages out of Lisp commands, and make arbitrary computations for the content of those. More so: these pages can invoke Lisp commands on demand, be it on button click or on some page event. The macros and functions to look at are:

Using the facilities Nyxt provides, you can make a random number generator page:

      (define-internal-page-command-global random-number
    (&key (max 1000000))
    (buffer "*Random*")
  "Generates a random number on every reload."
    (:h1 (princ-to-string (random max)))
    (:button.button :onclick
        (:title "re-load/re-generate the random number")
        (reload-buffer buffer)))
     :title "Re-generate the random number again" "New number")))

Several things to notice here:

  • Internal page command is much like a regular command in being a Lisp function that you can call either from the REPL or from the execute-command (Ctrl+space) menu.
    • With one important restriction: internal page commands should only have keyword arguments. Other argument types are not supported. This is to make them invocable through the URL they are assigned. For example, when you invoke the random-number command you've written, you'll see the nyxt:nyxt-user:random-number?max=%1B1000000 URL in the status buffer. The keyword argument is being seamlessly translated into a URL query parameter.
    • There's yet another important restriction: the values you provide to the internal page command should be serializable to URLs. Which restricts the arguments to numbers, symbols, and strings, for instance.
  • Those commands should return the content of the page in their body, like internal schemes do.
  • If you want to return HTML, then with-html-string is your best friend, but no one restricts you from producing HTML in any other way, including simply writing it by hand ;)
  • nyxt/ps:lisp-eval is a Parenscript macro to request Nyxt to run arbitrary code. The signature is: ((&key (buffer '(nyxt:current-buffer)) title callback) &body form). You can bind it to a <button>'s onClick event, for example.

If you're making an extension, you might find other macros more useful. define-internal-page-command, for example, defines a command to only be visible when in the corresponding mode is enabled. Useful to separate the context-specific commands from the universally useful ( -global) ones. If there's a page that you'd rather not have a command for, you can still define it as:

      (define-internal-page not-a-command
    (:title "*Hello*" :page-mode 'base-mode)
  "Hello there!")

and use as:

      (buffer-load-internal-page-focus 'not-a-command)

See the slots and documentation of internal-page to understand what you can pass to define-internal-page.



Hooks provide a powerful mechanism to tweak the behavior of various events that occur in the context of windows, buffers, modes, etc.

A hook holds a list of handlers. Handlers are named and typed functions. Each hook has a dedicated handler constructor.

Hooks can be 'run', that is, their handlers are run according to the combination slot of the hook. This combination is a function of the handlers. Depending on the combination, a hook can run the handlers either in parallel, or in order until one fails, or even compose them (pass the result of one as the input of the next). The handler types specify which input and output values are expected.

To add or delete a hook, you only need to know a couple of functions:

  • handler a class to wrap hook handlers in.
  • add-hook (also known as hooks:add-hook) allows you to add a handler to a hook,for it to be invoked when the hook fires.
  • nhooks:on (also available as hooks:on) as a shorthand for the nhooks:add-hook.
  • remove-hook (also available as hooks:remove-hook) that removes the handler from a certain hook.
  • nhooks:once-on (also available as hooks:once-on) as a one-shot version of nhooks:on that removes the handler right after it's completed.

Many hooks are executed at different points in Nyxt, among others:

  • Global hooks, such as after-init-hook or after-startup-hook.
  • Window- or buffer-related hooks.
  • Commands :before and :after methods.
    • Try, for example, (defmethod set-url :after (&key (prefill-current-url-p t)) ...) to do something after the set-url finishes executing.
  • Modes 'enable' and 'disable' methods and their :before, :after, and :around methods.
  • Mode-specific hooks, like before-download-hook and after-download-hook for download.

For instance, if you want to force '' over '', you can set a hook like the following in your configuration file:

    (defun old-reddit-handler (request-data)
  (let ((url (url request-data)))
    (setf (url request-data)
            (if (search "" (quri.uri:uri-host url))
                 (setf (quri.uri:uri-host url) "")
                 (log:info "Switching to old Reddit: ~s"
                           (render-url url))

(define-configuration web-buffer
    (sera:add-hook %slot-default% 'old-reddit-handler))))

(See url-dispatching-handler for a simpler way to achieve the same result.)

Or, if you want to set multiple handlers at once,

    (define-configuration web-buffer
    (reduce #'sera:add-hook '(old-reddit-handler auto-proxy-handler)
            :initial-value %slot-default%))))

Some hooks like the above example expect a return value, so it's important to make sure we return request-data here. See the documentation of the respective hooks for more details.

Data paths and data profiles


Nyxt provides a uniform configuration interface for all data files persisted to disk (bookmarks, cookies, etc.). To each file corresponds a nyxt-file object. An nyxt-profile is a customizable object that helps define general rules for data storage. Both nyxt-file and nyxt-profile compose, so it's possible to define general rules for all files (even for those not known in advance) while it's also possible to specialize some data given an nyxt-profile.

The profile can be set from command line and from the configuration file.You can list all known profiles (including the user-defined profiles) with the --list-profiles command-line option.

The nyxt-files can be passed a hint from the --with-file command line option, but each nyxt-file and profile rules are free to ignore it.

When a path ends with the .gpg extension, by default your GnuPG key is used to decrypt and encrypt the file transparently. Refer to the GnuPG documentation for how to set it up.

Note that the socket and the initialization nyxt-paths cannot be set in your configuration (the socket is used before the initialization file is loaded). Instead you can specify these paths from their respective command-line option. You can instantiate a unique, separate Nyxt instance when you provide a new socket path. This is particularly useful in combination with profiles, say to develop Nyxt or extensions.

Example to create a development profile that stores all data in /tmp/nyxt and stores bookmark in an encrypted file:

    (define-class dev-profile (nyxt-profile)
              ((files:name :initform "nyxt-dev"))
              (:documentation "Development profile."))

(defmethod files:resolve ((profile dev-profile) (path nyxt-file))
  "Expand all data paths inside a temporary directory."
   (files:expand (make-instance 'nyxt-temporary-directory))
   (uiop/pathname:relativize-pathname-directory (call-next-method))))

(defmethod files:resolve ((profile dev-profile) (file history-file))
  "Persist history to default location."
  (files:resolve (global-profile) file))

(define-configuration web-buffer
  "Make new profile the default."
     (or (find-profile-class (getf *options* :profile))

Then you can start a separate instance of Nyxt using this profile with nyxt --profile dev --socket /tmp/nyxt.socket.

Password management


Nyxt provides a uniform interface to some password managers including KeepassXC and Password Store. The supported installed password manager is automatically detected.See the password-interface buffer slot for customization.

You may use the define-configuration macro with any of the password interfaces to configure them. Please make sure to use the package prefixed class name/slot designators within the define-configuration.

KeePassXC support


The interface for KeePassXC should cover most use-cases for KeePassXC, as it supports password database locking with

To configure KeePassXC interface, you might need to add something like this snippet to your config:

      (defmethod initialize-instance :after
           ((interface password:keepassxc-interface)
            &key &allow-other-keys)
  "It's obviously not recommended to set master password here,
as your config is likely unencrypted and can reveal your password to someone
peeking at the screen."
  (setf (password:password-file interface)
        (password:key-file interface) "/path/to/your/keyfile"
        (password:yubikey-slot interface) "1:1111"))

(define-configuration nyxt/mode/password:password-mode
    (make-instance 'password:keepassxc-interface))))

(define-configuration buffer
    (append (list 'nyxt/mode/password:password-mode) %slot-value%))))



Much of the visual style can be configured by the user. You can use the facilities provided by theme and browser theme slot. The simplest option would be to use a built-in theme:

    (define-configuration browser
  ((theme theme:+dark-theme+ :doc "Setting dark theme.
The default is theme:+light-theme+.")))

There's also an option of creating a custom theme. For example, to set a theme to a midnight-like one, you can add this snippet to your configuration file:

    (define-configuration browser
    (make-instance 'theme:theme :background-color "black"
                   :action-color "#37a8e4" :primary-color "#808080"
                   :secondary-color "darkgray" :text-color
                   "lightgray" :contrast-text-color "black")
    "You can omit the colors you like in default theme, and they will stay as they were.")))

This, on the next restart of Nyxt, will repaint all the interface elements into a dark-ish theme.

As a more involved theme example, here's how one can redefine most of the semantic colors Nyxt uses to be compliant with Solarized Light theme:

    (define-configuration browser
    (make-instance 'theme:theme :background-color "#eee8d5"
                   :action-color "#268bd2" :primary-color "#073642"
                   :secondary-color "#586e75" :success-color
                   "#2aa198" :warning-color "#dc322f"
                   :highlight-color "#d33682" :codeblock-color
                   "#6c71c4" :text-color "#002b36"
                   :contrast-text-color "#fdf6e3")
    "Covers all the semantic groups (warning-color, codeblock-color etc.)
Note that you can also define more nuanced colors, like warning-color+, so
that the interface gets even nicer. Otherwise Nyxt generates the missing colors
automatically, which should be good enough... for most cases.")))

As an alternative to the all-encompassing themes, you can alter the style of every individual class controlling Nyxt interface elements. All such classes have a style slot that you can configure with your own CSS like this:

    (define-configuration nyxt/mode/style:dark-mode
    (theme:themed-css (theme *browser*)
      `(* :background-color ,theme:background "!important"
          :background-image none "!important" :color "red"
      `(a :background-color ,theme:background "!important"
        :background-image none "!important" :color "#AAAAAA"
  "Notice the use of theme:themed-css for convenient theme color injection.")

This snippet alters the style of Nyxt dark mode to have a more theme-compliant colors, using the theme:themed-css macro (making all the theme colors you've configured earlier available as variables like theme:on-primary.)

Status buffer appearance


You can customize the layout and styling of status-buffer using the methods it uses for layout. These methods are:

General layout of the status buffer, including the parts it consists of.
The ("Back", "Forward", "Reload") buttons section.
The current URL display section.
Tab listing.
List of modes.

To complement the layout produced by these format-* functions, you might need to add more rules or replace the style of status buffer.



You can evaluate code from the command line with --eval and --load. From a shell:

    $ nyxt --no-config --eval '+version+' 
  --load my-lib.lisp --eval '(format t "Hello ~a!~&" (my-lib:my-world))'

You can evaluate multiple --eval and --load in a row, they are executed in the order they appear.

You can also evaluate a Lisp file from the Nyxt interface with the load-file (Ctrl+O) command. For convenience, load-config-file (unbound) (re)loads your initialization file.

You can even make scripts. Here is an example foo.lisp:

exec nyxt --script "$0"

;; Your code follows:
(format t "~a~&" +version+)

--eval and --load can be commanded to operate over an existing instance instead of a separate instance that exits immediately.

The remote-execution-p of the remote instance must be non-nil:

    (define-configuration browser
  ((remote-execution-p t)))

To let know a private instance of Nyxt to load a foo.lisp script and run its foofunction:

    nyxt --profile nosave --remote --load foo.lisp --eval '(foo)' --quit

Note that --quitat the end of each Nyxt CLI call here. If you don't provide --quit when dealing with a remote instance, it will go into a REPL mode, allowing an immediate communication with an instance:

   nyxt --remote
(echo "~s" (+ 1 2)) ;; Shows '3' in the message area of remote Nyxt

User scripts


User scripts are a conventional and lightweight way to run arbitrary JavaScript code on some set of pages/conditions. While not as powerful as either WebExtensions on Lisp-native extensions to Nyxt, those hook into the tenderer inner working and allow you to change the page and JavaScript objects associated to it.

As an example, you can remove navbars from all the pages you visit with this small configuration snippet (note that you'd need to have user-script-mode in your buffer default-modes ):

    (define-configuration web-buffer
  "Enable user-script-mode, if you didn't already."
    (pushnew 'nyxt/mode/user-script:user-script-mode %slot-value%))))

(define-configuration nyxt/mode/user-script:user-script-mode
     (make-instance 'nyxt/mode/user-script:user-script :code
                    "// ==UserScript==
                              // @name          No navbars!
                              // @description	A simple script to remove navbars
                              // @run-at        document-end
                              // @include       http://*/*
                              // @include       https://*/*
                              // @noframes
                              // ==/UserScript==

                              var elem = document.querySelector(\"header\") || document.querySelector(\"nav\");
                              if (elem) {
    :doc "Alternatively, save the code to some file and use
:base-path #p\"/path/to/our/file.user.js\".
Or fetch a remote script with
url (quri:uri \"\")")))

Greasemonkey documentation lists all the possible properties that a user script might have. To Nyxt implementation, only those are meaningful:

@include and include
Sets the URL pattern to enable this script for. Follows the pattern scheme://host/path, where scheme is either a literal scheme or and asterisk (matching any scheme), and host and path are any valid characters plus asterisks (matching any set of characters) anywhere.
Same as @include.
@exclude and exclude
Similar to @include, but rather disables the script for the matching pages.
@noframes and all-frames-p
When present, disables the script for all the frames but toplevel ones. When absent, injects the script everywhere. The Lisp-side all-frames-pworks in an opposite way.
@run-at and run-at
When to run a script. Allowed values: document-start, document-end, document-idle (in Nyxt implementation, same as document-end).
Allows including arbitrary JS files hosted on the Internet or loaded from the same place as the script itself. Neat for including some JS libraries, like jQuery.

Headless mode


Similarly to Nyxt's scripting functionality, headless mode runs without a graphical user interface. Possible use-cases for this mode are web scraping, automations and web page analysis.

To enable headless mode, simply start Nyxt with the --headless CLI flag and provide a script file to serve as the configuration file:

    nyxt --headless --config /path/to/your/headless-config.lisp

Note that you pass it a configuration file—headless mode is only different from the regular Nyxt functions in that it has no GUI, and is all the same otherwise, contrary to all the seeming similarities to the --script flag usage.

The example below showcases frequent idioms that are found in the mode's configuration file:

exec nyxt --headless --no-auto-config --profile nosave --config "$0"

(define-configuration browser
  "Disable session restoration to speed up startup and get more reproducible
  ((restore-session-on-startup-p nil)))

(define-configuration browser
  "Load the URL of Nyxt repository by default in all new buffers.
Alternatively, call buffer-load in after-startup-hook."
    (quri.uri:uri ""))))

(hooks:on (after-startup-hook *browser*) (browser)
  ;; Once the page's done loading, do your thing.
  (hooks:once-on (buffer-loaded-hook (current-buffer)) (buffer)
    ;; It's sometimes necessary to sleep, as `buffer-loaded-hook' fires when the
    ;; page is loaded, which does not mean that all the resources and scripts
    ;; are done loading yet. Give it some time there.
    (sleep 0.5)
    ;; All the Nyxt reporting happens in headless mode, so you may want to log
    ;; it with `echo' and `echo-warning'.
    (echo "Nyxt GitHub repo open.")
    ;; Updating the `document-model' so that it includes the most relevant
    ;; information about the page.
    ;; Click the star button.
     (elt (clss:select "[aria-label=\"Star this repository\"]"
                       (document-model buffer))
    (echo "Clicked the star.")
    ;; It's good tone to `nyxt:quit' after you're done, but if you use nyxt
    ;; --no-socket, you don't have to. Just be ready for some RAM eating :)

The contents of headless-config.lisp feature configuration forms that make Nyxt perform some actions to the opened pages and/or on certain hooks. Things you'd most probably want to put there are:

  • Hook bindings, using the nhooks library and hooks provided by Nyxt.
  • Operations on the page. Check the nyxt/dom library and the document-model method.
    • The document-model method has a reasonably fresh copy of the page DOM (Document Object Model, reflecting the dynamic structure of the page). It is a Plump DOM, which means that all Plump (and CLSS) functions can be used on it.
    • update-document-model is a function to force DOM re-parsing for the cases when you consider the current document-model too outdated.
    • select is a CLSS function to find elements using CSS selectors (a terse notation for web page element description).
    • clss:ordered-select is the same as select, except it guarantees that all the elements are returned in a depth-first traversal order.
    • click-element to programmatically click a certain element (including the ones returned by select.)
    • focus-select-element to focus an input field, for example.
    • check-element to check a checkbox or a radio button.
    • select-option-element to select an option from the <select> element options.

Additionally, headless mode gracefully interacts with other CLI toggles the Nyxt has:

  • --headless itself! Notice that you can debug your script by omitting this CLI flag. When you're confident enough about it, put it back in. A good debugging tip, isn't it?
  • --no-socket flag allows starting as many Nyxt instances as your machine can handle. Useful to parallelize computations.
  • --profile nosave to not pollute your history and cache with the script-accessed pages.

Built-in REPL


Nyxt has a built-in REPL, available with repl (unbound) command.The REPL can be used to try out some code snippets for automation or quickly make some Lisp calculations. All the packages Nyxt depends on are available in REPL with convenient nicknames, and all the code is evaluated in nyxt-user package.

Once the REPL is open, there's only one input cell visible. This cell, always present at the bottom of the screen, adds new cells to the multi-pane interface of Nyxt REPL. You can type in (print "Hello, Nyxt!") and press C-return to evaluate the cell. A new cell will appear at the top of the buffer, with input area containing familiar code, with some v332 = "Hello, Nyxt!" variable assignment, and with a verbatim text outputted by your code:

   Hello, Nyxt!

This cell-based code evaluation is the basis of the Nyxt REPL. For more features, see REPL mode documentation.

Extending the REPL


Nyxt REPL is made to be extensible and allow to make custom cell types with their own display and functionality. The two cell types provided by default are:

Both of these serve as examples of cell extension, but it may be more illuminating to create one of a different type from the default ones. A commentary cell, for example—the type of cell one can use as an annotation to other cells.

First, define a new cell type:

      (define-class comment-cell (nyxt/mode/repl:cell)
              ((name "Commentary")) (:export-class-name-p t)
              (:export-accessor-names-p t))

There are methods of cell that can be redefined for a better display:

  • evaluate as a function to get results from the cell.
  • suggest for choosing the most intuitive content to paste into the cell.
  • render-input as the one rendering the input area for the cell.
  • render-actions, allowing to render a custom set of actions.
  • And render-results to show cell results (the immediate values evaluation returns) and output (the text printed out while evaluating the cell).
  • And, in case none of those fits well, render-cell to override all the rendering code.

The easiest thing is render-input: it's already defined as an input field updating the cell input at every keypress. The only change to it that is needed for the commentary cell is to render as a <pre> tag when marked ready-p:

      (defmethod nyxt/mode/repl:render-input ((cell comment-cell))
  "Render as a pre tag when ready, render as default input otherwise."
  (if (nyxt/mode/repl:ready-p cell)
        (:pre (input cell)))

render-results is another method useless for comment cell. It can safely return the empty string:

      (defmethod nyxt/mode/repl:render-results ((cell comment-cell))
  (declare (ignore cell))

render-actions should stay as it is, because it produces an aesthetic set of buttons near the input. No need to tweak it in this case.

Last but not least, evaluate. This method should produce the results and output of the cell. The REPL infrastructure sets ready-pto T when the evaluation ends, which enables the <pre> rendering. Given that comment cells produce no result evaluate can stay empty:

      (defmethod nyxt/mode/repl:evaluate ((cell comment-cell))
  (declare (ignore cell)))

After all of these methods are defined in the configuration file and Nyxt restarted, invoking repl (unbound) and pressing the Add a cell button will allow to create a new comment cell.

Note that the display of the comment cell is not exactly concise. But making it better is left as an exercise to the reader.

Advanced configuration


While define-configuration is convenient, it is mostly restricted to class slot configuration. If you want to do anything else on class instantiation, you'll have to specialize the lower-level customize-instance generic function. Example:

    (defmethod customize-instance ((buffer buffer) &key)
  (echo "Buffer ~a created." buffer))

All classes with metaclass user-class call customize-instance on instantiation, after initialize-instance :after. The primary method is reserved to the user, however the :after method is reserved to the Nyxt core to finalize the instance.



To install an extension, copy inside the *extensions-directory* (default to ~/.local/share/nyxt/extensions).

Extensions are regular Common Lisp systems.

A catalog of extensions is available in the document/ file in the source repository.



Debugging and reporting errors


If you experience hangs or errors you can reproduce, you can use the toggle-debug-on-error (unbound) command to enable Nyxt-native debugger and see the reasons of these. Based on this information, you can report a bug using report-bug (unbound).

You can also try to start the browser with the --failsafe command line option and see if you can reproduce your issue then. If not, then the issue is most likely due to your configuration, an extension, or some corrupt data file like the history.

Note that often errors, hangs, and crashes happen on the side of renderer and thus are not visible to the Nyxt-native debugger and fixable on the side of Nyxt. See below.

Playing videos


Nyxt delegates video support to third-party plugins.

When using the WebKitGTK backends, GStreamer and its plugins are leveraged. Depending on the video, you will need to install some of the following packages:

  • gst-libav
  • gst-plugins-bad
  • gst-plugins-base
  • gst-plugins-good
  • gst-plugins-ugly

On Debian-based systems, you might be looking for (adapt the version numbers):

  • libgstreamer1.0-0
  • gir1.2-gst-plugins-base-1.0

For systems from the Fedora family:

  • gstreamer1-devel
  • gstreamer1-plugins-base-devel

After the desired plugins have been installed, clear the GStreamer cache at ~/.cache/gstreamer-1.0 and restart Nyxt.

Website crashes


If some websites systematically crash, try to install all the required GStreamer plugins as mentioned in the 'Playing videos' section.

Input method support (CJK, etc.)


Depending on your setup, you might have to set some environment variables or run some commands before starting Nyxt, for instance

ibus --daemonize --replace --xim

You can persist this change by saving the commands in your .xprofile or similar.

HiDPI displays


The entire UI may need to be scaled up on HiDPI displays.

When using the WebKitGTK renderer, export the environment variable below before starting Nyxt. Note that GDK_DPI_SCALE (not to be confused with GDK_SCALE) scales text only, so tweaking it may be undesirable.

     export GDK_SCALE=2

StumpWM mouse scroll


If the mouse scroll does not work for you, see the StumpWM FAQ for a fix.

Blank WebKit web-views


If you are experiencing problems with blank web-views on some sites you can try to disable compositing. To disable compositing from your initialization file, you can do the following:

      (setf (uiop/os:getenv "WEBKIT_DISABLE_COMPOSITING_MODE") "1")

Missing cursor icons


If you are having issues with the cursor not changing when hovering over buttons or links, it might be because Nyxt can't locate your cursor theme. To fix that, try adding the following to your .bash_profile or similar:

      export XCURSOR_PATH=${XCURSOR_PATH}:/usr/share/icons
export XCURSOR_PATH=${XCURSOR_PATH}:~/.local/share/icons