Jeem: a tiny flat-file CMS

Jeem is a tiny flat-file CMS. No database to mess with, no admin interface, no special syntax, twigs, plugins, or anything fancy. Simply create a file for each page and write your html/Markdown!

Jeem is free for non-commercial use, and while it's in beta.


Jeem is tiny and simple; in less than 400 lines of php code, it is lightweight and fast.


Creating and editing pages is a matter of creating and editing html files. This page is all the documentation you will need!

  HTML, Markdown, and PHP

What you write is what you get. The HTML is the language of the web, and of Jeem pages as well. Jeem also supports Markdown and PHP.

  PHP Templates

PHP is a templating language in the first place, so why not use it as such?

  Open Source

Jeem is publicly available on BitBucket. You are welcome to contribute.

Installing Jeem

Download Jeem and upload the extracted files to your server. Now you have a live website with sample pages!

Since Jeem is a file-based CMS, there is no database to setup or configure. Backup, restoration, and deployment are simpler since your website is just a collection of files.

Typically you will want to create and edit your website on your local machine and then upload it to your live server. You can use PHP's built-in server for local development. To upload your pages to your website, you can use an FTP client for example.

Creating Pages

You will need to create a directory called 'pages' containing an index page and a 404 (not found) page. Until you create it, Jeem will serve pages from the 'sample-pages' directory.

The name of a page file determines its "slug", or the link which can be used to access the page. The file name has 3 parts: An ordinal, a title (or slug), and a file extension, all separated by dots . Consider this example:


The ordinal part is optional and does not affect the page link, but can be used to influence the order in which pages are listed, because Jeem always lists pages based on the alphabetical order of page file names. It can contain dots.

The title or slug part determines the link which can be used to access the page, as well as the page title if you don't explicitly specify a title.

Jeem supports 3 file extensions for page files: html, md (Markdown), and php. Code in .php pages is executed when the page is displayed. Support for .php pages is one of the main advantages of using Jeem over a static site generator.

The directory structure in the "pages" directory is reflected in the page links. If a subdirectory does not contain an index file, Jeem ignores it. Consider this table:

Page File Page Link

Page Properties

You can assign any property you want to a page in the optional page properties section, then use those properties in your page template or the page itself. The properties section is enclosed by two tokens (default is ---). Example:

menu = Jeem
title = Jeem: the Tiny Flat-file CMS
date = July 12th, 2018

Jeem understands the following page properties:

  • menu: If not explicitly defined, Jeem sets it from the file name. The default theme uses it as the text for links to the page in navigation.
  • title: If not explicitly defined, Jeem sets it from the contents of the first <h1> tag. If no <h1> tag was found, it is set to the 'menu' property. The default theme uses the page title property to set the <title> tag in the page head.
  • description: If defined, the default theme uses it to create a meta description tag inside the <head> tag, which is useful for search engine visibility.
  • theme: Defines the theme to use for the page. This overrides the theme you specify in the site's configuration file (jeem-config.php, see Configuring Jeem below).
  • hidden: If set to true, the page will not show up in navigation links, but will remain accessible through direct links.
  • private: If set to true, the page will not be accessible. Trying to access it will show a "This page is private" page. Implies 'hidden'.
  • redirect: slug of a page to redirect to when this page is visited. Note that Jeem does nothing to ensure you have no cyclic redirects.

Jeem Values

Inside your page and theme, you can insert a jeem value from the "Name" column inside double curly braces {{ }} (no spaces) and Jeem will substitute it with the actual value.

{{page.title}}Jeem: the tiny flat-file PHP CMS
{{JEEM_VERSION}}1.0 - beta

PHP Pages

One of the main advantages of using a dynamic CMS instead of a static site generator is the ability to create "dynamic" sites. Jeem supports php pages and themes for this purpose.

Jeem needs to read a page file for two reasons: extracting properties (for generating navigation links for example), and to actually display the page. Jeem does not execute php pages in the first case - it only executes them when they are being displayed.

Configuring Jeem

You can set custom configuration settings for Jeem by creating a file called "jeem-config.php" in the root directory. Use the sample-jeem-config.cfg file as reference. Uncomment the setting you want and modify its value:


// The site name. Used by the default theme to display in the page header.
//$config["site-name"] = "Jeem";

// The theme to use for pages which do not explicitly specify a theme in their meta data section.
//$config["theme"] = "default";

// Directory names
//$config["pages-dir"] = "pages";
//$config["themes-dir"] = "themes";

// The file extensions for page files. Remove extensions you don't want to support.
//$config["page-extensions"] = ["php", "md", "html"];

// If true, Jeem will ignore pages which don't have explicit page menu assignment in the meta section.
//$config["require-menu"] = false;

