Update a field (#+LAST_MODIFIED: ) at save

Here’s how I handle it in my config:

I don’t think this is something we should ship with Org-roam, but it’s not a hard-no.


Thank you! That is exactly what I needed. I can’t claim to weigh in on whether or not it should ship with org-roam. I am quite happy to have this in my config file(s). It is doing exactly what I want it to do.

1 Like

I would love to implement this in my configuration.
I use spacemacs, and I do not use use-package to load my layers.
Could you point me to how I can use this hook?
I think that I can simply put all the definitions of the functions in my config file.
But how should I introduce the before-save hook in you 2590 line?

Like this:

(add-hook 'before-save-hook #'zp/org-set-last-modified)
1 Like

thanks! but how does it know to activate it in org-mode? should it simply be in my user-config?

The check for Org-mode is run inside the function, so you don’t need to worry about it:

1 Like

Works like a charm. THANK YOU!

@roi.holtzman Could you put here your configuration for spacemacs for documentation? Thank you in advance.


I added this part to my user-config in the init.el file.
(for spacemacs users that have their configuration in a .spacemacs file it should be in the user-config part of .spacemacs)

(add-hook 'before-save-hook #'zp/org-set-last-modified)

  (defun zp/org-find-time-file-property (property &optional anywhere)
    "Return the position of the time file PROPERTY if it exists.
When ANYWHERE is non-nil, search beyond the preamble."
      (goto-char (point-min))
      (let ((first-heading
               (re-search-forward org-outline-regexp-bol nil t))))
        (when (re-search-forward (format "^#\\+%s:" property)
                                 (if anywhere nil first-heading)

  (defun zp/org-has-time-file-property-p (property &optional anywhere)
    "Return the position of time file PROPERTY if it is defined.
As a special case, return -1 if the time file PROPERTY exists but
is not defined."
    (when-let ((pos (zp/org-find-time-file-property property anywhere)))
        (goto-char pos)
        (if (and (looking-at-p " ")
                 (progn (forward-char)
                        (org-at-timestamp-p 'lax)))

  (defun zp/org-set-time-file-property (property &optional anywhere pos)
    "Set the time file PROPERTY in the preamble.
When ANYWHERE is non-nil, search beyond the preamble.
If the position of the file PROPERTY has already been computed,
it can be passed in POS."
    (when-let ((pos (or pos
                        (zp/org-find-time-file-property property))))
        (goto-char pos)
        (if (looking-at-p " ")
          (insert " "))
        (delete-region (point) (line-end-position))
        (let* ((now (format-time-string "[%Y-%m-%d %a %H:%M]")))
          (insert now)))))

  (defun zp/org-set-last-modified ()
    "Update the LAST_MODIFIED file property in the preamble."
    (when (derived-mode-p 'org-mode)
      (zp/org-set-time-file-property "LAST_MODIFIED")))

That’s great. Thank you !

Yes! This is a nice addition. It should be included in org-roam. Very useful.

A much simpler option is to use emacs’ in-built timestaamp functionality:


Could you show a example of config ?

that would simply be putting:

(require 'time-stamp)
(add-hook 'write-file-functions 'time-stamp)

in your config.


Is #+LAST_MODIFIED recognized by some other functions? I have tried to make time-stamp use the standard Org-mode #+date (I like lower case) keyword and only run it for Org mode files but I am not sure if #+date is allowed to have time.

(add-hook 'org-mode-hook
          (lambda () (add-hook 'before-save-hook 'time-stamp nil 'local)))

(add-hook 'org-mode-hook
          (lambda ()
            (set (make-local-variable 'time-stamp-pattern)
                 "8/^#\\+date: %%$")))

I just watched your tutorials, thanks for making them!

One of the first things I noticed was the LAST_MODIFIED property in your file headers, and immediately searched out your config to steal your code. I’ve wanted this for a long time, so thank you.

Using doom I do the following to update my timestamps:

(after! org
  (setq time-stamp-active t
    time-stamp-start "#\\+modified:[ \t]*"
    time-stamp-end "$"
    time-stamp-format "\[%04y-%02m-%02d %3a %02H:%02M\]")
(add-hook 'before-save-hook 'time-stamp))
1 Like

I’m trying @alexv suggestion which works very well with default time-stamp-format:

  (add-hook 'org-mode-hook
    (lambda () (add-hook 'before-save-hook 'time-stamp nil 'local)))

  (add-hook 'org-mode-hook
    (lambda ()
      (set (make-local-variable 'time-stamp-pattern)
     	   "7/^#\\+EDITED: %%$"

but does not work if changing time-stamp-format in last but one line:

   	        "7/^#\\+EDITED: [%04y-%02m-%02d %3a %02H:%02M]"

Any idea how to make it work?

(add-hook 'org-mode-hook (lambda ()
                             (setq-local time-stamp-active t
                                         time-stamp-start "#\\+EDITED:[ \t]*"
                                         time-stamp-end "$"
                                         time-stamp-format "\[%04y-%02m-%02d %3a %02H:%02M\]")
                             (add-hook 'before-save-hook 'time-stamp nil 'local)))

There’s a comment in the source code saying not to change time-stamp-pattern.

Thanks, but it does not work this way.

I know very little about elisp but I don’t understand what part says not to change time-stamp-pattern in original code?