PHPackages                             wptechnix/wp-models - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. wptechnix/wp-models

ActiveLibrary[Database &amp; ORM](/categories/database)

wptechnix/wp-models
===================

Type-safe Active Record ORM models and entities for WordPress plugin development

1.0.0(1mo ago)13↓100%MITPHPPHP ^8.1CI passing

Since Mar 19Pushed 1mo agoCompare

[ Source](https://github.com/WPTechnix/wp-models)[ Packagist](https://packagist.org/packages/wptechnix/wp-models)[ Docs](https://github.com/wptechnix/wp-models)[ RSS](/packages/wptechnix-wp-models/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (6)Versions (3)Used By (0)

WP Models
=========

[](#wp-models)

[![CI](https://github.com/wptechnix/wp-models/actions/workflows/ci.yml/badge.svg)](https://github.com/wptechnix/wp-models/actions/workflows/ci.yml)[![Latest Version](https://camo.githubusercontent.com/77e55df0b22651731593bd71b2197f3f1cea25decfe44af4ed63a2ac8ef285f7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7770746563686e69782f77702d6d6f64656c732e737667)](https://packagist.org/packages/wptechnix/wp-models)[![PHP Version](https://camo.githubusercontent.com/059e1afb51c5e623447ed66c7e84f29786d5babb8a3df0bf3ad484a230692aab/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f7770746563686e69782f77702d6d6f64656c732e737667)](https://packagist.org/packages/wptechnix/wp-models)[![License](https://camo.githubusercontent.com/e4c3828b64013c8607434cf7b511d496753a3a38c0a6b7cfe3da551bb2a451ae/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f7770746563686e69782f77702d6d6f64656c732e737667)](LICENSE)

**Type-safe Active Record ORM for WordPress plugin development.**

WP Models replaces scattered `$wpdb` queries with a clean data-access layer. Database rows become typed PHP objects with automatic casting, UTC-aware datetime handling, safe SQL compilation, built-in object caching, and pagination — all without pulling in a full framework.

---

Features
--------

[](#features)

- **Typed entities** — declare column types once (`int`, `decimal`, `bool`, `datetime`, `json`, `enum:ClassName`); the library casts on read and serialises on write, no manual conversion needed
- **Full query API** — `find`, `findWhere`, `paginate`, `create`, `update`, `delete`, `firstOrCreate`, `updateOrCreate`, bulk variants, `chunk`, `pluck`, and more
- **Safe SQL by design** — conditions use a column allow-list and `wpdb::prepare()` placeholders; SQL injection is structurally prevented, not just filtered
- **Automatic caching** — two-level WordPress object cache (query result IDs + entity rows); reads hit cache first, writes stay consistent automatically
- **Pagination** — `paginate()` returns an immutable result object with navigation helpers, page-number generation for UI controls, and `JsonSerializable` output for REST API responses
- **UTC datetime handling** — dates stored as UTC, returned in the WordPress site timezone, written back as UTC — no manual timezone conversion required
- **PHP 8.1 Backed Enums** — first-class enum casting in entity definitions
- **Dirty tracking** — know exactly which attributes changed before calling `save()`

---

Requirements
------------

[](#requirements)

RequirementVersion / NotesPHP`^8.1`WordPressLoaded by your plugin — provides `$wpdb`, `ABSPATH`, and timezone functions---

Installation
------------

[](#installation)

```
composer require wptechnix/wp-models
```

Load Composer's autoloader in your plugin file if you haven't already:

```
// my-plugin/my-plugin.php
require_once __DIR__ . '/vendor/autoload.php';
```

---

Quick Start
-----------

[](#quick-start)

### Define an Entity and a Model

[](#define-an-entity-and-a-model)

```
use WPTechnix\WPModels\AbstractEntity;
use WPTechnix\WPModels\AbstractModel;

// One entity class represents one database row
class OrderEntity extends AbstractEntity
{
    protected static string $primaryKey = 'id';

    protected static array $casts = [
        'id'         => 'int',
        'user_id'    => 'int',
        'total'      => 'decimal',   // stored and returned as string — safe for money
        'status'     => 'string',
        'is_paid'    => 'bool',
        'meta'       => 'json',      // auto-encoded/decoded
        'created_at' => 'datetime',  // stored UTC, returned in WP timezone
    ];
}

// One model class manages one database table
class OrderModel extends AbstractModel
{
    protected string $table       = 'orders';           // without WP prefix
    protected string $entityClass = OrderEntity::class;
    protected string $primaryKey  = 'id';

    // Security allow-list: only these columns may appear in WHERE / ORDER BY
    protected array $queryableColumns = [
        'id', 'user_id', 'status', 'is_paid', 'total', 'created_at',
    ];
}
```

### Create, Read, Update, Delete

[](#create-read-update-delete)

```
$orders = OrderModel::instance();

// Create
$id = $orders->create([
    'user_id' => get_current_user_id(),
    'total'   => '49.99',
    'status'  => 'pending',
    'meta'    => ['source' => 'checkout'],
]);

// Read — properties are typed, no casting needed
$order = $orders->find($id);
echo $order->total;                         // string '49.99'
echo $order->is_paid;                       // bool false
echo $order->created_at->format('d M Y');   // DateTimeImmutable in WP timezone
echo $order->meta['source'];               // 'checkout'

// Update
$orders->update($id, ['status' => 'complete', 'is_paid' => true]);

// Delete
$orders->delete($id);
```

### Query and Paginate

[](#query-and-paginate)

```
// Filter with conditions
$pending = $orders->findWhere([
    ['column' => 'status',  'value' => 'pending'],
    ['column' => 'is_paid', 'value' => false],
], orderBy: ['created_at' => 'DESC']);

// Paginate
$page = $orders->paginate(
    page: absint($_GET['paged'] ?? 1),
    perPage: 20,
    conditions: [['column' => 'user_id', 'value' => get_current_user_id()]],
);

foreach ($page->items as $order) {
    echo $order->total;
}

echo "Showing {$page->getFromNumber()}–{$page->getToNumber()} of {$page->total}";
```

---

Documentation
-------------

[](#documentation)

GuideDescription[Getting Started](docs/getting-started.md)Installation, table setup, first entity and model, full usage walkthrough[Entity Reference](docs/entity.md)Cast types, datetime and timezone handling, enum support, dirty tracking, save and delete[Model Reference](docs/model.md)Complete query API — CRUD, bulk operations, pagination, chunking, aggregates[Query Conditions](docs/clause-builder.md)Conditions format, all operators, OR groups, building conditions dynamically[PaginatedResult Reference](docs/paginated-result.md)Navigation helpers, page number generation, REST API serialisation[Caching](docs/caching.md)How caching works, what clears it, and how to handle edge cases---

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md).

License
-------

[](#license)

MIT © [WPTechnix](https://wptechnix.com) — see [LICENSE](LICENSE).

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance90

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 66.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

52d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e0fb168547a02b4a7b28df9403ab375f354591c75e814dce020f245e77f04e5f?d=identicon)[owaisahmed5300](/maintainers/owaisahmed5300)

---

Top Contributors

[![owaisahmed5300](https://avatars.githubusercontent.com/u/28684548?v=4)](https://github.com/owaisahmed5300 "owaisahmed5300 (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

wordpressdatabaseormmodelentityactive-recordwpdb

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/wptechnix-wp-models/health.svg)

```
[![Health](https://phpackages.com/badges/wptechnix-wp-models/health.svg)](https://phpackages.com/packages/wptechnix-wp-models)
```

###  Alternatives

[vlucas/spot2

Simple DataMapper built on top of Doctrine DBAL

605392.8k7](/packages/vlucas-spot2)[cycle/active-record

Provides a simple way to work with your database using Active Record pattern and Cycle ORM

671.3k3](/packages/cycle-active-record)[williarin/wordpress-interop

Interoperability library to work with WordPress database in third party apps

6610.9k2](/packages/williarin-wordpress-interop)[flightphp/active-record

Micro Active Record library in PHP, support chain calls, events, and relations.

163.0k](/packages/flightphp-active-record)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