// Controls whether or not Jeem will look for a <head> tag in the page contents and move it to the head section of the template.
//$config["extract-head"] = true;

// Format of the page meta data section. Jeem supports "INI" and "JSON".
//$config["meta-format"] = "INI";

// Tokens that mark the beginning and end of the page meta data section.
//$config["meta-opening-token"] = "---";
//$config["meta-closing-token"] = "---";

// This forces Jeem to use clean urls. Use this if you know url rewriting is working but Jeem can't detect it.
//$_ENV["JEEM_CLEAN_URLS"] = true;

// ---------------- Default theme configuration ---------------
//$config["site-name"] = "My Site";
//$config["theme-color"] = 'teal';

// Following configurations are used to embed various external services.
// Since some of these services use the domain name for identification,
// they are disabled by default while developing on localhost.
if ($_SERVER["SERVER_NAME"] != "localhost")
    //$config["disqus-embed-link"] = null; // Get from disqus.
    //$config[""] = null; // Get from
    //$config["statcounter.src"] = null; // Get from statcounter.
// The following are used by the default theme.
$config["social-links"] = array( 'facebook' => '#', 'twitter' => '#', 'youtube' => '#', 'e-mail' => '#' );
$config["footer-links"] = array( 'About Us' => '#', 'Terms and Conditions' => '#', 'Privacy Policy' => '#', 'Cookies Policy' => '#', 'Advertise with Us' => '#');


Currently Jeem comes with two themes (or templates). You can select the active theme by changing $config["theme"] = "default"; in jeem-config.php. You are encouraged to create your own theme however. To start, copy the "bare" or "default" directory in the "themes" directory, then rename it (Jeem uses the directory name as the theme name) and start modifying it. Jeem only requires an "index.php" file that will be the page template.

Clean URLs

You can enable clean urls in Jeem using the included .htaccess file for apache servers. This will allow you to access pages without the "?page=" part of the URL. If Jeem does not automatically detect that url rewriting is enabled, try uncommenting the line $_ENV["JEEM_CLEAN_URLS"] = true; in jeem-config.php.

Questions and Answers

The page title is automatically set by Jeem from the page file name. "top-10-productivity-apps.html" becomes "Top 10 Productivity Apps". To set a custom page title in the page meta data section:

title = Top 10 Productivity Apps you need to check NOW!

See Page Properties for more details.

You can create a directory to contain your images and other resource files in the root directory. How you upload your files to that directory is up to you (FTP for example). If the directory is called "assets", you can reference asset files like so:

<img src="{{BASE_URL}}assets/cute-cat.jpg">

Jeem does not process assets or read any directories other than the "pages" directory.

To insert links to pages while keeping the website code portable, you can use the BASE_PAGE_URL and BASE_URL Jeem values:

<a href="{{BASE_PAGE_URL}}slug-of-page">Link Text</a>
<a href="{{BASE_URL}}">Homepage</a>

You can specify that a page should be hidden (not listed in navigation) or private (not accessible, implies hidden) by including "hidden" or "private" in the ordinal part of the page file name. For example: "". The page is also private if its file name starts with a dot. Finally, you can achieve this by explicitly setting the properties in the meta data section in your page:

hidden = true
# or
private = true

Page file contents are placed inside the body tag, but you can insert html in the <head> tag as well. This is needed for some tags such as <style>, <script>, and <meta>. Simply add a <head> tag in your page and Jeem will extract it and put its contents in the head section (see the default theme).

    <!-- font awesome -->
    <script defer src="" ...></script>

    .content-wrapper {
    max-width: 100%;
    padding: 0;

<p>Jeem will remove the above head tag and place its contents where it belongs.</p>

Creating a Blog

You can easily create a blog on your website thanks to the hierarchical nature of the file system. The screenshot shows how you would store your blog inside the "pages" directory.

The ordinal for each blog post contains the post date in yyyy-mm-dd format to ensure that blog posts show up in chronological order.

Some basic php knowledge is required to write the logic needed to list blog posts on the blog index page. You can use the included sample blog as reference.

Basic example: pages/blog/index.php

foreach( GetPages( $page["dir"], 1 ) as $post )
	if( $page["url"] == $post["url"] )
		continue; // skip this index page.
	<div class="post">
			<a href="<?=$post["url"]?>"><?=$post["title"]?></a>
			<div class="date"><?php echo $post['date']; ?></div>
		<div class="excerpt">
			<?php if( isset($post["excerpt"]) ) echo $post["excerpt"]; ?>
<?php }?>

A similar setup can be used to group any set of related pages together, like news items for example. See the included sample pages.

Jeem does not implement pagination yet.

blog file structure
Typical file structure for a website with a blog

More Information

Visit for more information about Jeem.