A framework you can actually read.

No magic, no annotations, no framework ceremony. PHP 8, PostgreSQL, and patterns that have run in production for years. Open it, read it, modify it.

View on GitHub Install Guide

Architecture overview

A clean, well-structured PHP application. No framework magic — just patterns that work.

PostgreSQL

Your data in a real database. Full SQL, JSONB columns, prepared statements throughout. No ORM to fight. No magic that generates surprise queries behind your back.

PHP 8.x, MVC-like

Front-controller routing through serve.php. Clean separation of data classes, logic files, and view templates. No autoloading mystery — you can trace exactly what loads and why.

Zero-Dependency Frontend

Modern vanilla JS. No jQuery, no Webpack, no build step. Bootstrap and Tailwind are supported when you want them. FormWriter adapts to whichever you choose.

Active Record Models

Define a class, get full CRUD. new User($id, true) loads the record. $user->save() writes it. No SQL strings for standard operations.

Plugin System

Self-contained modules with their own routes, data models, admin pages, and scheduled tasks. Build features in isolation. Deploy without touching core.

Theme Override Chain

theme → plugin → base. Swap any view or asset at the theme level without forking anything downstream.

Security is structural, not optional.

Most frameworks require you to remember to use security features. In Joinery, they're structural — you'd have to work to bypass them.

SQL Injection Protection

PDO prepared statements everywhere. There are no concatenated query strings in the codebase. The model layer is structurally incapable of passing raw input to the database.

XSS Prevention

All user-generated output is escaped via htmlspecialchars(). The FormWriter system handles output encoding automatically — individual views cannot forget to escape.

CSRF Protection

FormWriter generates and validates CSRF tokens on every form. You get it for free on every form you build with the system.

Password Hashing

Argon2id. Not bcrypt (which PHP used to default to), not MD5, not SHA-1. The current best practice. Legacy hashes are automatically upgraded on the user's next login.

Cookie Security

HttpOnly, SameSite=Lax, Secure. Session cookies are inaccessible to JavaScript and scoped to prevent cross-site attacks.

Source Available

You can read every line of code that touches user data. No obfuscation, no compiled binaries, no trust-us black boxes.

Every feature is accessible through the API.

Build integrations, automate workflows, or build your own frontend.

# List members curl -H "X-API-Key: your_key" \ https://yoursite.com/api/users # Get a single member curl -H "X-API-Key: your_key" \ https://yoursite.com/api/users/42 # Create an event curl -X POST \ -H "X-API-Key: your_key" \ -H "Content-Type: application/json" \ -d '{"evt_name": "Monthly Meetup"}' \ https://yoursite.com/api/events

Key-Based Auth

Generate API keys per user or per integration. Scoped permissions and per-key rate limiting.

40+ Model Endpoints

CRUD plus action operations across all core data models. JSON in and out.

Extend in Plugins

Add custom endpoints in your plugin. Same auth, routing, and rate limiting infrastructure.

Build features in isolation.

Plugins are self-contained modules. Each has its own MVC structure, activated and deactivated without data loss.

plugins/bookings/ plugin.json data/ booking_class.php # data model — table auto-created views/ booking.php # /booking route works immediately admin/ admin_bookings.php # appears in admin dashboard logic/ booking_logic.php # business logic layer tasks/ SendBookingReminders.php # scheduled task

Auto-Discovered Routes

No route config. Create a view file and the URL works immediately.

Automatic Schema

Plugin tables created and updated automatically on deploy. No migration files for schema changes.

Your Code, Your License

Plugins you write are yours. Release under any license you choose — MIT, proprietary, commercial. No restrictions.

Replace the entire UI without forking anything.

If you're building a custom app, you can completely replace the public-facing UI through the theme override chain, without touching anything in core or plugins.

Override Chain

theme → plugin → base. Customize any view at the theme level. It overrides downstream automatically.

Framework Choice

Bootstrap, Tailwind, or zero-dependency HTML5. FormWriter and the rest of the system adapt to your choice.

Full UI Replacement

Your design, your HTML, your CSS — completely replaceable without forking. Core updates don't touch your theme.

Built for production. Running in production.

ScrollDaddy is a commercial DNS filtering service. It runs entirely on Joinery.

User accounts, device management, filter configuration, subscription billing, scheduled blocklist updates, and a REST API served to a companion Go DNS server — all built as a Joinery plugin and theme. No core modifications. Real users, real billing, real traffic. Same framework you'd download today.

Plugin System

11 data models, full admin interface, scheduled tasks — all in one plugin.

Custom Theme

Full public-facing site with its own design system, no core modifications.

Stripe Billing

Feature-gated subscription tiers with real paying customers.

REST API

Device configurations served to the Go DNS server in real time.

Run on your own infrastructure.

Requirements — Ubuntu 24.04 recommended. Apache2, PHP 8.x, PostgreSQL. Standard LAMP stack, nothing exotic.

Installation — Download the release, run the install script. Apache, PHP, and PostgreSQL are configured automatically. Full guide at /install.

Updates — Automated upgrade system. One command pulls the latest release and applies any schema changes.

Explore the source.

Source-available under the PolyForm Noncommercial license.