| .forgejo/workflows | Initial commit. | |
| .gitignore | Initial commit. | |
| LICENSE.org | Initial commit. | |
| Makefile | Initial commit. | |
| ox-atom-tests.el | Initial commit. | |
| ox-atom.el | Initial commit. | |
| README.org | Initial commit. | |
emacs-ox-atom
A general-purpose Atom 1.0 feed exporter for Org-mode.
Installation
Clone or copy org-atom.el somewhere on your load-path, then:
(require 'org-atom)
Quick start
Approach A — Single file (one headline per entry)
Each top-level headline becomes one <entry>. File-level keywords
supply the feed metadata.
#+TITLE: My Blog
#+ATOM_ID: https://example.org/feed.xml
#+ATOM_LINK: https://example.org/
#+ATOM_SELF: https://example.org/feed.xml
#+ATOM_AUTHOR: Alice <alice@example.org>
#+ATOM_LOGO: https://example.org/logo.png
* My first post
:PROPERTIES:
:ATOM_ID: https://example.org/posts/first.html
:ATOM_LINK: https://example.org/posts/first.html
:ATOM_UPDATED: [2026年02月12日]
:ATOM_PUBLISHED: [2025年11月23日]
:ATOM_SUMMARY: A short teaser shown in feed readers.
:END:
Body text here becomes =<content type="html">=.
Export via the menu (C-c C-e a) or programmatically:
;; To a file (e.g. feed.xml):
(org-atom-export-to-atom)
;; To a buffer for inspection:
(org-atom-export-as-atom)
Or as an ox-publish publishing function:
(add-to-list
'org-publish-project-alist
'("my-feed"
:base-directory "~/blog/"
:publishing-directory "~/blog/public/"
:include ("feed.org")
:publishing-function org-atom-publish-to-atom))
Approach B — Multi-file (one file per post)
Each .org file in a directory is an independent post. After exporting
the files to HTML, call org-atom-write-feed to assemble the feed:
(org-atom-write-feed
:file "public/feed.xml"
:title "My Blog"
:id "https://example.org/"
:link "https://example.org/"
:self "https://example.org/feed.xml"
:authors '(:name "Alice" :email "alice@example.org")
:entries (org-atom-entries-from-files
(directory-files "org/posts" t "\\.org$")
:base-url "https://example.org/posts/"
:html-dir "public/posts/"
:exclude-drafts t))
This is the recommended approach when using ox-publish for a blog with
one post per file; call org-atom-write-feed from a
:completion-function in your posts project.
Reference
File-level keywords (single-file mode)
These #+KEYWORD: lines go at the top of your feed .org file.
| Keyword | Feed element | Notes |
|---|---|---|
#+TITLE |
<title type="text"> |
Standard Org keyword |
#+ATOM_ID |
<id> |
Permanent IRI; falls back to #+ATOM_LINK |
#+ATOM_SUBTITLE |
<subtitle type="text"> |
|
#+ATOM_AUTHOR |
<author> |
Parsed as "Name <email>" |
#+ATOM_LINK |
<link rel="alternate"> |
URL of the site's homepage |
#+ATOM_SELF |
<link rel="self"> |
URL of the feed itself |
#+ATOM_RIGHTS |
<rights type="text"> |
|
#+ATOM_ICON |
<icon> |
|
#+ATOM_LOGO |
<logo> |
Entry properties (single-file mode)
These go in the :PROPERTIES: drawer of each top-level headline.
| Property | Entry element | Notes |
|---|---|---|
:ATOM_ID: |
<id> |
Falls back to :CUSTOM_ID:, then a generated URN |
:ATOM_LINK: |
<link rel="alternate"> |
|
:ATOM_UPDATED: |
<updated> |
Falls back to #+DATE |
:ATOM_PUBLISHED: |
<published> |
Falls back to :ATOM_UPDATED: |
:ATOM_SUMMARY: |
<summary type="text"> |
|
:ATOM_AUTHOR: |
<author> |
Parsed as "Name <email>"; overrides feed author |
:ATOM_RIGHTS: |
<rights type="text"> |
Org tags on the headline (* My post :tag1:tag2:) become
<category term="..."/> elements automatically.
Sub-headlines are delegated to the HTML back-end and included in
<content type="html">.
Post keywords (multi-file mode)
These keywords are read from each individual post .org file by
org-atom-entries-from-files.
| Keyword | Entry element | Notes |
|---|---|---|
#+TITLE |
<title> |
|
#+DATE |
<updated>, <published> |
|
#+DESCRIPTION |
<summary type"text">= |
|
#+AUTHOR |
<author> |
Parsed as "Name <email>" |
#+DRAFT: t |
(entry excluded) | Honored when :exclude-drafts t |
#+ATOM_ID |
<id> |
Overrides the default URL-based id |
#+ATOM_UPDATED |
<updated> |
Overrides #+DATE |
#+ATOM_RIGHTS |
<rights> |
Top-level Org tags in the file become <category term="..."/> elements.
org-atom-write-feed keyword arguments
| Argument | Type | Description |
|---|---|---|
:file |
string (required) | Output path; parent directories are created |
:title |
text construct | Feed <title> |
:subtitle |
text construct | Feed <subtitle> |
:id |
IRI string | Feed <id> (permanent, globally unique) |
:link |
string or link plist | Alternate link to the site homepage |
:self |
string or link plist | Self link to the feed URL |
:authors |
person plist or list | Feed-level <author> element(s) |
:contributors |
person plist or list | Feed-level <contributor> element(s) |
:categories |
category plist or list | Feed-level <category> element(s) |
:links |
link plist or list | Additional <link> elements |
:generator |
generator plist | Overrides org-atom-generator; set to nil to omit |
:icon |
IRI string | Feed <icon> |
:logo |
IRI string | Feed <logo> |
:rights |
text construct | Feed <rights> |
:updated |
time value | Feed <updated>; derived from entries if omitted |
:entries |
list of entry plists | The feed entries; see below |
org-atom-entries-from-files keyword arguments
| Argument | Type | Description |
|---|---|---|
:base-url |
string | Prepended to each HTML filename to form <link> and <id> |
:html-dir |
string | Directory of exported HTML files; enables <content> population |
:exclude-drafts |
boolean | Skip files with #+DRAFT: t |
:author-fallback |
person plist/list | Used when a file has no #+AUTHOR |
:id-fn |
function | Called with the entry plist; returns the <id> IRI |
Text constructs
Wherever a text construct is accepted (:title, :subtitle, :summary,
:rights, and feed-level equivalents), you may pass:
| Value | Rendered as |
|---|---|
"plain string" |
<... type="text"> |
'(:text "...") |
<... type="text"> |
'(:html "...") |
<... type="html"> |
'(:xhtml "...") |
<... type="xhtml"> (wrapped in <div xmlns=...>) |
Person plists
Wherever a person construct is accepted (:authors, :contributors),
pass a plist with:
| Key | Required | Description |
|---|---|---|
:name |
yes | Human-readable name |
:email |
no | Email address |
:uri |
no | Homepage URI |
A list of such plists produces multiple <author> / <contributor>
elements. Author strings of the form "Name <email>" are parsed
automatically when read from Org keywords and properties.
Link plists
Wherever a link is accepted (:link, :self, :links), pass a plist with:
| Key | Description |
|---|---|
:href |
The IRI (required) |
:rel |
Relation type (alternate, self, ...) |
:type |
Media type |
:hreflang |
Language of the referenced resource |
:title |
Human-readable label |
:length |
Content length in bytes |
The :link and :self shorthand arguments also accept a plain string,
which is wrapped in the appropriate rel and type attributes
automatically.
Content values
The :content key of an entry plist accepts:
| Value | RFC 4287 rendering |
|---|---|
"plain string" |
<content type="text"> |
'(:text "...") |
<content type="text"> |
'(:html "...") |
<content type="html"> |
'(:xhtml "...") |
<content type="xhtml"> |
'(:media "mime/type" "src") |
Out-of-line: <content type="..." src="..."/> |
'(:inline "mime/type" "b64") |
Inline base64: <content type="...">...</content> |
Customisation
M-x customize-group RET org-atom RET
| Variable | Default | Description |
|---|---|---|
org-atom-extension |
"xml" |
File extension for exported feeds |
org-atom-generator |
Org-mode name, URI, and version | <generator> element; set to nil to omit |