⚡ Lightweight Flat-File PHP Blog Script
⚡ A tiny flat-file PHP blog script with zero database.
Posts are simple .md files inside a posts/ folder.
This version includes optional starter posts and fully scoped CSS so it won’t interfere with the rest of your site.
Live demo: View working example
<?php
/**
* Flat-File PHP Blog (No DB, Demo-Safe)
*
* Install:
* /blog/index.php
* /blog/posts/
*
* URLs:
* /blog/
* /blog/?post=slug
*/
declare(strict_types=1);
// ---------------- CONFIG ----------------
$posts_dir = __DIR__ . '/posts';
$site_title = 'My Flat-File Blog';
$site_tagline = 'Tiny PHP blog powered by Markdown files.';
$auto_seed_posts = true;
// ---------------- HEADERS ----------------
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
// ---------------- HELPERS ----------------
function esc(string $s): string {
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
function safe_slug(string $s): string {
$s = strtolower(trim($s));
$s = preg_replace('/[^a-z0-9]+/', '-', $s);
return trim($s, '-');
}
function parse_post(string $file): array {
$raw = @file_get_contents($file);
if (!$raw) return [];
[$head, $body] = array_pad(preg_split("/\R\R/", $raw, 2), 2, '');
$out = ['title'=>'Untitled','date'=>'','excerpt'=>'','body'=>$body];
foreach (preg_split("/\R/", $head) as $line) {
if (strpos($line, ':') === false) continue;
[$k,$v] = array_map('trim', explode(':',$line,2));
$k = strtolower($k);
if (isset($out[$k])) $out[$k] = $v;
}
return $out;
}
function markdown(string $t): string {
$t = esc($t);
$t = preg_replace('/^### (.+)$/m','<h3>$1</h3>',$t);
$t = preg_replace('/^## (.+)$/m','<h2>$1</h2>',$t);
$t = preg_replace('/^# (.+)$/m','<h1>$1</h1>',$t);
$t = preg_replace('/\*\*(.+?)\*\*/','<strong>$1</strong>',$t);
$t = preg_replace('/\*(.+?)\*/','<em>$1</em>',$t);
return nl2br($t,false);
}
function seed_posts(string $dir): void {
if (glob($dir.'/*.md')) return;
@mkdir($dir,0755,true);
file_put_contents($dir.'/welcome.md',
"Title: Welcome
Date: 2025-12-18
Excerpt: Your blog is live.
# Welcome
This is a **flat-file** blog.
Each post is just a Markdown file.");
file_put_contents($dir.'/adding-posts.md',
"Title: Adding Posts
Date: 2025-12-17
Excerpt: Create a file and refresh.
# How it works
Create a new <code>.md</code> file in the posts folder.
Refresh the page. That’s it.");
}
// ---------------- RUN ----------------
if ($auto_seed_posts) seed_posts($posts_dir);
$slug = isset($_GET['post']) ? safe_slug($_GET['post']) : '';
$posts = [];
foreach (glob($posts_dir.'/*.md') as $f) {
$p = parse_post($f);
if (!$p) continue;
$p['slug'] = safe_slug(basename($f,'.md'));
$p['ts'] = $p['date'] ? strtotime($p['date']) : filemtime($f);
$posts[] = $p;
}
usort($posts, fn($a,$b)=>$b['ts']<=>$a['ts']);
// ---------------- VIEW (SCOPED) ----------------
echo '<div class="ffb">';
echo '<style>
.ffb{font-family:system-ui,Arial,sans-serif;background:#eef0ff;padding:20px}
.ffb .box{max-width:820px;margin:auto;background:#fff;border-radius:16px;padding:16px}
.ffb h1{margin:0}
.ffb a{color:#A829A7;font-weight:700;text-decoration:none}
.ffb .small{color:#5a6177;font-size:13px}
.ffb ul{list-style:none;padding:0}
.ffb li{margin:12px 0}
</style>';
echo '<div class="box">';
echo '<h1>'.esc($site_title).'</h1>';
echo '<div class="small">'.esc($site_tagline).'</div>';
if ($slug) {
foreach ($posts as $p) {
if ($p['slug'] === $slug) {
echo '<hr><h2>'.esc($p['title']).'</h2>';
echo '<div class="small">'.esc($p['date']).'</div>';
echo markdown($p['body']);
echo '<p><a href="./">➜ Back</a></p>';
echo '</div></div>';
exit;
}
}
http_response_code(404);
echo '<p>Post not found.</p></div></div>';
exit;
}
echo '<hr><ul>';
foreach ($posts as $p) {
echo '<li><a href="?post='.$p['slug'].'">'.esc($p['title']).'</a>
<div class="small">'.esc($p['excerpt']).'</div></li>';
}
echo '</ul></div></div>';
?>
Comments (0)
No comments yet — be the first.