PHPackages                             ralfhortt/wp-post-meta - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. ralfhortt/wp-post-meta

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ralfhortt/wp-post-meta
======================

WordPress post meta utilities for admin list tables

014PHPCI failing

Since Mar 17Pushed 3mo agoCompare

[ Source](https://github.com/Horttcore/wp-post-meta)[ Packagist](https://packagist.org/packages/ralfhortt/wp-post-meta)[ RSS](/packages/ralfhortt-wp-post-meta/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependenciesVersions (1)Used By (0)

[![WordPress Post Meta](.github/banner.svg)](.github/banner.svg)

[![Tests](https://github.com/Horttcore/wp-post-meta/actions/workflows/tests.yml/badge.svg)](https://github.com/Horttcore/wp-post-meta/actions/workflows/tests.yml)[![codecov](https://camo.githubusercontent.com/3cc14a8e7486a711a0abf5bd6ff27bb10d671eacba10de3379d246aa0c5dd9e5/68747470733a2f2f636f6465636f762e696f2f67682f486f727474636f72652f77702d706f73742d6d6574612f67726170682f62616467652e737667)](https://codecov.io/gh/Horttcore/wp-post-meta)[![PHP Version](https://camo.githubusercontent.com/21d6ef3992fce11cce4a9edaceb86edd04d89416507a1f399b7756214822d6bd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f72616c66686f7274742f77702d706f73742d6d657461)](https://packagist.org/packages/ralfhortt/wp-post-meta)[![Latest Version](https://camo.githubusercontent.com/d496dff103ff5459adf8d8d70b09abc47f231f1fd5367fb876e18aacf55c1d75/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f72616c66686f7274742f77702d706f73742d6d657461)](https://packagist.org/packages/ralfhortt/wp-post-meta)[![License](https://camo.githubusercontent.com/279461d3fece3e8fc0badca1bf0d86de2ebee0d7167bde4e56b4262b26b76474/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f72616c66686f7274742f77702d706f73742d6d657461)](https://github.com/Horttcore/wp-post-meta/blob/main/LICENSE)

WordPress Post Meta Utilities
=============================

[](#wordpress-post-meta-utilities)

A modern, fluent WordPress Composer package for working with post meta fields in the REST API and admin list tables.

Features
--------

[](#features)

- 📡 **REST API Meta** - Expose post meta in WordPress REST API with type conversion
- 📋 **Admin Columns** - Add custom columns to admin list tables
- 🔐 **Authorization** - Fine-grained permission control
- 🎯 **Type Helpers** - Built-in support for strings, booleans, integers, numbers, arrays, dates
- ✨ **Fluent API** - Chainable, self-documenting code
- 🚀 **Zero Boilerplate** - No class extension needed
- ✅ **Fully Tested** - 141 tests passing

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

[](#requirements)

- PHP 8.1+
- WordPress 5.0+

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

[](#installation)

```
composer require ralfhortt/wp-post-meta
```

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

[](#quick-start)

### REST API Meta Fields

[](#rest-api-meta-fields)

```
use RalfHortt\Meta\Meta;

Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->addNumber(key: 'price', description: 'Product price')
    ->addBoolean(key: 'featured', description: 'Is featured')
    ->needsCapability(capability: 'edit_posts')
    ->register();
```

Your REST API responses now include these fields:

```
{
  "id": 123,
  "title": {...},
  "meta": {
    "sku": "PROD-123",
    "price": 99.99,
    "featured": true
  }
}
```

### REST API + Admin Columns

[](#rest-api--admin-columns)

Add columns to admin list tables at the same time:

```
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->addColumn(key: 'sku', label: __('SKU', 'plugin'), sortable: true)

    ->addNumber(key: 'price', description: 'Product price')
    ->addColumn(
        key: 'price',
        label: __('Price', 'plugin'),
        render: fn($postId) => '$' . number_format(get_post_meta($postId, 'price', true), 2),
        sortable: true
    )

    ->needsCapability(capability: 'edit_posts')
    ->register();
```

### REST API + Admin Columns + Quick Edit

[](#rest-api--admin-columns--quick-edit)

Enable quick editing of meta fields directly from the admin list table:

```
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->addColumn(key: 'sku', label: __('SKU', 'plugin'), sortable: true)
    ->showInQuickEdit(keys: 'sku')

    ->addNumber(key: 'price', description: 'Product price')
    ->addColumn(
        key: 'price',
        label: __('Price', 'plugin'),
        render: fn($postId) => '$' . number_format(get_post_meta($postId, 'price', true), 2),
        sortable: true
    )
    ->showInQuickEdit(keys: 'price')

    ->needsCapability(capability: 'edit_posts')
    ->register();
```

REST API Usage
--------------

[](#rest-api-usage)

### Type Helpers

[](#type-helpers)

#### String Fields

[](#string-fields)

```
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->register();
```

#### Boolean Fields

[](#boolean-fields)

Automatically converts between boolean (API) and string (storage):

```
Meta::for(objectSubtypes: 'product')
    ->addBoolean(key: 'featured', description: 'Is featured')
    ->register();
```

#### Integer &amp; Number Fields

[](#integer--number-fields)

```
Meta::for(objectSubtypes: 'product')
    ->addInteger(key: 'stock', description: 'Stock quantity')
    ->addNumber(key: 'price', description: 'Product price')
    ->register();
```

#### Array Fields

[](#array-fields)

```
Meta::for(objectSubtypes: 'product')
    ->addArray(key: 'tags', description: 'Product tags')
    ->register();
```

#### Date Fields

[](#date-fields)

Automatically converts to/from ISO 8601:

```
Meta::for(objectSubtypes: 'event')
    ->addDate(
        key: 'event_date',
        description: 'Event date',
        inputFormat: 'Y-m-d'  // Storage format
    )
    ->addDate(
        key: 'created_at',
        description: 'Created timestamp',
        inputFormat: 'timestamp'  // Unix timestamp
    )
    ->register();
```

### Authorization

[](#authorization)

Control who can edit meta fields via the REST API:

```
// Require edit_posts capability
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku')
    ->needsCapability(capability: 'edit_posts')
    ->register();

// Require manage_options (admin) capability
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'internal_note')
    ->needsCapability(capability: 'manage_options')
    ->register();

// Custom WordPress capability
Meta::for(objectSubtypes: 'product')
    ->addNumber(key: 'wholesale_price')
    ->needsCapability(capability: 'manage_woocommerce')
    ->register();

// Per-field custom authorization callback
Meta::for(objectSubtypes: 'product')
    ->add(
        key: 'cost',
        type: 'number',
        authCallback: function($allowed, $context, $objectId) {
            return current_user_can('manage_shop') && $objectId > 0;
        }
    )
    ->register();
```

### Custom Callbacks

[](#custom-callbacks)

#### Transform on Read

[](#transform-on-read)

```
Meta::for(objectSubtypes: 'product')
    ->add(
        key: 'price',
        type: 'object',
        getCallback: function ($object) {
            $price = get_post_meta($object['id'], 'price', true);
            $tax = get_post_meta($object['id'], 'tax_rate', true);
            return [
                'net' => (float) $price,
                'gross' => (float) $price * (1 + (float) $tax)
            ];
        }
    )
    ->register();
```

#### Validate on Write

[](#validate-on-write)

```
Meta::for(objectSubtypes: 'product')
    ->add(
        key: 'sku',
        type: 'string',
        updateCallback: function ($value, $object) {
            if (!preg_match('/^[A-Z]{3}-\d{4}$/', $value)) {
                return new \WP_Error('invalid_sku', 'Invalid SKU format');
            }
            return update_post_meta($object->ID, 'sku', strtoupper($value));
        }
    )
    ->register();
```

### Quick Edit

[](#quick-edit)

Enable inline editing of meta fields from the admin list table:

```
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->addNumber(key: 'price', description: 'Product price')
    ->addBoolean(key: 'featured', description: 'Is featured')

    // Enable quick edit for specific fields
    ->showInQuickEdit(keys: ['sku', 'price', 'featured'])

    ->needsCapability(capability: 'edit_posts')
    ->register();
```

Quick edit automatically renders appropriate input types:

- **String** fields → text input
- **Number/Integer** fields → number input
- **Boolean** fields → checkbox

### Editor Meta Boxes

[](#editor-meta-boxes)

Add fields to Gutenberg sidebar panels for inline editing in the block editor:

```
Meta::for(objectSubtypes: 'product')
    ->addString(key: 'sku', description: 'Product SKU')
    ->addNumber(key: 'price', description: 'Product price')
    ->addInteger(key: 'stock', description: 'Stock quantity')

    // Group fields in sidebar meta box
    ->showInEditor(
        keys: ['sku', 'price', 'stock'],
        title: __('Product Details', 'plugin')
    )

    ->needsCapability(capability: 'edit_posts')
    ->register();
```

**Multiple Meta Boxes:**

```
Meta::for(objectSubtypes: 'product')
    // Basic information
    ->addString(key: 'sku', description: 'SKU')
    ->addNumber(key: 'price', description: 'Price')
    ->showInEditor(
        keys: ['sku', 'price'],
        title: __('Basic Info', 'plugin'),
        metaBoxId: 'product-basic',
        context: 'side'  // Sidebar (default)
    )

    // Inventory details in main content area
    ->addInteger(key: 'stock', description: 'Stock')
    ->addBoolean(key: 'backorder', description: 'Allow backorders')
    ->showInEditor(
        keys: ['stock', 'backorder'],
        title: __('Inventory', 'plugin'),
        metaBoxId: 'product-inventory',
        context: 'normal'  // Main content area
    )

    ->register();
```

**Supported Field Types:**

- String → Text input
- Number/Integer → Number input
- Boolean → Toggle switch

**Note:** Array and object types are automatically skipped (Gutenberg sidebar only supports simple types).

### Complete Example

[](#complete-example)

```
use RalfHortt\Meta\Meta;

add_action('init', function () {
    Meta::for(objectSubtypes: 'product')
        // SKU field - REST API + admin column + quick edit + editor
        ->addString(key: 'sku', description: 'Product SKU')
        ->addColumn(key: 'sku', label: __('SKU', 'plugin'), sortable: true)
        ->showInQuickEdit(keys: 'sku')
        ->showInEditor(
            keys: 'sku',
            title: __('Product SKU', 'plugin'),
            metaBoxId: 'product-sku'
        )

        // Price field - REST API + formatted column + quick edit + editor
        ->addNumber(key: 'price', description: 'Product price')
        ->addColumn(
            key: 'price',
            label: __('Price', 'plugin'),
            render: fn($postId) => '$' . number_format(get_post_meta($postId, 'price', true), 2),
            sortable: true
        )
        ->showInQuickEdit(keys: 'price')
        ->showInEditor(
            keys: 'price',
            title: __('Pricing', 'plugin'),
            metaBoxId: 'product-pricing'
        )

        // Stock quantity - all features
        ->addInteger(key: 'stock', description: 'Stock quantity')
        ->addColumn(key: 'stock', label: __('Stock', 'plugin'), sortable: true)
        ->showInQuickEdit(keys: 'stock')
        ->showInEditor(
            keys: 'stock',
            title: __('Inventory', 'plugin'),
            metaBoxId: 'product-inventory'
        )

        // Featured flag - REST + column (no quick edit/editor for this one)
        ->addBoolean(key: 'featured', description: 'Is featured')
        ->addColumn(key: 'featured', label: __('Featured', 'plugin'))

        // Tags - REST only (arrays don't support quick edit/editor)
        ->addArray(key: 'tags', description: 'Product tags')

        // Release date
        ->addDate(key: 'release_date', description: 'Release date')
        ->addColumn(key: 'release_date', label: __('Release', 'plugin'))

        ->needsCapability(capability: 'edit_posts')
        ->register();
});
```

Admin Columns
-------------

[](#admin-columns)

For standalone admin columns without REST API, use the `Columns` class:

```
use RalfHortt\Meta\Columns;

Columns::for(postTypes: 'product')
    ->add(key: 'sku', label: __('SKU', 'plugin'))
    ->add(key: 'price', label: __('Price', 'plugin'))
    ->sortable(keys: ['sku', 'price'])
    ->register();
```

### Type-Specific Helpers

[](#type-specific-helpers)

```
Columns::for(postTypes: 'product')
    // Currency formatting
    ->addCurrency(
        key: 'price',
        label: __('Price', 'plugin'),
        decimals: 2,
        currencySymbol: '$'
    )

    // Date formatting
    ->addDate(
        key: 'event_date',
        label: __('Date', 'plugin'),
        format: 'F j, Y'
    )

    // Boolean display
    ->addBoolean(
        key: 'featured',
        label: __('Featured', 'plugin'),
        trueLabel: 'Yes',
        falseLabel: 'No'
    )

    // Image thumbnails
    ->addImage(
        key: 'thumbnail',
        label: __('Image', 'plugin'),
        width: 50,
        height: 50
    )

    // Array/list display
    ->addList(
        key: 'tags',
        label: __('Tags', 'plugin'),
        separator: ', ',
        limit: 3
    )

    ->sortable(keys: ['price', 'event_date'])
    ->register();
```

### Custom Renderers

[](#custom-renderers)

```
Columns::for(postTypes: 'product')
    ->add(
        key: 'stock',
        label: __('Stock', 'plugin'),
        render: function(int $postId): string {
            $stock = (int) get_post_meta($postId, 'stock', true);
            $color = $stock > 10 ? 'green' : ($stock > 0 ? 'orange' : 'red');
            return sprintf('%d', $color, $stock);
        }
    )
    ->sortable(keys: 'stock')
    ->register();
```

API Reference
-------------

[](#api-reference)

### Meta Class

[](#meta-class)

#### Factory

[](#factory)

- `Meta::for(string $objectType = 'post', string|array $objectSubtypes = []): self`

#### Type Helpers

[](#type-helpers-1)

- `addString(key, description, getCallback, updateCallback, authCallback): self`
- `addBoolean(key, description, authCallback): self`
- `addInteger(key, description, authCallback): self`
- `addNumber(key, description, authCallback): self`
- `addArray(key, description, authCallback): self`
- `addDate(key, description, inputFormat, authCallback): self`

#### Low-Level

[](#low-level)

- `add(key, type, description, getCallback, updateCallback, authCallback): self`

#### Authorization

[](#authorization-1)

- `needsCapability(capability): self` - Require specific WordPress capability

#### Admin Integration

[](#admin-integration)

- `addColumn(key, label, render, sortable): self` - Add admin list table column
- `showInQuickEdit(keys): self` - Enable quick edit for field(s)
- `showInEditor(keys, title, metaBoxId, context): self` - Add Gutenberg editor meta box

#### Registration

[](#registration)

- `register(): void`

### Columns Class

[](#columns-class)

#### Factory

[](#factory-1)

- `Columns::for(string|array $postTypes): self`

#### Basic

[](#basic)

- `add(key, label, render): self`

#### Type Helpers

[](#type-helpers-2)

- `addCurrency(key, label, decimals, currencySymbol, symbolPosition): self`
- `addDate(key, label, format, inputFormat): self`
- `addBoolean(key, label, trueLabel, falseLabel): self`
- `addImage(key, label, width, height, isAttachmentId): self`
- `addList(key, label, separator, limit): self`

#### Configuration

[](#configuration)

- `sortable(keys): self`
- `priority(value): self`

#### Registration

[](#registration-1)

- `register(): void`

Development
-----------

[](#development)

### Running Tests

[](#running-tests)

```
composer test              # Run all tests
composer test:coverage     # With coverage (requires Xdebug/PCOV)
```

Changelog
---------

[](#changelog)

### v2.0.0

[](#v200)

- Complete rewrite with fluent API
- Added REST API meta field registration (`Meta` class)
- Added column integration to `Meta` class
- Removed abstract class pattern
- Added type-specific helpers
- PHP 8.1+ with named arguments

### v1.0.0

[](#v100)

- Initial release with abstract class pattern

License
-------

[](#license)

MIT

Author
------

[](#author)

Ralf Hortt -

###  Health Score

21

—

LowBetter than 18% of packages

Maintenance55

Moderate activity, may be stable

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity12

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/45d54752dcf3da868ffe4a5ad2b876d6acb911fd914700b52e4d4777ff7d4a02?d=identicon)[Ralf Hortt](/maintainers/Ralf%20Hortt)

---

Top Contributors

[![Horttcore](https://avatars.githubusercontent.com/u/503252?v=4)](https://github.com/Horttcore "Horttcore (2 commits)")

### Embed Badge

![Health badge](/badges/ralfhortt-wp-post-meta/health.svg)

```
[![Health](https://phpackages.com/badges/ralfhortt-wp-post-meta/health.svg)](https://phpackages.com/packages/ralfhortt-wp-post-meta)
```

PHPackages © 2026

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