Why I still reach for plain PHP for small projects
I built a tiny site for a friend last weekend. A landing page, a contact form, two extra pages, one of those silly little tools that calculates something the visitor types in. Total time: about three hours, including the coffee break. Stack: PHP 8.3, a single index.php, four includes, no Composer, no vendor folder, no build step. Deploy was an SFTP drag-and-drop.
I’ve been doing web work since 2002 and I keep coming back to this approach for small things. Not because I’m a luddite (I use Laravel when the job calls for it, I’ve shipped Symfony projects, I’ve done my time with Node), but because the framework reflex has gotten weirdly automatic. Somebody asks for a five-page site and the first instinct, especially from younger devs, is to scaffold a whole project. Add a router. Add a templating engine. Pick a CSS framework. Set up CI. Two days later you’ve got the same five-page site, except now it has 14,000 files in node_modules and you can’t deploy without a runner.
What “plain PHP” actually means to me
I don’t mean some <?php spaghetti from 2004 with SQL strung together via $_GET. I mean modern PHP, 8.x, with the language features that have quietly become really nice. Typed properties, named arguments, match expressions, readonly classes, null coalescing all the way down. You can write very tidy procedural-with-a-bit-of-OOP code without ever pulling in a framework. The standard library does most of what a small site needs.
My usual layout for these jobs:
- An
index.phpthat does some kind of front-controller routing, usually 20 lines ofswitchon a clean URL. - A
config.phpkept outside the public folder. - A
views/directory with PHP files that are basically templates with the occasional<?= htmlspecialchars($x) ?>. - An
includes/folder for the handful of helper functions that are actually shared.
That’s it. Done before lunch.
Where vanilla PHP genuinely wins
A few categories where I won’t even consider reaching for a framework:
File-based, mostly-static sites. The kind of site where content is in flat files (Markdown, HTML fragments, sometimes a JSON config), there’s no database, and PHP is really just stitching templates around content. A flat-file CMS like Kirby exists for exactly this case, but for the smallest sites I’d rather just write the 60 lines of code myself than learn somebody else’s conventions.
Single-page tools. A unit converter. A loan calculator. A name generator. A “paste your text and get a word count” page. These have one form, one handler, maybe a session if you’re feeling fancy. They do not need an ORM. They do not need dependency injection. They need $_POST, a function, and an echo.
Throwaway scripts. CLI scripts that hit an API, parse the JSON, write to a CSV. I run a dozen of these for various clients. They live in a folder called scripts, they get cron’d, and they have been chugging along for years. Bringing in Symfony Console for these always felt like wearing a tuxedo to fix a sink.
Contact forms. Honestly, the contact form is probably the canonical case. Validate, send via PHPMailer or just plain mail() if the host’s MTA is configured, redirect, done. I’ve watched developers reach for an entire SaaS form-handler with a monthly fee for a contact form that gets four submissions a month.
Where it doesn’t win, and I switch
I want to be clear because the framework people will (rightly) come at me with examples. The moment a project has any of the following, I stop writing vanilla PHP and reach for Laravel or, if it’s tiny but with structure, Slim:
Auth and sessions for multiple users. The instant you have logins, password resets, role-based access, and any kind of admin area, you are going to reinvent half a framework anyway, and you’ll get it slightly wrong. Use the thing that’s been battle-tested by a million sites. PHP can absolutely do dynamic stuff, I’m not saying otherwise – I’m saying you don’t want to write your own session-fixation defense at 11pm on a Friday.
Anything with a real data layer. More than two or three tables, with relationships, with migrations. An ORM (Eloquent, Doctrine, whatever) saves so much time here that refusing it is just stubbornness.
Background jobs, queues, scheduled work. Could you wire up cron and a database table and call it a queue? You could. Should you? No.
API endpoints with a public spec. Rate limiting, versioning, validation, OpenAPI docs – this is framework territory.
The Pivot connection
It’s worth saying that Pivot, the blog software this site is named after, was vanilla PHP from day one. Back in 2002, frameworks barely existed in PHP-land. Smarty was around. Symfony 1 hadn’t shipped yet. Laravel was eight years away. We just wrote PHP. The whole thing was a few thousand lines, file-based, no database required. It powered a respectable chunk of small blogs in the mid-2000s on shared hosting that couldn’t run anything fancier.
I’m not saying that because I’m pining for 2004 (the CSS situation alone, please no), but because that experience taught me how much you can actually do with the platform if you just sit down and write the code. Sessions, file uploads, RSS feeds, comment systems, basic templating – all doable in a few hundred lines if your scope is small.
The hidden cost of the framework default
Here is the thing nobody talks about with the “always use a framework” advice: a framework is a long-term obligation. You upgrade it. You learn its release notes. You eat the breaking changes. A site I built in vanilla PHP in 2014 still runs, untouched, on a host that quietly upgraded the runtime from 5.6 to 7.4 to 8.2 with maybe two tiny fixes (one was a deprecated function, one was a string-to-int coercion warning). A Laravel 5 site from the same era? Either rewritten by now or stuck on an old PHP version praying nobody notices.
For tiny projects that you don’t want to babysit, that durability is a real feature.
How to tell which kind of project you have
My rough rule, and this is not science: if I can describe the whole site in three sentences and it doesn’t include the word “users”, I write it in vanilla PHP. If it has logged-in users, an admin area, or a real database schema, I scaffold Laravel and don’t think twice.
Most freelance work, in my experience, falls in the first bucket more often than people admit. The client says “I just need a small site” and they actually mean it. The instinct to over-engineer comes from us, not them.
Anyway. The little site I built last weekend has been running for six days now, no errors in the log, the contact form has caught two real messages and one piece of spam, and I haven’t thought about it once since I uploaded it. Which is exactly what I wanted.
About Marin Holvers
Marin Holvers is the senior editor at Pivot. With a background in PHP development and a soft spot for the early-2000s era of dynamic publishing tools, he writes about web design, content strategy, and the craft of building independent websites that last. When he isn’t editing, he’s usually breaking and re-fixing his personal blog.
More about Marin Holvers →