Experimental — not yet production-ready
Your data isn’t a post.
Stop storing it like one.
Foundry brings real database tables to WordPress with the ergonomics of custom post types. Schema-driven models, familiar queries, and native REST API, WP-CLI, and admin integrations — no magic, full control.
composer require humanmade/foundry
use Foundry\Database\Model;
class Event extends Model {
protected static function get_table_name() : string {
return $GLOBALS['wpdb']->prefix . 'events';
}
protected static function get_table_schema() : array {
return [
'fields' => [
'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
'name' => 'varchar(255)',
'starts' => 'datetime',
],
'indexes' => [
'PRIMARY KEY (id)',
],
];
}
}
// Real columns, real indexes, familiar API.
$events = Event::query( [ 'name' => 'WordCamp' ] );Why custom tables?
Custom post types are the path of least resistance, so everything becomes a post — and every field becomes post meta. That works right up until it doesn’t.
The workaround
Querying structured data through wp_postmeta means JOINs against
a key/value table and string comparisons on longtext columns.
At scale, it falls over.
new WP_Query( [
'post_type' => 'event',
'meta_query' => [
[
'key' => 'starts',
'value' => '2026-07-10',
'compare' => '>=',
],
],
] );
// JOIN on wp_postmeta, comparing strings. Ouch.The Foundry way
Your data gets its own table, with proper column types and indexes you design. You keep the convenience — and the database does what databases are good at.
$events = Event::query( [
'starts' => '2026-07-10',
] );
// One query on an indexed datetime column.Everything you need, nothing you don’t
Start from a plain model and compose upwards. Traits add shared behaviour, decorators map your models into WordPress systems that feel native.
Schema-driven models
Declare fields and indexes in your Active Record class; Foundry creates the table and handles CRUD. Accessors are yours to design.
Queries you already know
A WP_Query-style builder, generated from your schema. Results are lazy, countable, and iterable.
Relationships
Many-to-many relationships between models — or with core posts, terms, users, and comments via built-in shims.
REST API decorator
Extend Foundry\Api\Controller — a real WP_REST_Controller — for full CRUD endpoints over your model.
WP-CLI decorator
Scaffold create, get, update, delete, and list subcommands for any model with Foundry\Cli\Command.
Admin list tables
A WP_List_Table that understands your model, plus a WithActions trait for row and bulk actions without the glue code.
Atomic bulk saves
The Importer decorator and save_many() wrap bulk operations in database transactions — all or nothing.
Compose with traits
Foundry’s With* traits add behaviour horizontally — and the pattern works just as well for your own shared fields.
Designed, not conjured
Explicit over implicit
No magic solutions, no surprise queries. You can read your code and know exactly what it does.
Predictable and controllable
You decide your accessors, your schema, and where to abstract. Foundry never takes decisions away from you.
Defaults, not decisions
Smart defaults get you moving fast, but they’re only defaults — every behaviour can be overridden.
Native to WordPress
wpdb prepared statements, WP_Error, the REST API, WP-CLI. It feels like WordPress because it is.
From schema to shipped
Define your model
Extend Foundry\Database\Model, declare your schema, and Foundry
creates the table. Add getters and setters your way — with validation
and typecasting where you want it.
class Event extends Model {
use WithName;
public function get_starts() : ?DateTime {
$starts = $this->get_field( 'starts' );
return $starts ? new DateTime( $starts ) : null;
}
// …your schema, your rules.
}Query it
Every field in your schema is queryable, and relationships can be queried too. Results load lazily and behave like the arrays you’re used to.
$events = Event::query( [ 'name' => 'WordCamp' ] );
foreach ( $events as $event ) {
echo $event->get_name();
}Decorate it
Layer on the integrations you need: REST API endpoints, WP-CLI commands, admin list tables. Each decorator provides the defaults and leaves you the control.
class Events_Controller extends Controller {
protected function get_namespace() : string {
return 'example/v1';
}
protected function get_rest_base() : string {
return 'events';
}
protected function get_model() : string {
return Event::class;
}
// GET/POST/PUT/DELETE wp-json/example/v1/events
}Dig into the docs
Design
The philosophy and architecture: models, traits, decorators, and queries.
Example Project
Build a project tracker step by step, from first model to shared traits.
Relationships
Relate models to each other — and to core posts, terms, and users.
Decorator: List Table
Admin list tables with schema-driven columns and row & bulk actions.
Decorator: REST API In progress
Expose models over the REST API with a real WP_REST_Controller.
Decorator: CLI In progress
Manage your models from the terminal with scaffolded WP-CLI commands.
Ready to get forging?
composer require humanmade/foundry
A word of warning: Foundry is highly experimental, incomplete, and not yet in production use. If you use it, be prepared to implement things yourself — and to tell us how it went.