A custom identifier to encode publisher data
This is a sample implementation of custom Denote identifiers (Define a completely custom identifier scheme).
This custom identifier cannot be derived programmatically though, so the function my-denote-get-identifier makes a series of prompts to get the relevant data. Then it joins them all together with the current date to create a unique identifier. The helper function my-denote-custom-identifier--make-unique-identifier makes sure to make small tweaks to the “current date” part of this identifier so that it is always unique.
We can always flesh out this example with, for example, prompts that leverage completing-read, though the point is to show how custom identifiers can be implemented in full. Everything else Denote does with identifiers, namely, links and backlinks, should work as intended.
Good luck!
;; NOTE 2025-09-18: This is for testing the following custom code.
;; The dates will need to be updated to match the test day.
;;
;; (let* ((denote-used-identifiers (make-hash-table :test #'equal))
;; (_ (progn
;; (puthash "2000E1AprotFbookPmaxT20250918" t denote-used-identifiers)
;; (puthash "2000E1AprotFbookPmaxT20250919" t denote-used-identifiers)
;; (puthash "2000E1AprotFbookPmaxT20250920" t denote-used-identifiers))))
;; (my-denote-get-identifier "2000E1AprotFbookPmaxT20250918" nil))
;;
;; (let* ((denote-used-identifiers (make-hash-table :test #'equal))
;; (_ (progn
;; (puthash "2000E1AprotFbookPmaxT20250918" t denote-used-identifiers)
;; (puthash "2000E1AprotFbookPmaxT20250919" t denote-used-identifiers)
;; (puthash "2000E1AprotFbookPmaxT20250920" t denote-used-identifiers))))
;; (my-denote-custom-identifier--make-unique-identifier "2000E1AprotFbookPmax" (format-time-string "%Y%m%d") denote-used-identifiers))
;; The code herein is a custom function for use with `denote-get-identifier-function':
(setq denote-get-identifier-function #'my-denote-get-identifier)
(defun my-denote-custom-identifier--make-unique-identifier (base-identifier date-string identifiers)
(let* ((current-date-string date-string)
(id (format "%sT%s" base-identifier current-date-string)))
(while (gethash id identifiers)
(let* ((year (string-to-number (substring current-date-string 0 4)))
(month (string-to-number (substring current-date-string 4 6)))
(day (string-to-number (substring current-date-string 6 8)))
(time (encode-time 0 0 0 day month year))
(next-day-time (seconds-to-time (+ (time-to-seconds time) 86400)))
(next-date-string (format-time-string "%Y%m%d" next-day-time)))
(setq current-date-string next-date-string)
(setq id (format "%sT%s" base-identifier current-date-string))))
id))
(defvar my-denote-custom-identifier-year-history nil)
(defvar my-denote-custom-identifier-edition-history nil)
(defvar my-denote-custom-identifier-format-history nil)
(defvar my-denote-custom-identifier-publisher-history nil)
(defvar my-denote-custom-identifier-author-history nil
"Minibuffer history for `my-denote-custom-identifier-author-prompt'.")
;; TODO 2025-09-18: Flesh out the other prompts as well. Some can
;; benefit from a `completing-read' approach.
(defun my-denote-custom-identifier-author-prompt ()
"Prompt for one or more authors.
Multiple authors are separated by a space or comma."
(read-string "Author (space or comma for many): " nil 'my-denote-custom-identifier-author-history))
(defun my-denote-custom-identifier-author-format (author)
"Format AUTHOR to use an A delimiter before each name."
(let ((authors (split-string author "[ ,]+" t "[\s\f\t\n\r\v]+")))
(format "A%s" (mapconcat #'identity authors "A"))))
(defun my-denote-get-identifier (initial-identifier date)
(let ((denote-used-identifiers (or denote-used-identifiers (denote--get-all-used-ids))))
(cond
((and initial-identifier (not (gethash initial-identifier denote-used-identifiers)))
initial-identifier)
(t
(let ((base-id (concat
(read-string "Year: " nil 'my-denote-custom-identifier-year-history)
"E" (read-string "Edition: " nil 'my-denote-custom-identifier-edition-history)
(my-denote-custom-identifier-author-format (my-denote-custom-identifier-author-prompt))
"F" (read-string "Format: " nil 'my-denote-custom-identifier-format-history)
"P" (read-string "Publisher: " nil 'my-denote-custom-identifier-publisher-history))))
(my-denote-custom-identifier--make-unique-identifier
base-id
(format-time-string "%Y%m%d" (or date (current-time)))
denote-used-identifiers))))))