Skip to content

Instantly share code, notes, and snippets.

@dnaeon
Created February 23, 2026 12:20
Show Gist options
  • Select an option

  • Save dnaeon/87427d319ae0b0a14bf7bf2bc0c49a77 to your computer and use it in GitHub Desktop.

Select an option

Save dnaeon/87427d319ae0b0a14bf7bf2bc0c49a77 to your computer and use it in GitHub Desktop.
Export org-roam notes to Hugo using Emacs Lisp
#!/usr/bin/env emacs --script
;;
;; A utility script to export all my org-roam notes via `ox-hugo'
;;
;; The script expects the `HUGO_BASE_DIR', `ORG_ROAM_DIR' and `ORG_ROAM_DB' env
;; vars to be set.
;;
;; `HUGO_BASE_DIR' specifies the directory where org-roam nodes will be
;; exported.
;;
;; Upon successful completion the script produces the following directories.
;;
;; $HUGO_BASE_DIR/content/notes - contains the generated Markdown files
;; $HUGO_BASE_DIR/static/ox-hugo - contains static files, e.g. images
;;
;; In order to serve the notes generated by the script simply sync the Markdown
;; files and static files from the directories above to your Hugo installation.
;;
(dolist (env-var '("HUGO_BASE_DIR" "ORG_ROAM_DIR" "ORG_ROAM_DB"))
(unless (getenv env-var)
(error (format "%s env var is not set" env-var))))
(require 'package)
(package-initialize)
(require 'org)
(require 'cl-lib)
(require 'ox-hugo)
(use-package org-roam
:ensure t
:config
;; Configure path to the org-roam database and notes
(setq
org-roam-directory (file-truename (getenv "ORG_ROAM_DIR"))
org-roam-db-location (file-truename (getenv "ORG_ROAM_DB"))))
;; My org-roam capture templates store the notes in `notes/YYYY/MM' directory
;; structure.
;;
;; When exporting to Markdown via `ox-hugo' the resulting links are invalid,
;; because they would point to `../MM/filename.md'.
;;
;; Since all files are being exported to the `$HUGO_BASE_DIR/content/notes'
;; directory we are simply stripping any leading directories from the filenames
;; here, in order to have valid links.
(defun org-export-resolve-id-link/strip-directory (val)
(car (last (file-name-split val))))
(advice-add 'org-export-resolve-id-link :filter-return #'org-export-resolve-id-link/strip-directory)
;; All my org-roam notes contain the `CREATED' property, which specifies the
;; creation date of the note.
;;
;; This wrapper adds the `:date' property to the export environment, which
;; `ox-hugo' will use when exporting to Markdown.
;;
;; Additional keys that may be added here include `:hugo-publishdate',
;; `:hugo-lastmod', etc.
(defun org-export-get-environment/add-date (orig-fun &rest args)
(let ((env (apply orig-fun args))
(created (org-entry-get nil "CREATED")))
(plist-put env :date created)))
(advice-add 'org-export-get-environment :around #'org-export-get-environment/add-date)
;; Iterate through the org-roam nodes and export each of them
(let* ((hugo-base-dir (file-truename (getenv "HUGO_BASE_DIR")))
(hugo-static-dir (file-name-concat hugo-base-dir "static"))
(org-roam-nodes (org-roam-node-list))
(unique-roam-nodes (cl-remove-duplicates
org-roam-nodes
:key (lambda (node) (org-roam-node-id node)))))
;; ox-hugo expects that the $HUGO_BASE_DIR/static directory exists in advance.
(unless (file-accessible-directory-p hugo-static-dir)
(make-directory hugo-static-dir t))
;; Export each org-roam node
(dolist (node unique-roam-nodes)
(let ((node-title (org-roam-node-title node))
(node-file (org-roam-node-file node)))
(with-current-buffer (find-file-noselect node-file)
(setq-local org-hugo-base-dir hugo-base-dir
org-hugo-front-matter-format "yaml"
org-hugo-section "notes"
org-agenda-files nil
org-export-with-broken-links t)
(org-hugo-export-wim-to-md))))
(message (format "Exported %d org-roam node(s)" (length unique-roam-nodes))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment