At $dayjob I take notes using org-mode and from time to time end up sharing these as exported PDF's or HTML files. When I export a subtree these notes end up overwriting the same file over and over again, the file workbook-2021.pdf when using i.e. C-c C-e C-s l p. I would like to have subtrees export to a file named workbook-2021-headline.pdf.

Using code shared by Melioratus on the Emacs Stack Exchange I was able to write a function that finds the current headline and removes non-alphanumeric characters.

(defun headline-title()
    (let* ((headline
                        (org-element-property :title (org-element-at-point)))))
        (replace-regexp-in-string "[^[:alnum:]-_]" "-" headline)))

I then want for this string to be appended to the filename of the current org-file when exporting the subtree. So given the following file named export-subtree.org and that the cursor is standing under the heading of "Works of wizards" then the file name would be export-subtree-Works-of-wizards.pdf:

* Week 1
** 2021-01-01
*** Concerning Hobbits
A word or two about their lore.
*** Works of wizards
Many more about wandering| wizards.

But this is where my Emacs foo comes short. I really don't know where to start. Because I do not want to be adding a property to every headline I want to export with a different name as many other answers on this network suggests.

1 Answer 1


Try this function:

(defun my/org-export-headline (&optional backend async subtreep visible-only body-only ext-plist)
  "Export the current Org headline using BACKEND.

The available backends are the ones of `org-export-backends' and

When optional argument SUBTREEP is non-nil, transcode the
sub-tree at point, extracting information from the headline
properties first.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

When optional argument BODY-ONLY is non-nil, only return body
code, without surrounding template.

Optional argument EXT-PLIST, when provided, is a property list
with external parameters overriding Org default settings, but
still inferior to file-local settings."
  (let* ((backend (unless backend
                     (completing-read "Available backends: "
                                      (append org-export-backends '(pdf))))))
         (headline (car (last (org-get-outline-path t))))
         (headline-alnum (replace-regexp-in-string "[^[:alnum:]-_]" "-" headline))
         (file-prefix (file-name-sans-extension (buffer-file-name)))
         (filename (format "%s-%s.%s" file-prefix headline-alnum
                           (if (eq backend 'pdf) "tex" backend))))
       (if (eq backend 'pdf) 'latex backend)
        filename async subtreep visible-only body-only ext-plist
      (when (eq backend 'pdf)
        (lambda (file) (org-latex-compile file))))

Usage: M-x my/org-export-headline and select a backend (e.g. pdf, html, etc.).

It may look over-complicated, but it is in fact really simple. To let it support pdf export, I inspired myself from org-latex-export-to-pdf which unfortunately force you to use its filename. I also make it support org-export-to-file extra parameters as org-latex-export-to-pdf does and copy-pasted its docstring. If you ignore the conditional (if (eq backend 'pdf) ...), it's really a matter of narrowing, org-export-to-file, then widening.

  • 1
    Thanks, I got it working. A nitpick: if the buffer is always narrowed, (org-narrow-to-subtree) (org-export-to-file ...) (org-widen) forgets this narrowing; to avoid this, I changed it to (save-restriction (org-export-to-file ...)).
    – toomas
    Oct 13, 2022 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.