{"article":{"slug":"extending-guix","title":"Extending Guix","subtitle":null,"summary":"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.","content_type":"tutorial","language":"en","canonical_url":"https://guix.gnu.org/en/blog/2026/extending-guix/","author":{"name":"Sergio Pastor Pérez","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"GNU Guix","url":"https://guix.gnu.org/","listing_slug":null,"listing":null},"topics":[{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Linux","slug":"linux","url":"https://listedarticles.com/topics/linux"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"CC-BY-SA-4.0","word_count":1143,"reading_minutes":5,"published_at":"2026-10-08T00:00:00.000Z","added_at":"2026-10-09T11:17:16.964Z","updated_at":"2026-10-09T11:17:16.964Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":false},"profile_url":"https://listedarticles.com/articles/extending-guix","markdown_url":"https://listedarticles.com/articles/extending-guix.md","example":false,"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)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://guix.gnu.org/en/blog/2026/extending-guix/"},"body_markdown":"# Extending Guix\n\nGuix is all about empowering people, so it should come as no surprise that [one\ncan extend it with new `guix`\ncommands](https://guix.gnu.org/manual/devel/en/html_node/Extending-Guix.html). You\ncan show the commands available in your current Guix through the help command:\n\n`$ guix help`\nIf you are using a recent Guix, there are a number of extensions at your\ndisposal, they are available as individual packages, and as [a meta-package\nproviding a collection of\nextensions](https://packages.guix.gnu.org/packages/guix-extension-collection). Try\nthem out with:\n\n`$ guix shell guix guile guix-extension-collection`\nI'm adding the `guix` and `guile` packages to the shell because we want the\ndifferent Guile and Guix search paths to be adjusted in the shell. To know\nmore about why this is needed, read [Search\nPaths](https://guix.gnu.org/manual/devel/en/html_node/Search-Paths.html).\n\n\nYou will notice that the help menu has been extended with information about the available extensions:\n\n```\n$ guix help\n...\n  extension commands\n    explore   interactively explore a Guix System configuration\n    removals  keep up to date with package removals\n    compose   docker compose compatibility layer\n    toys      Explore packages and services through REST API\n    xsearch   search for packages using a fast Xapian cache\n...\n```\nSince this blog post is called \"Extending Guix\", let's write an extension, shall we?\n\n# How does Guix locate extensions?\n\nGuix locates extensions by looking up\n`GUIX_EXTENSIONS_PATH`. [Recently](https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912)\nGuix has introduced a new way of writing extensions. The old way would search\nextensions under `/path/to/guix/extensions` and the extensions modules would be\nnamed `(guix extensions NAME)`. The new schema expects extensions to be under\n`/path/to/SCHEMA_VERSION`; the name of the module will still be `(guix extensions NAME)`.\n\nThe advantage of this new schema is the Guix can treat the extension module as a\nstandard Guile module, meaning that the runtime is able to find the compiled\n`.go` file of the extension. The old schema was relaying on runtime evaluation;\nthe load machinery was not handling compiled files. This has a considerable\nimprovement on performance, so you are encouraged to update any old extension to\nthe new schema.\n\n# Writing an extension\n\nEnough introductions, let's write a basic extension.\n\nThe first step is to create the project structure. Remember that the extension\nmachinery expects modules to be named `(guix extensions NAME)`.\n\nWe start by creating, in our project root, the directories for the extension.\n\n`$ mkdir -p guix/extensions`\nNow, we create the file `guix/extensions/hello.scm` with the following contents:\n\n`(define-module (guix extensions hello))`\nA blank canvas...\n\nWe import `(guix scripts)` to get the `define-command` macro. We declare it and\nexport it so the extension machinery can find the command in the public\ninterface of the module.\n\n```\n(define-module (guix extensions hello)\n  #:use-module (guix scripts)\n  #:export (guix-hello))\n(define-command (guix-hello . args)\n  (category extension)\n  (synopsis \"say hello\")\n  (display \"hello, I'm a Guix extension!\\n\"))\n```\nWith this, we already have a working Guix extension. We can run the extension like this from the root of the project.\n\n```\n$ GUIX_EXTENSIONS_PATH=$PWD guix hello\nhello, I'm a Guix extension!\n```\nSince we would like our extension to be discoverable by users, we will add a\nhelp message which will make the extension display in the `guix help` menu.\n\nWe will use `(srfi srfi-37)` to write the option parser, and `(guix ui)` for the\ninternationalized strings; you will see that I import these modules when I show\nyou the complete extension. Let's focus on the option definitions:\n\n```\n(define (show-help)\n  (display (G_ \"Usage: guix hello\\n\"))\n  (display (G_ \"Just say hello, it's not that complicated.\\n\"))\n  (display (G_ \"\n      --help             display this message\"))\n  (newline))\n(define %options\n  (list (option '(#\\h \"help\") #f #f\n                (lambda args\n                  (leave-on-EPIPE (show-help))\n                  (exit 0)))))\n```\nSince we are only handling the arguments for showing the help message, we just need to call the parser at the start of the command:\n\n```\n(define-command (guix-hello . args)\n  (category extension)\n  (synopsis \"say hello\")\n  (parse-command-line args %options (list '()))\n  (display \"hello, I'm a Guix extension!\\n\"))\n```\nHere you have the complete extension module:\n\n```\n(define-module (guix extensions hello)\n  #:use-module (guix scripts)\n  #:use-module (guix ui)\n  #:use-module (srfi srfi-37)\n  #:export (guix-hello))\n(define (show-help)\n  (display (G_ \"Usage: guix hello\\n\"))\n  (display (G_ \"Just say hello, it's not that complicated.\\n\"))\n  (display (G_ \"\n      --help             display this message\"))\n  (newline))\n(define %options\n  (list (option '(#\\h \"help\") #f #f\n                (lambda args\n                  (leave-on-EPIPE (show-help))\n                  (exit 0)))))\n(define-command (guix-hello . args)\n  (category extension)\n  (synopsis \"say hello\")\n  (parse-command-line args %options (list '()))\n  (display \"hello, I'm a Guix extension!\\n\"))\n```\nWith that, our extension will be present when we ask Guix for help:\n\n```\n$ GUIX_EXTENSIONS_PATH=$PWD guix help\n...\n  extension commands\n    hello  say hello\n```\nIt also takes a `--help` flag:\n\n```\n$ GUIX_EXTENSIONS_PATH=$PWD guix hello --help\nUsage: guix hello\nJust say hello, it's not that complicated.\n      --help             display this message\n```\n# Packaging a Guix extension\n\nAt the time of writing, we don't have a dedicated [Guix\nbuild-system](https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html)\nfor extensions. Fortunately, the\n[`guile-build-system`](https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html#index-guile_002dbuild_002dsystem)\nis close enough for this use case.\n\nLet's make a package definition for our new hello extension that uses our local\nsources. Create a `guix.scm` file at the root of the project with the following\ncontents:\n\n```\n(use-modules (gnu packages package-management)\n             (guix build-system guile)\n             (guix gexp)\n             (guix git-download)\n             (guix packages)\n             ((guix licenses) #:prefix license:))\n(define vcs-file?\n  ;; Return true if the given file is under version control.\n  (or\n   (git-predicate\n    (dirname (canonicalize-path\n              (assq-ref (current-source-location) 'filename))))\n   (const #t)))\n(define-public guix-hello\n  (package\n    (name \"guix-hello\")\n    (version \"0.0.0-git\")\n    (source (local-file (assume-valid-file-name \".\")\n                        \"pin-checkout\"\n                        #:recursive? #t\n                        #:select? vcs-file?))\n    (build-system guile-build-system)\n    (arguments\n     (list\n      #:scheme-file-regexp\n      #~(lambda (file stat)\n          (and ((file-name-predicate #$default-scheme-file-regexp)\n                file stat)\n               (not ((file-name-predicate \"^(guix|channels|manifest)\\\\.scm$\")\n                     file stat))))\n      #:phases\n      #~(modify-phases %standard-phases\n          (add-after 'build 'move-to-extension-directory\n            (lambda _\n              (with-directory-excursion #$output\n                (mkdir-p \"share/guix/extensions/1.5/guix/extensions\")\n                (rename-file (string-append \"share/guile/site/\"\n                                            (target-guile-effective-version)\n                                            \"/guix/extensions/hello.scm\")\n                             \"share/guix/extensions/1.5/guix/extensions/hello.scm\")))))))\n    (native-inputs (list guix))\n    (inputs (list (lookup-package-input guix \"guile\")))\n    (home-page \"https://codeberg.org/guix-extensions\")\n    (synopsis \"Make Guix say hello\")\n    (description\n     \"This extension provides the @command{guix hello} command,\nwhich makes Guix say hello.\")\n    (license license:gpl3+)))\nguix-hello\n```\nWe can test this new extension like this:\n\n`$ guix shell -CW -f guix.scm -- guix hello`\nThe flags passed to `guix shell` are the following:\n\n- `-C` (`--container` ): To prevent your environment from interfering.\n- `-W` (`--nesting` ): To bring the current Guix you are using into the\ncontainer so it can load the extension.\n\nSince this example is using the new extension scheme, the `guix` command you\nare running must be at or newer than commit\n[de069958fc](https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912).\n\n\n# Closing words\n\nI hope you find this small introduction to Guix extensions useful and that you start to write your own Guix extensions.\n\nI would like to encourage everyone reading this to submit their extensions to\nthe [guix-extensions](https://codeberg.org/guix-extensions) Codeberg\norganization. The idea is to make this organization a central hub for everyone\nto participate in the development of useful extensions for the community.\n\nUnless otherwise stated, blog posts on this site are\ncopyrighted by their respective authors and published under the terms of\nthe [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\nFront-Cover Texts, and no Back-Cover Texts).","body_html":"<h1 id=\"extending-guix\">Extending Guix</h1>\n<p>Guix is all about empowering people, so it should come as no surprise that <a href=\"https://guix.gnu.org/manual/devel/en/html_node/Extending-Guix.html\" rel=\"nofollow ugc noopener\">one\ncan extend it with new <code>guix</code>\ncommands</a>. You\ncan show the commands available in your current Guix through the help command:</p>\n<p><code>$ guix help</code>\nIf you are using a recent Guix, there are a number of extensions at your\ndisposal, they are available as individual packages, and as <a href=\"https://packages.guix.gnu.org/packages/guix-extension-collection\" rel=\"nofollow ugc noopener\">a meta-package\nproviding a collection of\nextensions</a>. Try\nthem out with:</p>\n<p><code>$ guix shell guix guile guix-extension-collection</code>\nI&#39;m adding the <code>guix</code> and <code>guile</code> packages to the shell because we want the\ndifferent Guile and Guix search paths to be adjusted in the shell. To know\nmore about why this is needed, read <a href=\"https://guix.gnu.org/manual/devel/en/html_node/Search-Paths.html\" rel=\"nofollow ugc noopener\">Search\nPaths</a>.</p>\n<p>You will notice that the help menu has been extended with information about the available extensions:</p>\n<pre><code>$ guix help\n...\n  extension commands\n    explore   interactively explore a Guix System configuration\n    removals  keep up to date with package removals\n    compose   docker compose compatibility layer\n    toys      Explore packages and services through REST API\n    xsearch   search for packages using a fast Xapian cache\n...</code></pre>\n<p>Since this blog post is called &quot;Extending Guix&quot;, let&#39;s write an extension, shall we?</p>\n<h1 id=\"how-does-guix-locate-extensions\">How does Guix locate extensions?</h1>\n<p>Guix locates extensions by looking up\n<code>GUIX_EXTENSIONS_PATH</code>. <a href=\"https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912\" rel=\"nofollow ugc noopener\">Recently</a>\nGuix has introduced a new way of writing extensions. The old way would search\nextensions under <code>/path/to/guix/extensions</code> and the extensions modules would be\nnamed <code>(guix extensions NAME)</code>. The new schema expects extensions to be under\n<code>/path/to/SCHEMA_VERSION</code>; the name of the module will still be <code>(guix extensions NAME)</code>.</p>\n<p>The advantage of this new schema is the Guix can treat the extension module as a\nstandard Guile module, meaning that the runtime is able to find the compiled\n<code>.go</code> file of the extension. The old schema was relaying on runtime evaluation;\nthe load machinery was not handling compiled files. This has a considerable\nimprovement on performance, so you are encouraged to update any old extension to\nthe new schema.</p>\n<h1 id=\"writing-an-extension\">Writing an extension</h1>\n<p>Enough introductions, let&#39;s write a basic extension.</p>\n<p>The first step is to create the project structure. Remember that the extension\nmachinery expects modules to be named <code>(guix extensions NAME)</code>.</p>\n<p>We start by creating, in our project root, the directories for the extension.</p>\n<p><code>$ mkdir -p guix/extensions</code>\nNow, we create the file <code>guix/extensions/hello.scm</code> with the following contents:</p>\n<p><code>(define-module (guix extensions hello))</code>\nA blank canvas...</p>\n<p>We import <code>(guix scripts)</code> to get the <code>define-command</code> macro. We declare it and\nexport it so the extension machinery can find the command in the public\ninterface of the module.</p>\n<pre><code>(define-module (guix extensions hello)\n  #:use-module (guix scripts)\n  #:export (guix-hello))\n(define-command (guix-hello . args)\n  (category extension)\n  (synopsis &quot;say hello&quot;)\n  (display &quot;hello, I&#39;m a Guix extension!\\n&quot;))</code></pre>\n<p>With this, we already have a working Guix extension. We can run the extension like this from the root of the project.</p>\n<pre><code>$ GUIX_EXTENSIONS_PATH=$PWD guix hello\nhello, I&#39;m a Guix extension!</code></pre>\n<p>Since we would like our extension to be discoverable by users, we will add a\nhelp message which will make the extension display in the <code>guix help</code> menu.</p>\n<p>We will use <code>(srfi srfi-37)</code> to write the option parser, and <code>(guix ui)</code> for the\ninternationalized strings; you will see that I import these modules when I show\nyou the complete extension. Let&#39;s focus on the option definitions:</p>\n<pre><code>(define (show-help)\n  (display (G_ &quot;Usage: guix hello\\n&quot;))\n  (display (G_ &quot;Just say hello, it&#39;s not that complicated.\\n&quot;))\n  (display (G_ &quot;\n      --help             display this message&quot;))\n  (newline))\n(define %options\n  (list (option &#39;(#\\h &quot;help&quot;) #f #f\n                (lambda args\n                  (leave-on-EPIPE (show-help))\n                  (exit 0)))))</code></pre>\n<p>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:</p>\n<pre><code>(define-command (guix-hello . args)\n  (category extension)\n  (synopsis &quot;say hello&quot;)\n  (parse-command-line args %options (list &#39;()))\n  (display &quot;hello, I&#39;m a Guix extension!\\n&quot;))</code></pre>\n<p>Here you have the complete extension module:</p>\n<pre><code>(define-module (guix extensions hello)\n  #:use-module (guix scripts)\n  #:use-module (guix ui)\n  #:use-module (srfi srfi-37)\n  #:export (guix-hello))\n(define (show-help)\n  (display (G_ &quot;Usage: guix hello\\n&quot;))\n  (display (G_ &quot;Just say hello, it&#39;s not that complicated.\\n&quot;))\n  (display (G_ &quot;\n      --help             display this message&quot;))\n  (newline))\n(define %options\n  (list (option &#39;(#\\h &quot;help&quot;) #f #f\n                (lambda args\n                  (leave-on-EPIPE (show-help))\n                  (exit 0)))))\n(define-command (guix-hello . args)\n  (category extension)\n  (synopsis &quot;say hello&quot;)\n  (parse-command-line args %options (list &#39;()))\n  (display &quot;hello, I&#39;m a Guix extension!\\n&quot;))</code></pre>\n<p>With that, our extension will be present when we ask Guix for help:</p>\n<pre><code>$ GUIX_EXTENSIONS_PATH=$PWD guix help\n...\n  extension commands\n    hello  say hello</code></pre>\n<p>It also takes a <code>--help</code> flag:</p>\n<pre><code>$ GUIX_EXTENSIONS_PATH=$PWD guix hello --help\nUsage: guix hello\nJust say hello, it&#39;s not that complicated.\n      --help             display this message</code></pre>\n<h1 id=\"packaging-a-guix-extension\">Packaging a Guix extension</h1>\n<p>At the time of writing, we don&#39;t have a dedicated <a href=\"https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html\" rel=\"nofollow ugc noopener\">Guix\nbuild-system</a>\nfor extensions. Fortunately, the\n<a href=\"https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html#index-guile_002dbuild_002dsystem\" rel=\"nofollow ugc noopener\"><code>guile-build-system</code></a>\nis close enough for this use case.</p>\n<p>Let&#39;s make a package definition for our new hello extension that uses our local\nsources. Create a <code>guix.scm</code> file at the root of the project with the following\ncontents:</p>\n<pre><code>(use-modules (gnu packages package-management)\n             (guix build-system guile)\n             (guix gexp)\n             (guix git-download)\n             (guix packages)\n             ((guix licenses) #:prefix license:))\n(define vcs-file?\n  ;; Return true if the given file is under version control.\n  (or\n   (git-predicate\n    (dirname (canonicalize-path\n              (assq-ref (current-source-location) &#39;filename))))\n   (const #t)))\n(define-public guix-hello\n  (package\n    (name &quot;guix-hello&quot;)\n    (version &quot;0.0.0-git&quot;)\n    (source (local-file (assume-valid-file-name &quot;.&quot;)\n                        &quot;pin-checkout&quot;\n                        #:recursive? #t\n                        #:select? vcs-file?))\n    (build-system guile-build-system)\n    (arguments\n     (list\n      #:scheme-file-regexp\n      #~(lambda (file stat)\n          (and ((file-name-predicate #$default-scheme-file-regexp)\n                file stat)\n               (not ((file-name-predicate &quot;^(guix|channels|manifest)\\\\.scm$&quot;)\n                     file stat))))\n      #:phases\n      #~(modify-phases %standard-phases\n          (add-after &#39;build &#39;move-to-extension-directory\n            (lambda _\n              (with-directory-excursion #$output\n                (mkdir-p &quot;share/guix/extensions/1.5/guix/extensions&quot;)\n                (rename-file (string-append &quot;share/guile/site/&quot;\n                                            (target-guile-effective-version)\n                                            &quot;/guix/extensions/hello.scm&quot;)\n                             &quot;share/guix/extensions/1.5/guix/extensions/hello.scm&quot;)))))))\n    (native-inputs (list guix))\n    (inputs (list (lookup-package-input guix &quot;guile&quot;)))\n    (home-page &quot;https://codeberg.org/guix-extensions&quot;)\n    (synopsis &quot;Make Guix say hello&quot;)\n    (description\n     &quot;This extension provides the @command{guix hello} command,\nwhich makes Guix say hello.&quot;)\n    (license license:gpl3+)))\nguix-hello</code></pre>\n<p>We can test this new extension like this:</p>\n<p><code>$ guix shell -CW -f guix.scm -- guix hello</code>\nThe flags passed to <code>guix shell</code> are the following:</p>\n<ul><li><code>-C</code> (<code>--container</code> ): To prevent your environment from interfering.</li><li><p><code>-W</code> (<code>--nesting</code> ): To bring the current Guix you are using into the</p><p>container so it can load the extension.</p></li></ul>\n<p>Since this example is using the new extension scheme, the <code>guix</code> command you\nare running must be at or newer than commit\n<a href=\"https://codeberg.org/guix/guix/commit/de069958fcf9050be92a4085c31b9738ca4ff912\" rel=\"nofollow ugc noopener\">de069958fc</a>.</p>\n<h1 id=\"closing-words\">Closing words</h1>\n<p>I hope you find this small introduction to Guix extensions useful and that you start to write your own Guix extensions.</p>\n<p>I would like to encourage everyone reading this to submit their extensions to\nthe <a href=\"https://codeberg.org/guix-extensions\" rel=\"nofollow ugc noopener\">guix-extensions</a> Codeberg\norganization. The idea is to make this organization a central hub for everyone\nto participate in the development of useful extensions for the community.</p>\n<p>Unless otherwise stated, blog posts on this site are\ncopyrighted by their respective authors and published under the terms of\nthe <a href=\"https://creativecommons.org/licenses/by-sa/4.0/\" rel=\"nofollow ugc noopener\">CC-BY-SA 4.0</a> license and those of the <a href=\"https://www.gnu.org/licenses/fdl-1.3.html\" rel=\"nofollow ugc noopener\">GNU Free Documentation License</a> (version 1.3 or later, with no Invariant Sections, no\nFront-Cover Texts, and no Back-Cover Texts).</p>","headings":[{"level":1,"text":"Extending Guix","id":"extending-guix"},{"level":1,"text":"How does Guix locate extensions?","id":"how-does-guix-locate-extensions"},{"level":1,"text":"Writing an extension","id":"writing-an-extension"},{"level":1,"text":"Packaging a Guix extension","id":"packaging-a-guix-extension"},{"level":1,"text":"Closing words","id":"closing-words"}]}}