ralfhortt / wp-post-meta
WordPress post meta utilities for admin list tables
Requires (Dev)
- mockery/mockery: ^1.6
- pestphp/pest: ^2.0
- yoast/phpunit-polyfills: ^2.0
This package is auto-updated.
Last update: 2026-03-17 09:57:30 UTC
README
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
- 📡 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
- PHP 8.1+
- WordPress 5.0+
Installation
composer require ralfhortt/wp-post-meta
Quick Start
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
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
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
Type Helpers
String Fields
Meta::for(objectSubtypes: 'product') ->addString(key: 'sku', description: 'Product SKU') ->register();
Boolean Fields
Automatically converts between boolean (API) and string (storage):
Meta::for(objectSubtypes: 'product') ->addBoolean(key: 'featured', description: 'Is featured') ->register();
Integer & Number Fields
Meta::for(objectSubtypes: 'product') ->addInteger(key: 'stock', description: 'Stock quantity') ->addNumber(key: 'price', description: 'Product price') ->register();
Array Fields
Meta::for(objectSubtypes: 'product') ->addArray(key: 'tags', description: 'Product tags') ->register();
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
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
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
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
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
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
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
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
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
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('<span style="color: %s">%d</span>', $color, $stock); } ) ->sortable(keys: 'stock') ->register();
API Reference
Meta Class
Factory
Meta::for(string $objectType = 'post', string|array $objectSubtypes = []): self
Type Helpers
addString(key, description, getCallback, updateCallback, authCallback): selfaddBoolean(key, description, authCallback): selfaddInteger(key, description, authCallback): selfaddNumber(key, description, authCallback): selfaddArray(key, description, authCallback): selfaddDate(key, description, inputFormat, authCallback): self
Low-Level
add(key, type, description, getCallback, updateCallback, authCallback): self
Authorization
needsCapability(capability): self- Require specific WordPress capability
Admin Integration
addColumn(key, label, render, sortable): self- Add admin list table columnshowInQuickEdit(keys): self- Enable quick edit for field(s)showInEditor(keys, title, metaBoxId, context): self- Add Gutenberg editor meta box
Registration
register(): void
Columns Class
Factory
Columns::for(string|array $postTypes): self
Basic
add(key, label, render): self
Type Helpers
addCurrency(key, label, decimals, currencySymbol, symbolPosition): selfaddDate(key, label, format, inputFormat): selfaddBoolean(key, label, trueLabel, falseLabel): selfaddImage(key, label, width, height, isAttachmentId): selfaddList(key, label, separator, limit): self
Configuration
sortable(keys): selfpriority(value): self
Registration
register(): void
Development
Running Tests
composer test # Run all tests composer test:coverage # With coverage (requires Xdebug/PCOV)
Changelog
v2.0.0
- Complete rewrite with fluent API
- Added REST API meta field registration (
Metaclass) - Added column integration to
Metaclass - Removed abstract class pattern
- Added type-specific helpers
- PHP 8.1+ with named arguments
v1.0.0
- Initial release with abstract class pattern
License
MIT
Author
Ralf Hortt - mail@ralfhortt.dev