Static Site Generator



Stapy is a Static Site Generator. It works with Python without any additional package.

Download Source

A question? Contact me!


Requires Python 3.5 or newer on any operating system.


Create a project directory anywhere and download the last release from the Stapy repository.

mkdir stapy
cd stapy
tar zxvf last.tar.gz --strip 1
rm last.tar.gz

HTTP server

Run standalone HTTP server:


On Windows, double-click on the file (Python required, easily install from Microsoft Store if needed).

Then access the URL http://localhost:1985


Static files are generated in the web directory. This directory contains all the necessary environment directories (devel, prod...).

For the production, add a prod directory in the web directory. It will contain all pages and files you need to deploy (html, css, js, images...).

After you add a new environment, you must restart the server.


When a page is open in the browser, the server search a json file in source/pages directory. The name of the json file is the same as the URL path. Examples:

URL Path Json file
/ index.html.json
/hello.html hello.html.json
/hello/world.html hello/world.html.json
/hello/world/ hello/world/index.html.json

If the json file does not exist, a 404 error is sent.


The json file contains all the data required for generate the page:

  "title": "Page title",
  "description": "Page description",
  "template": "template/default.html",
  "content": "content/index.html"

The template key is required.

Set the environment variables with the environment suffix:

  "url.local": "http://localhost:1985/",
  "": ""

The environment suffix must have the same name as your environment directory. For local rendering, the suffix is always "local".

A variable can have a default value:

  "my_text": "All environments display this text",
  "my_text.local": "Except the local with this"


A file named html.json in the source/layout directory is used for the default html page configuration. It will be merged with the page's json file. This is useful for a global configuration.


  "title": "Default title",
  "template": "template/default.html"


  "title": "Home title",
  "content": "content/index.html"

layout/html.json + pages/index.html.json

  "title": "Home title",
  "template": "template/default.html",
  "content": "content/index.html"

You can add layout file for any file extensions you need:

A common.json config file is available for all pages (all extensions).

Finally, it is possible to add a layout file for the page subdirectories:

JSON Weight Required
layout/common.json 1 N
layout/html.json 2 N
layout/blog/common.json 3 N
layout/blog/html.json 4 N
layout/blog/2022/html.json 5 N
pages/blog/2022/my-first-post.html.json 6 Y

Json data with higher weight will override and extend lower weights.

Tip: Add _page/ before the path to fetch json merged data.



The template file is the skeleton of the page:

<!DOCTYPE html>
<html lang="fr">
        <meta charset="utf-8">
        <title>{{ meta_title }}</title>
        <meta name="description" content="{{ meta_description }}" />
        <link rel="stylesheet" href="{{ url }}css/style.css" />
            {% header %}
            {% content %}
            {% footer %}


Tip: set empty string for a content file variable in the json file to replace it with empty text.

Child block

Use specific json data for the child content with a + separator (spaces are required):

{% post + my-first-post.html %}

The json/my-first-post.html.json data will be accessible in the post template, with a $ before the var:

<a href="{{ url }}{{ $_path }}">{{ $post_title }}</a>

To loop json data with a query, use ~ as separator (spaces are required):

{% post ~ {key:value} {key:dir} {start:end} %}


{% post ~ tags:post date:desc 1:10 %}

This query retrieves the 10 first pages with post value in tags, sorted by date. The tags and date vars must be present in the json data of the pages:

  "date": "2022-01-01",
  "tags": ["post"]

The json data will be accessible in the post template, with a $ before the var.

Reserved variables


Variable Description Example
_path Cleaned page path blog/
_full_path Full page path blog/index.html
_env Environment name prod


{{ url }}{{ _path }}
<!-- -->
{{ url }}{{ _full_path }}
<!-- -->
<script type="text/javascript" src="{{ url }}js/init.{{ _env }}.js"></script>
<!-- -->


All necessary resources like js, css or images are copied from the source/assets directory in all environment directories (e.g. web/prod).

Static files

The final static HTML files and resources are added or refreshed when the pages are opened in the browser. When /hello.html is open, the hello.html file is automatically generated in all environment directories (e.g. web/prod).


Get official Stapy plugins.

A plugin allows you to add custom code when rendering the page.

Event Description Variable Args
file_content_opened Update any file content (html, json, md...) File content (str) path, file_system
page_data_merged Update the current page json data Current page data (dict) path, file_system
file_copy_before Do something before copying the asset file File path to copy (str) source, destination, file_system
file_copy_after Do something after the asset file was copied File path copied (str) source, destination, file_system
before_content_parsed Update the page template content before parsing Page content (str) data, env, path, file_system
after_content_parsed Update the page template content after parsing Page content (str) data, env, path, file_system
child_content_data Update child data before content generation Child data (dict) key, env, path, file_system
child_content_query_result Update data result before content generation All child data (list) key, env, path, file_system

A plugin is a python script in the plugins directory. Plugin files are loaded in alphabetical order.

For example, to convert Markdown to HTML, add a file in the plugins directory with the following content:

import markdown

def file_content_opened(content: str, args: dict) -> str:
    if (args['file_system']).get_file_extension(args['path']) != 'md':
        return content
    return markdown.markdown(content)

pip3 install markdown

You can now use Markdown syntax in your page content:

Hello World!

[Back to home]({{ url }})


The website can be regenerated with a crawler. Stapy gives a python crawler script in the tools directory.

python3 tools/ {delete} {copy} {crawl}

Launch crawler with no option perform delete, copy and crawl:

python3 tools/

PUT 200
HEAD 200 /index.html
HEAD 200 /hello.html


If you are using an automated deployment tool like Netlify, Cloudflare Pages, Vercel or Render, configure the tool to deploy the environment directory from the git repository (e.g. web/prod).

To deploy with Github Pages, create a docs directory with a symlink from web/prod to docs. Commit and push the docs directory, then configure Github Pages to deploy docs.


Simple and minimal themes for Stapy: