bastibe/org-static-blog

A static site generator using org-mode

Emacs Lisp

397

237 commits

updated Apr 13, 2026

See the code

README

#+TITLE: ORG-STATIC-BLOG

[[http://melpa.org/packages/org-static-blog-badge.svg]] [[http://stable.melpa.org/packages/org-static-blog-badge.svg]]


Static blog generators are a dime a dozen. This is one more, which
focuses on being simple. All files are simple org-mode files in a
directory. The only requirement is that every org file must have a
=#+TITLE= and a =#+DATE=, and optionally, =#+FILETAGS=, =#+DESCRIPTION= and
=#+IMAGE=.

=#+FILETAGS= set the tags for the post, which can be delimited by
colons in the form of =#+FILETAGS: :foo bar:baz:=, or by whitespaces
in the form of =#+FILETAGS: foo bar=. And you can put a short summary
in =#+DESCRIPTION=, which will be converted to a HTML
~<meta name="description" >~ tag, it's good for SEO as described in
Wikipedia's [[https://en.wikipedia.org/wiki/Meta_element#The_description_attribute][Meta element]].

If =org-static-blog-enable-og-tags= is set to =t=, =#+DESCRIPTION= and
=#+IMAGE= (it must be e a relative url) will generate the =og:description=
and =og:image= meta properties that will be used in URL previews
generated in social networks and similar sites (see [[https://ogp.me/][Open Graph
Protocol]]).

This file is also available from marmalade and melpa-stable.

Set up your blog by customizing org-static-blog's parameters, then
call =M-x org-static-blog-publish= to render the whole blog or
=M-x org-static-blog-publish-file filename.org= to render only only
the file =filename.org=.

Above all, I tried to make org-static-blog as simple as possible.
There are no magic tricks, and all of the source code is meant to be
easy to read, understand and modify.

For org-static-blog, a blog consists of six parts:
- Blog posts contain individual entries. Every org file in
  =org-static-blog-posts-directory= is one blog post. Each blog post
  is rendered as its own HTML page.
- The index page contains the last few blog posts on a single page.
  The number of entries on the index page can be customized using
  =org-static-blog-index-length=.
- Optionally show a preview of the post (instead of the full post) on
  the index page setting =org-static-blog-use-preview= to t.  The region
  of the post used as a preview is, by default, its first paragraph,
  but can be fine-tuned using =org-static-blog-preview-start= and
  =org-static-blog-preview-end.=
- The archive page lists the publishing dates and headlines of every
  blog post.
- The RSS feed is a machine-readable XML file that contains every blog
  post. It is not meant to be consumed by humans. Instead RSS readers
  can use the RSS feed to aggregate entries from multiple blogs.
- Drafts are rendered like regular blog posts, but are not included in
  the index, the archive, or the RSS feed.
- Each blog post can be tagged, and each tag links to a page that
  lists all other posts of the same tag. Additionally, a tag overview
  page is created that lists the publishing dates and headlines of
  every blog post, sorted by tags. This feature is only enabled if you
  set =org-static-blog-enable-tags= to =t=.
- If =org-static-blog-enable-og-tags= is set to =t=, all generated pages
  will include some useful [[https://ogp.me/][Open Graph]] meta properties such as the
  title, the description and an optional image. If no =#+IMAGE= property
  is provided in a post, the default one specified in
  =org-static-blog-image= will be used.
- If =org-static-blog-enable-social= is set to =t=, a =social.org= file
  is generated following the [[https://github.com/tanrax/org-social][Org Social]] protocol. Each blog post
  becomes a social entry with a preview and a link to the full
  article, allowing the blog to participate in the Org Social
  decentralized network. You can configure the feed metadata with
  =org-static-blog-social-nick=, =org-static-blog-social-description=,
  and =org-static-blog-social-avatar=.
- To disable comments for single blog posts, reserve a tag name as
  =org-static-blog-no-comments-tag= and tag the post with that tag.
- It is also possible to create per-tag RSS feeds.  This feature is
  only enabled when you set =org-static-blog-enable-tag-rss= to t.

Every HTML page in org-static-blog can be customized in the following
ways:
- The contents of =org-static-blog-page-header= are inserted into the
  =<head>= of every page. Use this to include custom CSS and
  JavaScript for your blog.
- The contents of =org-static-blog-page-preamble= is inserted just
  before the content of every page. This is a good place to put the
  header or menus for your blog.
- The contents  of =org-static-blog-page-postamble= is  inserted after
  the content of every generated page: after any blog post page, after
  the index page, the tag pages and the archive. This is where you can
  include copyright notices.
- The   return    values   of    =org-static-blog-post-preamble=   and
  =org-static-blog-post-postamble= are prepended and appended to every
  blog post. If you want to change the formatting of dates, titles, or
  the tag list,  overwrite these functions. In  particular the content
  of =org-static-blog-post-comments=  is inserted  at the end  of each
  blog post. Use this to add a comment box.

You can customize the RSS feed output by setting
=org-static-blog-rss-extra=. Its content is placed right before the
sequence of posts. For example you can add an RSS icon for the feed,
or advertise that you built your blog with org-static-blog.  You can
also limit the number of entries in the feed via
=org-static-blog-rss-max-entries=.


There are some static texts like "/Other posts/", "/Tags/" etc that
org-static-blog includes in produced html. By default org-static-blog
uses english texts, but language chosen depends on value set to
=org-static-blog-langcode=. If your language is not supported yet, you
will see placeholders like =[other-posts:de]= and =[tags:de]=.
You can add new language by adding texts to =org-static-blog-texts=
list. And if you do, please share and create Pull Request.

If you want to activate a few convenience key bindings, add
=(add-to-list 'auto-mode-alist (cons (concat org-static-blog-posts-directory ".*\\.org\\'") 'org-static-blog-mode))=
to your /init.el/. These key bindings are:
- =C-c C-f= / =C-c C-b= to open next/previous post.
- =C-c C-p= to open the matching published HTML file of a post.
- =C-c C-n= to create a new blog post.


If you have questions, if you find bugs, or if you would like to
contribute something to org-static-blog, please open an issue or pull
request on GitHub.

Finally, I would like to remind you that I am developing this project
for free, and in my spare time. While I try to be as accommodating as
possible, I can not guarantee a timely response to issues. Publishing
Open Source Software on GitHub does not imply an obligation to /fix
your problem right now/. Please be civil.

* Examples

** Minimal Configuration
   This minimal configuration should be added to your /init.el/, and will
   set up a minimal org-static-blog for the URL https://staticblog.org,
   which will be saved in the directory ~/projects/blog/.

   #+begin_src elisp
     (setq org-static-blog-publish-title "My Static Org Blog")
     (setq org-static-blog-publish-url "https://staticblog.org/")
     (setq org-static-blog-publish-directory "~/projects/blog/")
     (setq org-static-blog-posts-directory "~/projects/blog/posts/")
     (setq org-static-blog-drafts-directory "~/projects/blog/drafts/")
     (setq org-static-blog-enable-tags t)
     (setq org-static-blog-enable-social t)
     (setq org-static-blog-social-nick "staticblog")
     (setq org-static-blog-social-description "My Static Org Blog on the Org Social network")
     (setq org-export-with-toc nil)
     (setq org-export-with-section-numbers nil)

     ;; This header is inserted into the <head> section of every page:
     ;;   (you will need to create the style sheet at
     ;;    ~/projects/blog/static/style.css
     ;;    and the favicon at
     ;;    ~/projects/blog/static/favicon.ico)
     (setq org-static-blog-page-header
           "<meta name=\"author\" content=\"John Dow\">
     <meta name=\"referrer\" content=\"no-referrer\">
     <meta name=\"viewport\" content=\"initial-scale=1,width=device-width,minimum-scale=1\">
     <link href= \"static/style.css\" rel=\"stylesheet\" type=\"text/css\" />
     <link rel=\"icon\" href=\"static/favicon.ico\">")

     ;; This preamble is inserted at the beginning of the <body> of every page:
     ;;   This particular HTML creates a <div> with a simple linked headline
     (setq org-static-blog-page-preamble
           "<div class=\"header\">
       <a href=\"https://staticblog.org\">My Static Org Blog</a>
     </div>")

     ;; This postamble is inserted at the end of the <body> of every page:
     ;;   This particular HTML creates a <div> with a link to the archive page
     ;;   and a licensing stub.
     (setq org-static-blog-page-postamble
           "<div id=\"archive\">
       <a href=\"https://staticblog.org/archive.html\">Other posts</a>
     </div>
     <center><a rel=\"license\" href=\"https://creativecommons.org/licenses/by-sa/3.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/3.0/88x31.png\" /></a><br /><span xmlns:dct=\"https://purl.org/dc/terms/\" href=\"https://purl.org/dc/dcmitype/Text\" property=\"dct:title\" rel=\"dct:type\">bastibe.de</span> by <a xmlns:cc=\"https://creativecommons.org/ns#\" href=\"https://bastibe.de\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Bastian Bechtold</a> is licensed under a <a rel=\"license\" href=\"https://creativecommons.org/licenses/by-sa/3.0/\">Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.</center>")

     ;; This HTML code is inserted into the index page between the preamble and
     ;;   the blog posts
     (setq org-static-blog-index-front-matter
           "<h1> Welcome to my blog </h1>\n")
   #+end_src

   In order for this to work, you will also need to create a style sheet
   at /~/projects/blog/static/style.css/, which might for example change
   the appearance of the ~#preamble~, the ~#content~, and the
   ~#postamble~.

   To write posts, you can now call ~org-static-blog-create-new-post~,
   and render your blog with ~org-static-blog-publish~.

   Each post is an org-mode file such as

   #+begin_src org-mode
 #+title: How to Write a Blog Post
 #+date: <2020-07-03 08:57>
 #+filetags: computers emacs blog

 Step one: Install ~org-static-blog~. \\
 Step Two: Execute ~M-x org-static-blog-create-new-post~ and write the content. \\
 Step Three: Execute ~M-x org-static-blog-publish~ and upload to your webhost. \\
 Done.
   #+end_src

   You can find more complete examples by looking at my [[https://github.com/bastibe/.emacs.d/blob/master/init.el#L670][init.el]] and the
   [[https://github.com/bastibe/bastibe.github.com][repository]] for my blog ([[http://bastibe.de/][bastibe.de]]) itself to see an example of how to
   use =org-static-blog= in practice.

*** Other org-static-blog blogs:
    - [[https://zngguvnf.org/][zngguvnf.org]] ---see the [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][writeup]]
    - [[https://matthewbauer.us/blog/][matthewbauer.us/blog/]]
    - [[https://jao.io/blog/simplicity-pays-off.html][jao's programming musings]]
    - [[https://f-santos.gitlab.io/][f-santos.gitlab.io]]
    - [[https://xgqt.gitlab.io/blog/][xgqt.gitlab.io/blog]]
    - [[https://unmonoqueteclea.github.io/][unmonoqueteclea]]
    - [[https://chenyo-17.github.io/org-static-blog/][chenyo-17.github.io/org-static-blog]]
    - [[https://dou-meishi.github.io/org-blog/][doumeishi.github.io/org-blog]] --- see the [[https://dou-meishi.github.io/org-blog/2024-01-22-TryOrgStaticBlog/notes.html][writeup]]
    - Please open a pull request to add your blog, here!

** Features
*** Hide some subtrees when publishing
    - Background
      When publishing some posts, we may not want to publish the more private or unfinished parts of the subtrees. So maybe we can use tags to identify these subtrees and ignore them during the posting process.
    - Usage
      - Set the corresponding tags
        Set the tag to ignore subtrees with =org-static-blog-no-post-tag=, default is =nonpost=.
      - Posting
        The parts containing this tag will be automatically ignored during the posting process.
    - Example
      - If you have an org-mode file containing =tree-1=, =tree-2=, =tree-3=. and only want to publish =tree-1= and =tree-3=, then the file would look like this
      #+begin_src org-mode
       * tree-1
       * tree-2 :nonpost:
       * tree-3
      #+end_src
      - Then the file will automatically ignore =tree-2= subtrees with the =nonpost= tag when it is published.

*** Extended cleaning of the suggested filename

If you regularly find yourself editing the suggested filename when creating new
posts, e.g. replacing slashes (`/`), then you can modify the value of
`org-static-blog-suggested-filename-cleaning-regexp`. With its default value,
`"\s"`, it only replaces whitespace.

For example, a post with the title "Using bastibe/org-static-blog for your blog"
would, with the default result in a suggested filename of
`2023-10-02-using-bastibe/org-static-blog-for-your-blog.org`. If the value of
`org-static-blog-suggested-filename-cleaning-regexp` is changed like this

```
(setq org-static-blog-suggested-filename-cleaning-regexp (rx (or "/" (in white)))
```

the `/` will be replaced too and the suggested filename will be
`2023-10-02-using-bastibe-org-static-blog-for-your-blog.org`.

* Known Issues

- Org-static-blog is a pure static site generator. As such, it does
  not include comments. However, you can easily include services like
  Disqus to do this for you.
- You can have hosting services like GitHub auto-render you blog every
  time you commit using continuous integration tools like Travis CI.
  An example of how to do this has been gracefully provided
  by [[https://gitlab.com/_zngguvnf/org-static-blog-example][zngguvnf]].
- Individual blog entries are only re-rendered if no current HTML file
  is available (i.e. the org file is older than the HTML file). If you
  want to forcibly re-render an entry, delete the HTML file.

* Changelog

- 2018-03-17 (v1.0.4): Massive speed up of org-static-blog. A
  re-render with one changed file used to take about a second per
  post, and now takes about a second total.
- 2018-03-21 (v1.1.0): Tags.
  Each post can now have tags (using =#+tags:=). If you enable
  =org-static-blog-enable-tags=, tags are included in each post,
  tag-index pages are generated for each tag, and a tag archive
  is generated for all tags.
- 2018-03-23 (v1.1.1): Tags.
  Deprecated =#+tags:= in favor of =#+filetags:=, which is the
  correct way of setting file-wide tags in org-mode.
  (Thank you, Kaushal Modi!)
- 2018-04-19 (v1.2.0): HTML5
  Org-static-blog now outputs valid HTML5 instead of XHTML. This makes
  the resulting HTML cleaner, but shouldn't impact your styles. Also,
  you can now customize your content language by setting
  =org-static-blog-langcode= and the HTML output has been fixed in a few
  places.
  (Thank you, Michael Cardell Widerkrantz!)
- 2020-03-20 (v1.3.0): Nested directories, Translations, and more
  Improve handling of local variables (Thank you, Matthew Bauer)
  Rewrote README in org-mode (Thank you, Rafał -rsm- Marek)
  Adds support for localizations (Thank you, Rafał -rsm- Marek)
  Put license in a LICENSE file (Thank you, Jonas Bernoulli)
  Adds uption to force-rerender entire blog (Thank you, Winny)
  Support for non-flat directory structure (Thank you, Shmavon Gazanchyan)
  Support for "preview" slugs on index page (Thank you, K. Scarlet)
  Various bugfixes (Thank you, Matthew Bauer, luhuaei, neeasade, Yauhen Makei, Winny, zsxh)
  Translations in RU, BY, FR (Thank you, Yauhen Makei, Théo Jacquin)
- 2020-07-20 (v1.4.0):
  Adds a command to create drafts (Thank you, Massimo Lauria)
  Adds optional RSS info (Thank you, Massimo Lauria)
  Restructures preamble and postamble to be more consistent (Thank you, Massimo Lauria)
  Translations in IT, ES (Thank you, Massimo Lauria, Alberto Álvarez)
  Option to make ellipsis link to full post (Thank you, jaor)
  Improves preview generation (Thank you, Allo)
  Render RSS dates as per RFT-822 and the RSS spec
- 2021-03-05 (v1.5.0)
  Better awareness for posts in subdirectories (Thank you, Justin Abrahms)
  New custom variable org-static-blog-rss-max-entries (Thank you, jao)
  Can now exclude some posts from RSS feeds (Thank you, jao)
  New custom variable for index page header (Thank you, Bruno Deremble)
- 2022-05-05 (v1.6.0)
  Adds ~#+description~ support that fills the description meta tag (Thank you, Guangwang Huang)
  Adds optional post slugs, and date before title (Thank you, jao)
  Correct date encoding in RSS and various RSS fixes (Thank you, Agnessa Bubowska)
  Ability to not publish subtrees by tag (Thank you, wangz)
  Fixes some warnings related to Emacs 28 (Thank you, Maciej Barć)
- 2025-03-20 (v1.7.0)
  Fixes path related issues for drafts
  Optionally disables comments if a customizable tag is set
  Adds viewport metadata to header (Thank you, Aze)
  Various fixes to blog generation (Thank you, Aze)
  Makes whitespace cleaning of filename suggestions customizable (Thank you, Magnus Therning)
  Adds support for Open Graph meta tags (Thank you, Pablo González Carrizo)
  Various bugfixes (Thank you, Dou Meishi, Musa Al-hassy, Alexis Purslane, chenyo, Johnny5, Miao ZhiCheng)
  Adds optional Org Social feed generation (Thank you, Andros Fenollosa)

* LICENSE

Copyright 2015, Bastian Bechtold

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

(top 30 of 40)

bastibe

133 commits

MunGell

11 commits

jaor

9 commits

kchanqvq

7 commits

bastibe/org-static-blog

A static site generator using org-mode

Emacs Lisp

397

237 commits

updated Apr 13, 2026

See the code

README

#+TITLE: ORG-STATIC-BLOG

[[http://melpa.org/packages/org-static-blog-badge.svg]] [[http://stable.melpa.org/packages/org-static-blog-badge.svg]]


Static blog generators are a dime a dozen. This is one more, which
focuses on being simple. All files are simple org-mode files in a
directory. The only requirement is that every org file must have a
=#+TITLE= and a =#+DATE=, and optionally, =#+FILETAGS=, =#+DESCRIPTION= and
=#+IMAGE=.

=#+FILETAGS= set the tags for the post, which can be delimited by
colons in the form of =#+FILETAGS: :foo bar:baz:=, or by whitespaces
in the form of =#+FILETAGS: foo bar=. And you can put a short summary
in =#+DESCRIPTION=, which will be converted to a HTML
~<meta name="description" >~ tag, it's good for SEO as described in
Wikipedia's [[https://en.wikipedia.org/wiki/Meta_element#The_description_attribute][Meta element]].

If =org-static-blog-enable-og-tags= is set to =t=, =#+DESCRIPTION= and
=#+IMAGE= (it must be e a relative url) will generate the =og:description=
and =og:image= meta properties that will be used in URL previews
generated in social networks and similar sites (see [[https://ogp.me/][Open Graph
Protocol]]).

This file is also available from marmalade and melpa-stable.

Set up your blog by customizing org-static-blog's parameters, then
call =M-x org-static-blog-publish= to render the whole blog or
=M-x org-static-blog-publish-file filename.org= to render only only
the file =filename.org=.

Above all, I tried to make org-static-blog as simple as possible.
There are no magic tricks, and all of the source code is meant to be
easy to read, understand and modify.

For org-static-blog, a blog consists of six parts:
- Blog posts contain individual entries. Every org file in
  =org-static-blog-posts-directory= is one blog post. Each blog post
  is rendered as its own HTML page.
- The index page contains the last few blog posts on a single page.
  The number of entries on the index page can be customized using
  =org-static-blog-index-length=.
- Optionally show a preview of the post (instead of the full post) on
  the index page setting =org-static-blog-use-preview= to t.  The region
  of the post used as a preview is, by default, its first paragraph,
  but can be fine-tuned using =org-static-blog-preview-start= and
  =org-static-blog-preview-end.=
- The archive page lists the publishing dates and headlines of every
  blog post.
- The RSS feed is a machine-readable XML file that contains every blog
  post. It is not meant to be consumed by humans. Instead RSS readers
  can use the RSS feed to aggregate entries from multiple blogs.
- Drafts are rendered like regular blog posts, but are not included in
  the index, the archive, or the RSS feed.
- Each blog post can be tagged, and each tag links to a page that
  lists all other posts of the same tag. Additionally, a tag overview
  page is created that lists the publishing dates and headlines of
  every blog post, sorted by tags. This feature is only enabled if you
  set =org-static-blog-enable-tags= to =t=.
- If =org-static-blog-enable-og-tags= is set to =t=, all generated pages
  will include some useful [[https://ogp.me/][Open Graph]] meta properties such as the
  title, the description and an optional image. If no =#+IMAGE= property
  is provided in a post, the default one specified in
  =org-static-blog-image= will be used.
- If =org-static-blog-enable-social= is set to =t=, a =social.org= file
  is generated following the [[https://github.com/tanrax/org-social][Org Social]] protocol. Each blog post
  becomes a social entry with a preview and a link to the full
  article, allowing the blog to participate in the Org Social
  decentralized network. You can configure the feed metadata with
  =org-static-blog-social-nick=, =org-static-blog-social-description=,
  and =org-static-blog-social-avatar=.
- To disable comments for single blog posts, reserve a tag name as
  =org-static-blog-no-comments-tag= and tag the post with that tag.
- It is also possible to create per-tag RSS feeds.  This feature is
  only enabled when you set =org-static-blog-enable-tag-rss= to t.

Every HTML page in org-static-blog can be customized in the following
ways:
- The contents of =org-static-blog-page-header= are inserted into the
  =<head>= of every page. Use this to include custom CSS and
  JavaScript for your blog.
- The contents of =org-static-blog-page-preamble= is inserted just
  before the content of every page. This is a good place to put the
  header or menus for your blog.
- The contents  of =org-static-blog-page-postamble= is  inserted after
  the content of every generated page: after any blog post page, after
  the index page, the tag pages and the archive. This is where you can
  include copyright notices.
- The   return    values   of    =org-static-blog-post-preamble=   and
  =org-static-blog-post-postamble= are prepended and appended to every
  blog post. If you want to change the formatting of dates, titles, or
  the tag list,  overwrite these functions. In  particular the content
  of =org-static-blog-post-comments=  is inserted  at the end  of each
  blog post. Use this to add a comment box.

You can customize the RSS feed output by setting
=org-static-blog-rss-extra=. Its content is placed right before the
sequence of posts. For example you can add an RSS icon for the feed,
or advertise that you built your blog with org-static-blog.  You can
also limit the number of entries in the feed via
=org-static-blog-rss-max-entries=.


There are some static texts like "/Other posts/", "/Tags/" etc that
org-static-blog includes in produced html. By default org-static-blog
uses english texts, but language chosen depends on value set to
=org-static-blog-langcode=. If your language is not supported yet, you
will see placeholders like =[other-posts:de]= and =[tags:de]=.
You can add new language by adding texts to =org-static-blog-texts=
list. And if you do, please share and create Pull Request.

If you want to activate a few convenience key bindings, add
=(add-to-list 'auto-mode-alist (cons (concat org-static-blog-posts-directory ".*\\.org\\'") 'org-static-blog-mode))=
to your /init.el/. These key bindings are:
- =C-c C-f= / =C-c C-b= to open next/previous post.
- =C-c C-p= to open the matching published HTML file of a post.
- =C-c C-n= to create a new blog post.


If you have questions, if you find bugs, or if you would like to
contribute something to org-static-blog, please open an issue or pull
request on GitHub.

Finally, I would like to remind you that I am developing this project
for free, and in my spare time. While I try to be as accommodating as
possible, I can not guarantee a timely response to issues. Publishing
Open Source Software on GitHub does not imply an obligation to /fix
your problem right now/. Please be civil.

* Examples

** Minimal Configuration
   This minimal configuration should be added to your /init.el/, and will
   set up a minimal org-static-blog for the URL https://staticblog.org,
   which will be saved in the directory ~/projects/blog/.

   #+begin_src elisp
     (setq org-static-blog-publish-title "My Static Org Blog")
     (setq org-static-blog-publish-url "https://staticblog.org/")
     (setq org-static-blog-publish-directory "~/projects/blog/")
     (setq org-static-blog-posts-directory "~/projects/blog/posts/")
     (setq org-static-blog-drafts-directory "~/projects/blog/drafts/")
     (setq org-static-blog-enable-tags t)
     (setq org-static-blog-enable-social t)
     (setq org-static-blog-social-nick "staticblog")
     (setq org-static-blog-social-description "My Static Org Blog on the Org Social network")
     (setq org-export-with-toc nil)
     (setq org-export-with-section-numbers nil)

     ;; This header is inserted into the <head> section of every page:
     ;;   (you will need to create the style sheet at
     ;;    ~/projects/blog/static/style.css
     ;;    and the favicon at
     ;;    ~/projects/blog/static/favicon.ico)
     (setq org-static-blog-page-header
           "<meta name=\"author\" content=\"John Dow\">
     <meta name=\"referrer\" content=\"no-referrer\">
     <meta name=\"viewport\" content=\"initial-scale=1,width=device-width,minimum-scale=1\">
     <link href= \"static/style.css\" rel=\"stylesheet\" type=\"text/css\" />
     <link rel=\"icon\" href=\"static/favicon.ico\">")

     ;; This preamble is inserted at the beginning of the <body> of every page:
     ;;   This particular HTML creates a <div> with a simple linked headline
     (setq org-static-blog-page-preamble
           "<div class=\"header\">
       <a href=\"https://staticblog.org\">My Static Org Blog</a>
     </div>")

     ;; This postamble is inserted at the end of the <body> of every page:
     ;;   This particular HTML creates a <div> with a link to the archive page
     ;;   and a licensing stub.
     (setq org-static-blog-page-postamble
           "<div id=\"archive\">
       <a href=\"https://staticblog.org/archive.html\">Other posts</a>
     </div>
     <center><a rel=\"license\" href=\"https://creativecommons.org/licenses/by-sa/3.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/3.0/88x31.png\" /></a><br /><span xmlns:dct=\"https://purl.org/dc/terms/\" href=\"https://purl.org/dc/dcmitype/Text\" property=\"dct:title\" rel=\"dct:type\">bastibe.de</span> by <a xmlns:cc=\"https://creativecommons.org/ns#\" href=\"https://bastibe.de\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Bastian Bechtold</a> is licensed under a <a rel=\"license\" href=\"https://creativecommons.org/licenses/by-sa/3.0/\">Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.</center>")

     ;; This HTML code is inserted into the index page between the preamble and
     ;;   the blog posts
     (setq org-static-blog-index-front-matter
           "<h1> Welcome to my blog </h1>\n")
   #+end_src

   In order for this to work, you will also need to create a style sheet
   at /~/projects/blog/static/style.css/, which might for example change
   the appearance of the ~#preamble~, the ~#content~, and the
   ~#postamble~.

   To write posts, you can now call ~org-static-blog-create-new-post~,
   and render your blog with ~org-static-blog-publish~.

   Each post is an org-mode file such as

   #+begin_src org-mode
 #+title: How to Write a Blog Post
 #+date: <2020-07-03 08:57>
 #+filetags: computers emacs blog

 Step one: Install ~org-static-blog~. \\
 Step Two: Execute ~M-x org-static-blog-create-new-post~ and write the content. \\
 Step Three: Execute ~M-x org-static-blog-publish~ and upload to your webhost. \\
 Done.
   #+end_src

   You can find more complete examples by looking at my [[https://github.com/bastibe/.emacs.d/blob/master/init.el#L670][init.el]] and the
   [[https://github.com/bastibe/bastibe.github.com][repository]] for my blog ([[http://bastibe.de/][bastibe.de]]) itself to see an example of how to
   use =org-static-blog= in practice.

*** Other org-static-blog blogs:
    - [[https://zngguvnf.org/][zngguvnf.org]] ---see the [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][writeup]]
    - [[https://matthewbauer.us/blog/][matthewbauer.us/blog/]]
    - [[https://jao.io/blog/simplicity-pays-off.html][jao's programming musings]]
    - [[https://f-santos.gitlab.io/][f-santos.gitlab.io]]
    - [[https://xgqt.gitlab.io/blog/][xgqt.gitlab.io/blog]]
    - [[https://unmonoqueteclea.github.io/][unmonoqueteclea]]
    - [[https://chenyo-17.github.io/org-static-blog/][chenyo-17.github.io/org-static-blog]]
    - [[https://dou-meishi.github.io/org-blog/][doumeishi.github.io/org-blog]] --- see the [[https://dou-meishi.github.io/org-blog/2024-01-22-TryOrgStaticBlog/notes.html][writeup]]
    - Please open a pull request to add your blog, here!

** Features
*** Hide some subtrees when publishing
    - Background
      When publishing some posts, we may not want to publish the more private or unfinished parts of the subtrees. So maybe we can use tags to identify these subtrees and ignore them during the posting process.
    - Usage
      - Set the corresponding tags
        Set the tag to ignore subtrees with =org-static-blog-no-post-tag=, default is =nonpost=.
      - Posting
        The parts containing this tag will be automatically ignored during the posting process.
    - Example
      - If you have an org-mode file containing =tree-1=, =tree-2=, =tree-3=. and only want to publish =tree-1= and =tree-3=, then the file would look like this
      #+begin_src org-mode
       * tree-1
       * tree-2 :nonpost:
       * tree-3
      #+end_src
      - Then the file will automatically ignore =tree-2= subtrees with the =nonpost= tag when it is published.

*** Extended cleaning of the suggested filename

If you regularly find yourself editing the suggested filename when creating new
posts, e.g. replacing slashes (`/`), then you can modify the value of
`org-static-blog-suggested-filename-cleaning-regexp`. With its default value,
`"\s"`, it only replaces whitespace.

For example, a post with the title "Using bastibe/org-static-blog for your blog"
would, with the default result in a suggested filename of
`2023-10-02-using-bastibe/org-static-blog-for-your-blog.org`. If the value of
`org-static-blog-suggested-filename-cleaning-regexp` is changed like this

```
(setq org-static-blog-suggested-filename-cleaning-regexp (rx (or "/" (in white)))
```

the `/` will be replaced too and the suggested filename will be
`2023-10-02-using-bastibe-org-static-blog-for-your-blog.org`.

* Known Issues

- Org-static-blog is a pure static site generator. As such, it does
  not include comments. However, you can easily include services like
  Disqus to do this for you.
- You can have hosting services like GitHub auto-render you blog every
  time you commit using continuous integration tools like Travis CI.
  An example of how to do this has been gracefully provided
  by [[https://gitlab.com/_zngguvnf/org-static-blog-example][zngguvnf]].
- Individual blog entries are only re-rendered if no current HTML file
  is available (i.e. the org file is older than the HTML file). If you
  want to forcibly re-render an entry, delete the HTML file.

* Changelog

- 2018-03-17 (v1.0.4): Massive speed up of org-static-blog. A
  re-render with one changed file used to take about a second per
  post, and now takes about a second total.
- 2018-03-21 (v1.1.0): Tags.
  Each post can now have tags (using =#+tags:=). If you enable
  =org-static-blog-enable-tags=, tags are included in each post,
  tag-index pages are generated for each tag, and a tag archive
  is generated for all tags.
- 2018-03-23 (v1.1.1): Tags.
  Deprecated =#+tags:= in favor of =#+filetags:=, which is the
  correct way of setting file-wide tags in org-mode.
  (Thank you, Kaushal Modi!)
- 2018-04-19 (v1.2.0): HTML5
  Org-static-blog now outputs valid HTML5 instead of XHTML. This makes
  the resulting HTML cleaner, but shouldn't impact your styles. Also,
  you can now customize your content language by setting
  =org-static-blog-langcode= and the HTML output has been fixed in a few
  places.
  (Thank you, Michael Cardell Widerkrantz!)
- 2020-03-20 (v1.3.0): Nested directories, Translations, and more
  Improve handling of local variables (Thank you, Matthew Bauer)
  Rewrote README in org-mode (Thank you, Rafał -rsm- Marek)
  Adds support for localizations (Thank you, Rafał -rsm- Marek)
  Put license in a LICENSE file (Thank you, Jonas Bernoulli)
  Adds uption to force-rerender entire blog (Thank you, Winny)
  Support for non-flat directory structure (Thank you, Shmavon Gazanchyan)
  Support for "preview" slugs on index page (Thank you, K. Scarlet)
  Various bugfixes (Thank you, Matthew Bauer, luhuaei, neeasade, Yauhen Makei, Winny, zsxh)
  Translations in RU, BY, FR (Thank you, Yauhen Makei, Théo Jacquin)
- 2020-07-20 (v1.4.0):
  Adds a command to create drafts (Thank you, Massimo Lauria)
  Adds optional RSS info (Thank you, Massimo Lauria)
  Restructures preamble and postamble to be more consistent (Thank you, Massimo Lauria)
  Translations in IT, ES (Thank you, Massimo Lauria, Alberto Álvarez)
  Option to make ellipsis link to full post (Thank you, jaor)
  Improves preview generation (Thank you, Allo)
  Render RSS dates as per RFT-822 and the RSS spec
- 2021-03-05 (v1.5.0)
  Better awareness for posts in subdirectories (Thank you, Justin Abrahms)
  New custom variable org-static-blog-rss-max-entries (Thank you, jao)
  Can now exclude some posts from RSS feeds (Thank you, jao)
  New custom variable for index page header (Thank you, Bruno Deremble)
- 2022-05-05 (v1.6.0)
  Adds ~#+description~ support that fills the description meta tag (Thank you, Guangwang Huang)
  Adds optional post slugs, and date before title (Thank you, jao)
  Correct date encoding in RSS and various RSS fixes (Thank you, Agnessa Bubowska)
  Ability to not publish subtrees by tag (Thank you, wangz)
  Fixes some warnings related to Emacs 28 (Thank you, Maciej Barć)
- 2025-03-20 (v1.7.0)
  Fixes path related issues for drafts
  Optionally disables comments if a customizable tag is set
  Adds viewport metadata to header (Thank you, Aze)
  Various fixes to blog generation (Thank you, Aze)
  Makes whitespace cleaning of filename suggestions customizable (Thank you, Magnus Therning)
  Adds support for Open Graph meta tags (Thank you, Pablo González Carrizo)
  Various bugfixes (Thank you, Dou Meishi, Musa Al-hassy, Alexis Purslane, chenyo, Johnny5, Miao ZhiCheng)
  Adds optional Org Social feed generation (Thank you, Andros Fenollosa)

* LICENSE

Copyright 2015, Bastian Bechtold

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

(top 30 of 40)

bastibe

133 commits

MunGell

11 commits

jaor

9 commits

kchanqvq

7 commits

Languages

Emacs Lisp

100.0%