Very simple almost-static blog, example:
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Very simple low-feature almost-static blog built with flask and a dash of jQuery
Here's an example

In short

Write posts in markdown using the online editor with live preview. Provides a near-static paginated blog page, where each post has its own customisable url.

In long

Aimed at people who want a lightweight blogging platform without using a pre-baked system like Wordpress. This is the raw dough of blogging platforms.

The basics are there, but you'll need to flavour it and cook it before eating. By which I mean style to taste with sass, add navigation/assets/title bars etc with html, implement any features you want/need, and start adding content.

What this currently does

  • Reads a markdown-formatted plaintext file of posts and generates a (almost) static html blog page
  • Notices when the posts file has been updated to add new posts without a server restart
  • Gives each post an id and a url of its own under /post/id. Posts can be safely deleted without affecting the id or url of other posts. Custom urls can be given to posts
  • Get a post's own page by clicking on its title
  • Compatible with all markdown syntax, easy to add links, images, etc
  • Upvote posts by clicking an upvote button
  • Tag posts with categories and be able to view all posts from a certain category
  • Mark posts as 'draft' to give them a draft url without publishing them to the main page
  • An admin login page - you'll need SSL if you want to use this feature, otherwise a MITM could steal your password and have free reign over editing the blog posts
  • Edit and add posts on the admin page and upload images
  • (Live) markdown preview
  • Search posts
  • Display the latest x posts and paginate the others
  • Post published date

What is planned

  • Allow image size to be changed
  • Limit post size on main page, either expand post or go to its own page to see the full thing
  • RSS feed


Flask backend, jQuery used for upvote button. Since there are no user accounts upvotes are unlimited. Redis is used for storage of each post's upvotes. The posts themselves are stored in a plain text file as shown below.

Markdown syntax is used for post formatting. In branch SimpleFormat you can find the old, simpler method of post formatting if you prefer.


  • export
  • sass styles/main.scss static/main.css
  • virtualenv -p python3 p3env
  • source p3env/bin/activate
  • pip install -r requirements.txt


The first thing to do is replace all the keys in keys. You should hash your admin password and store the hash on line 3. The other two keys are for signing cookies and form security, so make sure they are long and random.

Deploy with flask run once the env var is set


Navigate to /login and enter your password to login. A 30 minute session cookie is generated.

Adding posts

Add posts by editing the posts file. The server notices the edit and provides the new post on page reload

You can also add posts on the admin page by logging in. Currently, you are presented with all available posts which means that you can edit, delete, and add all posts freely. To make this a bit safer, whenever you edit posts on the admin page a pre-edit backup is stored in the /backup folder on the server.

Upload images using the form provided. Admins are assumed to be trusted so the server currently does not test for filename shenanigans or check the filetype (no check for uploading to ../../../home/username/.bashrc). Duplicate filenames are incremented so images aren't overwritten.


In each post you can specify tags, markdown, and a custom url.
A post takes this form:

# my title

Add images by putting them in /static/img or uploading them via the admin page. Include them in posts with markdown syntax ![alt text](/static/img/filename). At the moment you have to manually type in /static/img/yourFilename.jpg.

Tags are included by putting them under ///post, and will be stripped of space and special characters.
Posting dates are automatically added but you can add/edit them yourself if you want.


draft is a reserved tag for marking draft posts. Draft posts will be available under /tags/draft and you need to be logged in to view it. Making the post live is as simple as removing its draft tag.

The number after ///post is the post id. This is automatically added if it is not there. All posts are scanned and the max id is incremented to produce the value for the next id. This means that you can assign custom ids to posts if you want, but if it is not unique only one of the posts will have its own unique url.

Customising post urls

You can give your post a custom url by adding the desired suffix in place of the id, for example, if I wanted my post to be found at /post/mypost the post tag in the posts file would be this ///postmypost. Please note that only url-safe characters should be used for this.


PyEmbedMarkdown is used to support iframes (Youtube, embeddable content) [!embed](http://embed/url)

The posts file is updated by adding a new post to the top and looks like this:

# title
this is my title
### text
this is my text

# title
this is my title 2
### text
this is my text 2

Note that you do not have to manually specify the post id or the date, these are automatically added if they're not there.