I write some of my web pages with emacs org-mode, and then export to html. Like markdown, org-mode is a convenient format to write a simple subset of html.

Each heading in the document will get exported with an ID so that it can be linked to from the table of contents. The default IDs look like #org3091de9. I usually replace these with a custom id that matches the section heading, so a heading Bounding box will get an id #bounding-box.

I've been doing this manually but looked for ways to do it automatically. I found alphapapa's export to html with useful anchors and Lee Hinman's emacs org mode generate ids. Both of these were very useful! I used them to write this code:

(defun my/org-generate-custom-ids ()
  "Generate CUSTOM_ID for any headings that are missing one"
  (let ((existing-ids (org-map-entries 
                     (lambda () (org-entry-get nil "CUSTOM_ID")))))
     (lambda ()
       (let* ((custom-id (org-entry-get nil "CUSTOM_ID"))
              (heading (org-heading-components))
              (level (nth 0 heading))
              (todo (nth 2 heading))
              (headline (nth 4 heading))
              (slug (my/title-to-filename headline))
              (duplicate-id (member slug existing-ids)))
         (when (and (not custom-id)
                    (< level 4)
                    (not todo)
                    (not duplicate-id))
           (message "Adding entry %s to %s" slug headline)
           (org-entry-put nil "CUSTOM_ID" slug)))))))

When I run this function, it goes through all the headings, checks if there's an existing CUSTOM_ID, it's a level ≤3 heading, it's not marked TODO, and it won't be the same an existing id, then inserts the new id constructed from the heading.

(defun my/title-to-filename (title)
  "Convert TITLE to a reasonable filename."
  ;; Based on the slug logic in org-roam, but org-roam also uses a
  ;; timestamp, and I use only the slug. BTW "slug" comes from
  ;; <https://en.wikipedia.org/wiki/Clean_URL#Slug>
  (setq title (s-downcase title))
  (setq title (s-replace-regexp "[^a-zA-Z0-9]+" "-" title))
  (setq title (s-replace-regexp "-+" "-" title))
  (setq title (s-replace-regexp "^-" "" title))
  (setq title (s-replace-regexp "-$" "" title))

I only set this up this morning so it's too early to tell whether it'll be helpful but I'm hoping it'll save me some time constructing clean anchor ids on my web pages. Update I used this for a month and it is quite helpful!