---
title: "Extending Guix"
slug: extending-guix
url: https://listedarticles.com/articles/extending-guix
canonical_url: https://guix.gnu.org/en/blog/2026/extending-guix/
content_type: tutorial
language: en
published_at: 2026-10-08T00:00:00.000Z
updated_at: 2026-10-09T11:17:16.964Z
author: "Sergio Pastor Pérez"
authored_by: human
publisher: "GNU Guix"
publisher_url: https://guix.gnu.org/
topics: ["Open Source", "Linux", "Programming"]
license: CC-BY-SA-4.0
word_count: 1143
reading_minutes: 5
citation: "Sergio Pastor Pérez, GNU Guix. \"Extending Guix.\" 8 Oct 2026. https://guix.gnu.org/en/blog/2026/extending-guix/ (CC-BY-SA-4.0)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Extending Guix

> A GNU Guix blog tutorial on extending Guix with new guix commands: listing available extensions, the existing extension packages and meta-package, and walking through creating an extension's project directories and Scheme module so it can be distributed and used by others.

# Extending Guix

Guix is all about empowering people, so it should come as no surprise that [one
can extend it with new `guix`
commands](https://guix.gnu.org/manual/devel/en/html_node/Extending-Guix.html). You
can show the commands available in your current Guix through the help command:

`$ guix help`
If you are using a recent Guix, there are a number of extensions at your
disposal, they are available as individual packages, and as [a meta-package
providing a collection of
extensions](https://packages.guix.gnu.org/packages/guix-extension-collection). Try
them out with:

`$ guix shell guix guile guix-extension-collection`
I'm adding the `guix` and `guile` packages to the shell because we want the
different Guile and Guix search paths to be adjusted in the shell. To know
more about why this is needed, read [Search
Paths](https://guix.gnu.org/manual/devel/en/html_node/Search-Paths.html).


You will notice that the help menu has been extended with information about the available extensions:

```
$ guix help
...
  extension commands
    explore   interactively explore a Guix System configuration
    removals  keep up to date with package removals
    compose   docker compose compatibility layer
    toys      Explore packages and services through REST API
    xsearch   search for packages using a fast Xapian cache
...
```
Since this blog post is called "Extending Guix", let's write an extension, shall we?

# How does Guix locate extensions?

Guix locates extensions by looking up
`GUIX_EXTENSIONS_PATH`. [Recently](https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912)
Guix has introduced a new way of writing extensions. The old way would search
extensions under `/path/to/guix/extensions` and the extensions modules would be
named `(guix extensions NAME)`. The new schema expects extensions to be under
`/path/to/SCHEMA_VERSION`; the name of the module will still be `(guix extensions NAME)`.

The advantage of this new schema is the Guix can treat the extension module as a
standard Guile module, meaning that the runtime is able to find the compiled
`.go` file of the extension. The old schema was relaying on runtime evaluation;
the load machinery was not handling compiled files. This has a considerable
improvement on performance, so you are encouraged to update any old extension to
the new schema.

# Writing an extension

Enough introductions, let's write a basic extension.

The first step is to create the project structure. Remember that the extension
machinery expects modules to be named `(guix extensions NAME)`.

We start by creating, in our project root, the directories for the extension.

`$ mkdir -p guix/extensions`
Now, we create the file `guix/extensions/hello.scm` with the following contents:

`(define-module (guix extensions hello))`
A blank canvas...

We import `(guix scripts)` to get the `define-command` macro. We declare it and
export it so the extension machinery can find the command in the public
interface of the module.

```
(define-module (guix extensions hello)
  #:use-module (guix scripts)
  #:export (guix-hello))
(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (display "hello, I'm a Guix extension!\n"))
```
With this, we already have a working Guix extension. We can run the extension like this from the root of the project.

```
$ GUIX_EXTENSIONS_PATH=$PWD guix hello
hello, I'm a Guix extension!
```
Since we would like our extension to be discoverable by users, we will add a
help message which will make the extension display in the `guix help` menu.

We will use `(srfi srfi-37)` to write the option parser, and `(guix ui)` for the
internationalized strings; you will see that I import these modules when I show
you the complete extension. Let's focus on the option definitions:

```
(define (show-help)
  (display (G_ "Usage: guix hello\n"))
  (display (G_ "Just say hello, it's not that complicated.\n"))
  (display (G_ "
      --help             display this message"))
  (newline))
(define %options
  (list (option '(#\h "help") #f #f
                (lambda args
                  (leave-on-EPIPE (show-help))
                  (exit 0)))))
```
Since we are only handling the arguments for showing the help message, we just need to call the parser at the start of the command:

```
(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (parse-command-line args %options (list '()))
  (display "hello, I'm a Guix extension!\n"))
```
Here you have the complete extension module:

```
(define-module (guix extensions hello)
  #:use-module (guix scripts)
  #:use-module (guix ui)
  #:use-module (srfi srfi-37)
  #:export (guix-hello))
(define (show-help)
  (display (G_ "Usage: guix hello\n"))
  (display (G_ "Just say hello, it's not that complicated.\n"))
  (display (G_ "
      --help             display this message"))
  (newline))
(define %options
  (list (option '(#\h "help") #f #f
                (lambda args
                  (leave-on-EPIPE (show-help))
                  (exit 0)))))
(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (parse-command-line args %options (list '()))
  (display "hello, I'm a Guix extension!\n"))
```
With that, our extension will be present when we ask Guix for help:

```
$ GUIX_EXTENSIONS_PATH=$PWD guix help
...
  extension commands
    hello  say hello
```
It also takes a `--help` flag:

```
$ GUIX_EXTENSIONS_PATH=$PWD guix hello --help
Usage: guix hello
Just say hello, it's not that complicated.
      --help             display this message
```
# Packaging a Guix extension

At the time of writing, we don't have a dedicated [Guix
build-system](https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html)
for extensions. Fortunately, the
[`guile-build-system`](https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html#index-guile_002dbuild_002dsystem)
is close enough for this use case.

Let's make a package definition for our new hello extension that uses our local
sources. Create a `guix.scm` file at the root of the project with the following
contents:

```
(use-modules (gnu packages package-management)
             (guix build-system guile)
             (guix gexp)
             (guix git-download)
             (guix packages)
             ((guix licenses) #:prefix license:))
(define vcs-file?
  ;; Return true if the given file is under version control.
  (or
   (git-predicate
    (dirname (canonicalize-path
              (assq-ref (current-source-location) 'filename))))
   (const #t)))
(define-public guix-hello
  (package
    (name "guix-hello")
    (version "0.0.0-git")
    (source (local-file (assume-valid-file-name ".")
                        "pin-checkout"
                        #:recursive? #t
                        #:select? vcs-file?))
    (build-system guile-build-system)
    (arguments
     (list
      #:scheme-file-regexp
      #~(lambda (file stat)
          (and ((file-name-predicate #$default-scheme-file-regexp)
                file stat)
               (not ((file-name-predicate "^(guix|channels|manifest)\\.scm$")
                     file stat))))
      #:phases
      #~(modify-phases %standard-phases
          (add-after 'build 'move-to-extension-directory
            (lambda _
              (with-directory-excursion #$output
                (mkdir-p "share/guix/extensions/1.5/guix/extensions")
                (rename-file (string-append "share/guile/site/"
                                            (target-guile-effective-version)
                                            "/guix/extensions/hello.scm")
                             "share/guix/extensions/1.5/guix/extensions/hello.scm")))))))
    (native-inputs (list guix))
    (inputs (list (lookup-package-input guix "guile")))
    (home-page "https://codeberg.org/guix-extensions")
    (synopsis "Make Guix say hello")
    (description
     "This extension provides the @command{guix hello} command,
which makes Guix say hello.")
    (license license:gpl3+)))
guix-hello
```
We can test this new extension like this:

`$ guix shell -CW -f guix.scm -- guix hello`
The flags passed to `guix shell` are the following:

- `-C` (`--container` ): To prevent your environment from interfering.
- `-W` (`--nesting` ): To bring the current Guix you are using into the
container so it can load the extension.

Since this example is using the new extension scheme, the `guix` command you
are running must be at or newer than commit
[de069958fc](https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912).


# Closing words

I hope you find this small introduction to Guix extensions useful and that you start to write your own Guix extensions.

I would like to encourage everyone reading this to submit their extensions to
the [guix-extensions](https://codeberg.org/guix-extensions) Codeberg
organization. The idea is to make this organization a central hub for everyone
to participate in the development of useful extensions for the community.

Unless otherwise stated, blog posts on this site are
copyrighted by their respective authors and published under the terms of
the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license and those of the [GNU Free Documentation License](https://www.gnu.org/licenses/fdl-1.3.html) (version 1.3 or later, with no Invariant Sections, no
Front-Cover Texts, and no Back-Cover Texts).
