Here is a list of things your application needs before it does anything a user would pay for: a login form, a password hash, a session, a logout, a password reset, a way to tell an administrator apart from a customer, a CSRF token, a database connection, a schema, a way to change that schema, a 404 page, a 500 page, a log file, a way to read the log file, an email sender, a rate limiter on the login form, a settings page, a way to seed the first admin account, and a deployment story.
None of that is your product. All of it is required. And every one of those pieces has a well-understood, decades-old correct answer that thousands of people have already stress-tested in production.
The premise of Tiknix is that you should inherit that list rather than re-derive it. Not because writing it is hard, but because writing it is done — and the twentieth reimplementation of a password reset flow is not craft, it is entropy.
What "primitive" means here
A primitive is a capability that is stable enough to build on without thinking about it again. Not a snippet you paste, not a service you rent, not a vendor you hope stays solvent — a piece of your own running system whose behavior you can name in a sentence and whose edges you can read in an afternoon.
Tiknix ships these as primitives:
| Primitive | Where it lives | What it settles |
|---|---|---|
| Authentication | controls/Auth.php | Register, login by username or email, logout, password reset, Google OAuth |
| Two-factor auth | lib/TwoFactorAuth.php | TOTP enrollment, verification, recovery codes, device trust |
| Authorization | authcontrol table | Which privilege level may reach which route |
| Persistence | RedBeanPHP | CRUD, relations, schema creation |
| Request handling | FlightPHP + lib/FlightMap.php | URL to controller method, JSON responses, redirects |
| CSRF | lib/SimpleCsrf.php | One session token, one helper, every form |
| Rate limiting | lib/RateLimiter.php | Brute-force resistance on login and reset |
| Secrets at rest | lib/EncryptionService.php | libsodium symmetric encryption for stored tokens |
lib/Mailer.php | Password reset, team invite, welcome, contact replies |
Each one is a few hundred lines you can read. That readability is the point: a primitive you cannot inspect is a dependency, and dependencies you cannot inspect are where outages live.
The AI argument, stated carefully
A coding agent is extremely good at writing code that resembles code it has seen. It is much less good at deciding which code should exist. Given an empty directory and the prompt "build me a customer portal," a capable model will happily invent an authentication system, a session scheme, a permissions concept, and a database layer — all plausible, all subtly different from the ones it invented last week, and none of them integrated with each other.
Give the same model a codebase where authentication is already a solved fact, and its job collapses into something it is genuinely excellent at: writing the next controller in the style of the existing eleven. The agent stops being an architect — a role it is unreliable in — and becomes a very fast, very consistent implementer, which is a role it is extremely strong in.
Constrained generation beats open generation. The narrower the decision space, the better the output — and existing primitives are how you narrow it.
This is why Tiknix leans on conventions so heavily. A URL maps to a method name by rule. A bean type maps to a table by rule. A route's permission is a row by rule. Rules are things an agent can follow perfectly, forever, without being reminded. Preferences are things it forgets by the third file.
This is not low-code
It is worth being blunt about what this is not. There is no visual builder here, no drag-and-drop
canvas, no proprietary runtime that generates an app you cannot read. What you get after
./install.sh is a directory of PHP files, a SQLite database, and a Git repository.
You can open any file. You can delete any feature. You can host it on a $5 VPS or a shared host
from 2009.
The distinction matters because the two approaches fail differently. A drag-and-drop tool fails at the boundary — the moment you need something the canvas doesn't express, you are stuck and your only move is to leave. A framework of primitives fails gradually and legibly: you hit something it doesn't do, you write it, and it becomes part of your codebase. The second failure mode is survivable. The first one has ended a lot of projects.
What you actually spend your time on
With the list at the top of this article already handled, a new feature in Tiknix is usually three things: a controller method, a view, and a permission row. Adding a "projects" section to an app looks like this:
// controls/Projects.php — /projects and /projects/create
namespace app;
use \Flight as Flight;
use \RedBeanPHP\R as R;
class Projects extends BaseControls\Control {
public function index() {
if (!$this->requireLogin()) return;
$projects = R::find('project', 'member_id = ? ORDER BY created_at DESC',
[Flight::get('member')['id']]);
$this->render('projects/index', [
'title' => 'Projects',
'projects' => $projects,
]);
}
}
There is no route registration, no dependency injection wiring, no schema migration, and no
auth check beyond one line. The project table does not exist yet; RedBeanPHP will
create it the first time you store one. The URL /projects already resolves. That is
what inheriting primitives buys: the interesting part of the work starts on line one.
The trade you are making
You are trading freedom of architecture for speed and coherence. If you adopt Tiknix, you are adopting its opinions: FlightPHP's request model, RedBeanPHP's bean semantics, numeric permission levels where lower means more powerful, controllers as the unit of routing. Those opinions are not universally correct. They are internally consistent, which for most projects is worth more.
- Opinions are load-bearing. Fighting the conventions — custom routing everywhere, raw SQL instead of beans — removes most of the benefit while keeping all of the constraints. If you disagree with the conventions, use something else rather than half-adopting this.
- Reading is still required. "You don't have to write it" is not "you don't have to understand it." Before you ship an auth system you did not write, read
controls/Auth.phpend to end. It is worth the hour. - The default install is a development install. Build mode on, a known admin password, debug output enabled. Those defaults are correct for your laptop and wrong for the internet — see the production checklist.
- Some included modules are starting points, not finished products. The contact form and help center are working scaffolds meant to be shaped to your domain, not polished SaaS features.
The rest of this series walks through the primitives one at a time — what each one does, the code that does it, and where its edges are. Next up: why the substrate is PHP and SQLite, which is a choice people have opinions about.