JSON as a Headless CMS
I pay $0/month for my CMS. No WordPress, no Contentful, no Strapi server. Just JSON files and Git.
The Setup
/data
├── posts.json # Blog content
├── projects.json # Portfolio items
└── settings.json # Site config
The Schema
{
"posts": [
{
"id": 7,
"slug": "my-new-post",
"title": "My New Post",
"excerpt": "Short description...",
"content": "# Markdown here...",
"date": "2026-04-15",
"tags": ["php", "css"],
"image": "assets/img/blog-7.svg",
"published": true,
"views": 0
}
]
}
The Admin Panel (Pure PHP)
<?php // admin/edit-post.php ?>
<?php
session_start();
require_once '../core/JsonStore.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$store = new JsonStore('posts.json');
$posts = $store->all();
$id = (int)$_POST['id'];
$key = array_search($id, array_column($posts, 'id'));
// Update fields
$posts[$key]['title'] = $_POST['title'];
$posts[$key]['content'] = $_POST['content'];
$posts[$key]['published'] = isset($_POST['published']);
$posts[$key]['date'] = $_POST['date'];
// Auto-generate excerpt if empty
if (empty($_POST['excerpt'])) {
$posts[$key]['excerpt'] = substr(strip_tags($_POST['content']), 0, 150) . '...';
}
if ($store->save($posts)) {
// Git commit automatically
exec('cd ' . __DIR__ . '/.. && git add data/posts.json && git commit -m "Update: ' . escapeshellarg($_POST['title']) . '"');
header('Location: /admin?success=1');
exit;
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Edit Post</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body>
<form method="POST" class="editor">
<input type="hidden" name="id" value="<?= $post['id'] ?>">
<input type="text" name="title" value="<?= htmlspecialchars($post['title']) ?>"
class="editor__title" placeholder="Post title...">
<textarea name="content" class="editor__content" rows="20"><?= htmlspecialchars($post['content']) ?></textarea>
<div class="editor__meta">
<input type="date" name="date" value="<?= $post['date'] ?>">
<label>
<input type="checkbox" name="published" <?= $post['published'] ? 'checked' : '' ?>>
Published
</label>
</div>
<button type="submit" class="btn btn--primary">Save Post</button>
</form>
<!-- Live preview with JS -->
<div id="preview" class="preview"></div>
<script>
// Simple markdown preview
const textarea = document.querySelector('.editor__content');
const preview = document.querySelector('#preview');
textarea.addEventListener('input', () => {
preview.innerHTML = simpleMarkdown(textarea.value);
});
function simpleMarkdown(text) {
return text
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\n/g, '<br>');
}
</script>
</body>
</html>
The Frontend Rendering
<?php // post.php ?>
<?php
$store = new JsonStore('posts.json');
$post = $store->findBySlug($_GET['slug']);
if (!$post || !$post['published']) {
http_response_code(404);
include '404.php';
exit;
}
// Parse markdown (simple version)
$content = $post['content'];
$content = preg_replace('/^# (.*$)/m', '<h1>$1</h1>', $content);
$content = preg_replace('/^## (.*$)/m', '<h2>$1</h2>', $content);
$content = preg_replace('/\*\*(.*?)\*\*/s', '<strong>$1</strong>', $content);
$content = nl2br($content);
?>
<article class="post">
<h1><?= htmlspecialchars($post['title']) ?></h1>
<div class="post__meta">
<time><?= date('F j, Y', strtotime($post['date'])) ?></time>
<span><?= $post['views'] ?> views</span>
</div>
<div class="post__content">
<?= $content ?>
</div>
</article>
The Git Workflow
1. Edit in admin panel → saves to JSON
2. PHP executes git commit automatically
3. Push to GitHub (backup + history)
4. Deploy via git pull on server
Free backups forever.
When to Upgrade
Move to a real database when you need:
- Multi-user editing (conflict resolution)
- Full-text search (use SQLite FTS or Meilisearch)
- Complex relationships (use PostgreSQL)
Until then? JSON is perfect.
Your CMS doesn't need to be a server. Sometimes it's just a well-structured file.