Create a simple site using php and json

There are many solutions to create a site using out-of-the-box software such as WordPress or Django. However sometimes we just need a very simple custom model such as a portfolio list, or some events. You may want to update your data, but have the paging and templates update automatically.

Here is a simple solution which allows you to use PHP to load json files (as data) and then render page templates using TWIG template engine. This solution is fully compatible to then run the json feeds from external sources or APIs at a later date!

First off you need to install PHP on your server. Many will come with it automatically installed, if not head over to to download and install php locally. Once installed you should be able to view a php page on your server without errors e.g.


Next we need to create a site folder and install TWIG template library. This will allow us to use a nice format to write templates, and keep a clean separation between controller code in php and views code in TWIG syntax. Download TWIG from and put the folder at:


Next we need to add a custom filter to load JSON files inside the templates. Add a /php/Twig_JSON.php file and paste in the json loading functions:

class Twig_JSON extends Twig_Extension {

    public function getName() {
        return 'Twig_JSON';

    public function getFunctions() {
        return array(
            'json'  => new Twig_Function_Method($this, 'json'),
    public function json($url) {
        return json_decode(file_get_contents($url), true);

Now we need to create a /php/Main.php file which will load the TWIG template library, the custom TWIG filter we just created, and map browser urls to specific template files:

require_once 'Twig/Autoloader.php';
require_once 'Twig/ExtensionInterface.php';
require_once 'Twig/Extension.php';
require_once 'Twig_JSON.php';

class Main {

    public function init() {
        $this->twig = new Twig_Environment(new Twig_Loader_Filesystem('../html'));
        $this->twig->addExtension(new Twig_JSON());

    public function render() {
        $self = explode('/', $_SERVER['PHP_SELF']);
        $uri = explode('/', $_SERVER['REQUEST_URI']);
        $sections = array_splice($uri, count($self)-2, 1);
        if ($sections[0]) {
            return $this->twig->render($sections[0].'.html');
        } else {
            return 'Try a page url e.g. <a href="./home">home</a>';

$main = new Main();
echo $main->render();

I’ve also added an optional index.php in the root to direct the request to the default template: home. This is because root url doesn’t have a section, so I don’t have a template which will be loaded. By redirecting the root to point to home, i’m telling it to render the home template:

    header("Location: home");

As part of my configuration i’ve also decided to add an .htaccess file at the root of the site to ensure all directory and folder requests hit our /php/Main.php file instead of real folders. This is optional depending on your setup but I think works well for mapping urls to templates:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . php/Main.php [L]

Now we need some data! Lets create a json file at /json/navigation.json with some test data:

        "title": "Home",
        "url": "./home"
        "title": "Videos",
        "url": "./videos"

And finally some templates! we can now load the json file using a TWIG filter and output it into the template. Lets create a template at /templates/home.html with the content:

<!doctype html>
<html lang="en">
    <meta charset="utf-8" />
    <title>Json templates</title>
    {% set items = json('../json/navigation.json') %}
    {% for item in items %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
    {% endfor %}

The json() filter allows you to pass through the url to the json file, which loads and returns it back to the template. This can then be counted/looped through and outputted. A very easy way to keep separation between view templates and your data sets.

You can download a working example here: