kevinpirnie / kpt-wpfieldframework
A PHP framework for creating WordPress Options Pages, Meta Boxes, and Gutenberg Blocks with repeatable field groups.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/kevinpirnie/kpt-wpfieldframework
Requires
- php: >=8.2
This package is auto-updated.
Last update: 2025-12-15 22:09:38 UTC
README
A PHP framework for creating WordPress Options Pages, Meta Boxes, and Gutenberg Blocks with repeatable field groups.
Requirements
- PHP 8.2+
- WordPress 6.8+
Installation
composer require kevinpirnie/kpt-wpfieldframework
Quick Start
In a Plugin
<?php // In your main plugin file. use KP\WPStarterFramework\Loader; // Bootstrap the framework. $framework = Loader::bootstrapPlugin(__FILE__); // Create an options page. $framework->addOptionsPage([ 'page_title' => 'My Plugin Settings', 'menu_title' => 'My Plugin', 'menu_slug' => 'my-plugin-settings', 'sections' => [ 'general' => [ 'title' => 'General Settings', 'fields' => [ [ 'id' => 'site_logo', 'type' => 'image', 'label' => 'Site Logo', 'description' => 'Upload your site logo.', ], [ 'id' => 'primary_color', 'type' => 'color', 'label' => 'Primary Color', 'default' => '#0073aa', ], ], ], ], ]); // Create a meta box. $framework->addMetaBox([ 'id' => 'page_settings', 'title' => 'Page Settings', 'post_types' => ['page'], 'fields' => [ [ 'id' => 'subtitle', 'type' => 'text', 'label' => 'Subtitle', ], [ 'id' => 'show_sidebar', 'type' => 'checkbox', 'label' => 'Layout', 'checkbox_label' => 'Show sidebar on this page', ], ], ]);
In a Theme
<?php // In your theme's functions.php. use KP\WPStarterFramework\Loader; add_action('after_setup_theme', function() { $framework = Loader::bootstrapTheme(); // Add your options pages and meta boxes here. });
Options Pages
Basic Options Page
$framework->addOptionsPage([ 'page_title' => 'Theme Options', 'menu_title' => 'Theme Options', 'capability' => 'manage_options', 'menu_slug' => 'theme-options', 'icon_url' => 'dashicons-admin-customizer', 'position' => 60, 'sections' => [ 'general' => [ 'title' => 'General', 'description' => 'General theme settings.', 'fields' => [ // Fields go here. ], ], ], ]);
Tabbed Options Page
$framework->addOptionsPage([ 'page_title' => 'Theme Options', 'menu_title' => 'Theme Options', 'menu_slug' => 'theme-options', 'tabs' => [ 'general' => [ 'title' => 'General', 'sections' => [ 'branding' => [ 'title' => 'Branding', 'fields' => [ [ 'id' => 'logo', 'type' => 'image', 'label' => 'Logo', ], ], ], ], ], 'advanced' => [ 'title' => 'Advanced', 'sections' => [ 'code' => [ 'title' => 'Custom Code', 'fields' => [ [ 'id' => 'custom_css', 'type' => 'code', 'label' => 'Custom CSS', 'code_type' => 'text/css', ], ], ], ], ], ], ]);
Submenu Options Page
$framework->addOptionsPage([ 'page_title' => 'Plugin Settings', 'menu_title' => 'Settings', 'menu_slug' => 'my-plugin-settings', 'parent_slug' => 'options-general.php', // Under Settings menu. 'sections' => [ // ... ], ]);
Retrieving Options
use KP\WPStarterFramework\Framework; // Get all options. $options = get_option('theme_options'); // Get specific option. $logo_id = $options['logo'] ?? ''; // Using the Storage class. $storage = Framework::getInstance()->getStorage(); $value = $storage->getOptionKey('theme_options', 'logo', '');
Meta Boxes
Post/Page Meta Box
$framework->addMetaBox([ 'id' => 'post_settings', 'title' => 'Post Settings', 'post_types' => ['post', 'page'], 'context' => 'normal', // normal, side, advanced 'priority' => 'high', // high, core, default, low 'fields' => [ [ 'id' => 'featured_video', 'type' => 'url', 'label' => 'Featured Video URL', ], ], ]);
Custom Post Type Meta Box
$framework->addMetaBox([ 'id' => 'product_details', 'title' => 'Product Details', 'post_types' => ['product'], 'fields' => [ [ 'id' => 'price', 'type' => 'number', 'label' => 'Price', 'min' => 0, 'step' => 0.01, ], [ 'id' => 'sku', 'type' => 'text', 'label' => 'SKU', ], ], ]);
User Profile Meta Box
$framework->addMetaBox([ 'id' => 'user_social', 'title' => 'Social Profiles', 'user_meta' => true, 'fields' => [ [ 'id' => 'twitter', 'type' => 'url', 'label' => 'Twitter URL', 'placeholder' => 'https://twitter.com/username', ], [ 'id' => 'linkedin', 'type' => 'url', 'label' => 'LinkedIn URL', 'placeholder' => 'https://linkedin.com/in/username', ], ], ]);
Nav Menu Item Meta Box
$framework->addMetaBox([ 'id' => 'menu_item_options', 'title' => 'Menu Item Options', 'nav_menu' => true, 'fields' => [ [ 'id' => 'icon_class', 'type' => 'text', 'label' => 'Icon Class', ], [ 'id' => 'hide_on_mobile', 'type' => 'checkbox', 'label' => 'Visibility', 'checkbox_label' => 'Hide on mobile devices', ], ], ]);
Retrieving Meta Values
// Standard WordPress function. $subtitle = get_post_meta($post_id, 'subtitle', true); // Using the Storage class. $storage = Framework::getInstance()->getStorage(); $subtitle = $storage->getMeta($post_id, 'subtitle', ''); // User meta. $twitter = $storage->getUserMeta($user_id, 'twitter', '');
Gutenberg Blocks
Creating a Block from a Meta Box
$framework->addMetaBox([ 'id' => 'cta_block', 'title' => 'Call to Action', 'post_types' => ['post', 'page'], 'create_block' => true, 'block_config' => [ 'name' => 'my-plugin/cta', 'title' => 'Call to Action', 'description' => 'A call to action block.', 'category' => 'common', 'icon' => 'megaphone', 'keywords' => ['cta', 'button', 'action'], ], 'fields' => [ [ 'id' => 'heading', 'type' => 'text', 'label' => 'Heading', ], [ 'id' => 'content', 'type' => 'textarea', 'label' => 'Content', ], [ 'id' => 'button_text', 'type' => 'text', 'label' => 'Button Text', ], [ 'id' => 'button_url', 'type' => 'url', 'label' => 'Button URL', ], ], ]);
Custom Block Rendering
$framework->addMetaBox([ 'id' => 'testimonial_block', 'title' => 'Testimonial', 'post_types' => ['post'], 'create_block' => true, 'block_config' => [ 'name' => 'my-plugin/testimonial', 'title' => 'Testimonial', 'icon' => 'format-quote', 'render_template' => get_template_directory() . '/blocks/testimonial.php', // Or use a callback: // 'render_callback' => 'my_render_testimonial_block', ], 'fields' => [ [ 'id' => 'quote', 'type' => 'textarea', 'label' => 'Quote', ], [ 'id' => 'author', 'type' => 'text', 'label' => 'Author', ], [ 'id' => 'photo', 'type' => 'image', 'label' => 'Photo', ], ], ]);
Block template file (blocks/testimonial.php):
<?php /** * Testimonial block template. * * @var array $attributes Block attributes. * @var array $fields Field configurations. */ $quote = $attributes['quote'] ?? ''; $author = $attributes['author'] ?? ''; $photo = $attributes['photo'] ?? 0; ?> <blockquote class="testimonial-block"> <?php if ($photo): ?> <div class="testimonial-photo"> <?php echo wp_get_attachment_image($photo, 'thumbnail'); ?> </div> <?php endif; ?> <p class="testimonial-quote"><?php echo esc_html($quote); ?></p> <?php if ($author): ?> <cite class="testimonial-author"><?php echo esc_html($author); ?></cite> <?php endif; ?> </blockquote>
Field Types
Text-Based Fields
// Text [ 'id' => 'title', 'type' => 'text', 'label' => 'Title', 'placeholder' => 'Enter title...', 'default' => '', 'required' => true, ] // Email [ 'id' => 'email', 'type' => 'email', 'label' => 'Email Address', ] // URL [ 'id' => 'website', 'type' => 'url', 'label' => 'Website URL', ] // Password [ 'id' => 'api_key', 'type' => 'password', 'label' => 'API Key', ] // Number [ 'id' => 'quantity', 'type' => 'number', 'label' => 'Quantity', 'min' => 0, 'max' => 100, 'step' => 1, ] // Telephone [ 'id' => 'phone', 'type' => 'tel', 'label' => 'Phone Number', ] // Hidden [ 'id' => 'hidden_value', 'type' => 'hidden', 'default' => 'some-value', ]
Date/Time Fields
// Date [ 'id' => 'event_date', 'type' => 'date', 'label' => 'Event Date', 'date_format' => 'yy-mm-dd', ] // Datetime [ 'id' => 'start_time', 'type' => 'datetime', 'label' => 'Start Date & Time', ] // Time [ 'id' => 'opening_time', 'type' => 'time', 'label' => 'Opening Time', ] // Week [ 'id' => 'target_week', 'type' => 'week', 'label' => 'Target Week', ] // Month [ 'id' => 'birth_month', 'type' => 'month', 'label' => 'Birth Month', ]
Selection Fields
// Select dropdown [ 'id' => 'country', 'type' => 'select', 'label' => 'Country', 'placeholder' => 'Select a country...', 'options' => [ 'us' => 'United States', 'ca' => 'Canada', 'uk' => 'United Kingdom', ], ] // Select with optgroups [ 'id' => 'city', 'type' => 'select', 'label' => 'City', 'options' => [ 'United States' => [ 'nyc' => 'New York', 'la' => 'Los Angeles', ], 'Canada' => [ 'tor' => 'Toronto', 'van' => 'Vancouver', ], ], ] // Multi-select [ 'id' => 'categories', 'type' => 'multiselect', 'label' => 'Categories', 'options' => [ 'tech' => 'Technology', 'health' => 'Health', 'sports' => 'Sports', ], 'size' => 5, ] // Single checkbox [ 'id' => 'featured', 'type' => 'checkbox', 'label' => 'Featured', 'checkbox_label' => 'Mark this item as featured', ] // Multiple checkboxes [ 'id' => 'features', 'type' => 'checkboxes', 'label' => 'Features', 'options' => [ 'wifi' => 'Free WiFi', 'parking' => 'Free Parking', 'pool' => 'Swimming Pool', ], ] // Radio buttons [ 'id' => 'size', 'type' => 'radio', 'label' => 'Size', 'options' => [ 'sm' => 'Small', 'md' => 'Medium', 'lg' => 'Large', ], 'default' => 'md', ]
Text Areas & Editors
// Textarea [ 'id' => 'description', 'type' => 'textarea', 'label' => 'Description', 'rows' => 5, 'cols' => 50, ] // WYSIWYG editor [ 'id' => 'content', 'type' => 'wysiwyg', 'label' => 'Content', 'rows' => 15, 'media_buttons' => true, 'teeny' => false, 'quicktags' => true, ] // Code editor [ 'id' => 'custom_css', 'type' => 'code', 'label' => 'Custom CSS', 'code_type' => 'text/css', // text/html, application/javascript, etc. 'rows' => 10, ]
Media Fields
// Image upload [ 'id' => 'featured_image', 'type' => 'image', 'label' => 'Featured Image', ] // File upload [ 'id' => 'download_file', 'type' => 'file', 'label' => 'Download File', ] // Gallery [ 'id' => 'gallery', 'type' => 'gallery', 'label' => 'Image Gallery', ]
Special Fields
// Color picker [ 'id' => 'accent_color', 'type' => 'color', 'label' => 'Accent Color', 'default' => '#ff6600', ] // Range slider [ 'id' => 'opacity', 'type' => 'range', 'label' => 'Opacity', 'min' => 0, 'max' => 100, 'step' => 5, 'default' => 100, ] // Post select [ 'id' => 'related_post', 'type' => 'post_select', 'label' => 'Related Post', 'post_type' => 'post', 'posts_per_page' => -1, ] // Page select [ 'id' => 'parent_page', 'type' => 'page_select', 'label' => 'Parent Page', ] // Term select [ 'id' => 'category', 'type' => 'term_select', 'label' => 'Category', 'taxonomy' => 'category', 'hide_empty' => false, ] // User select [ 'id' => 'author', 'type' => 'user_select', 'label' => 'Author', 'role' => 'author', // Optional: filter by role. ]
Layout Fields
// Heading [ 'id' => 'section_heading', 'type' => 'heading', 'label' => 'Advanced Settings', 'tag' => 'h3', // h1-h6 ] // Separator [ 'id' => 'separator_1', 'type' => 'separator', ] // Raw HTML [ 'id' => 'custom_html', 'type' => 'html', 'content' => '<p class="custom-notice">This is custom HTML content.</p>', ] // Message/Notice [ 'id' => 'warning_message', 'type' => 'message', 'message_type' => 'warning', // info, success, warning, error 'content' => 'This is a warning message.', ]
Repeater Fields
Basic Repeater
[
'id' => 'team_members',
'type' => 'repeater',
'label' => 'Team Members',
'button_label' => 'Add Team Member',
'min_rows' => 1,
'max_rows' => 10,
'collapsed' => true,
'sortable' => true,
'row_label' => 'Member',
'fields' => [
[
'id' => 'name',
'type' => 'text',
'label' => 'Name',
'required' => true,
],
[
'id' => 'title',
'type' => 'text',
'label' => 'Job Title',
],
[
'id' => 'photo',
'type' => 'image',
'label' => 'Photo',
],
[
'id' => 'bio',
'type' => 'textarea',
'label' => 'Biography',
],
],
]
Repeater with Multiple Field Types
[
'id' => 'faq_items',
'type' => 'repeater',
'label' => 'FAQ Items',
'button_label' => 'Add FAQ',
'fields' => [
[
'id' => 'question',
'type' => 'text',
'label' => 'Question',
'required' => true,
],
[
'id' => 'answer',
'type' => 'wysiwyg',
'label' => 'Answer',
'rows' => 5,
'teeny' => true,
],
[
'id' => 'category',
'type' => 'select',
'label' => 'Category',
'options' => [
'general' => 'General',
'billing' => 'Billing',
'shipping' => 'Shipping',
],
],
],
]
Retrieving Repeater Data
use KP\WPStarterFramework\Repeater; // Get repeater data. $team_members = get_post_meta($post_id, 'team_members', true); if (Repeater::hasRows($team_members)) { foreach ($team_members as $index => $member) { $name = $member['name'] ?? ''; $title = $member['title'] ?? ''; $photo = $member['photo'] ?? 0; $bio = $member['bio'] ?? ''; // Output member... } } // Get specific row value. $first_member_name = Repeater::getValue($team_members, 0, 'name', 'Default'); // Get all values for a specific field across all rows. $all_names = Repeater::getColumnValues($team_members, 'name'); // Get row count. $count = Repeater::getRowCount($team_members);
Field Groups
Basic Group
[
'id' => 'address',
'type' => 'group',
'label' => 'Address',
'fields' => [
[
'id' => 'street',
'type' => 'text',
'label' => 'Street Address',
],
[
'id' => 'city',
'type' => 'text',
'label' => 'City',
],
[
'id' => 'state',
'type' => 'text',
'label' => 'State',
],
[
'id' => 'zip',
'type' => 'text',
'label' => 'ZIP Code',
],
],
]
Retrieving Group Data
$address = get_post_meta($post_id, 'address', true); $street = $address['street'] ?? ''; $city = $address['city'] ?? ''; $state = $address['state'] ?? ''; $zip = $address['zip'] ?? '';
Field Configuration Options
All fields support these common options:
| Option | Type | Description |
|---|---|---|
id |
string | Unique field identifier (required) |
type |
string | Field type (required) |
label |
string | Field label |
description |
string | Help text displayed below field |
default |
mixed | Default value |
placeholder |
string | Placeholder text |
required |
bool | Whether field is required |
disabled |
bool | Whether field is disabled |
readonly |
bool | Whether field is read-only |
class |
string | Additional CSS class(es) |
attributes |
array | Additional HTML attributes |
sanitize |
callable | Custom sanitization callback |
validate |
callable | Custom validation callback |
Custom Sanitization
[
'id' => 'custom_field',
'type' => 'text',
'label' => 'Custom Field',
'sanitize' => function($value, $field) {
// Custom sanitization logic.
return strtoupper(sanitize_text_field($value));
},
]
Custom Validation
[
'id' => 'custom_field',
'type' => 'text',
'label' => 'Custom Field',
'validate' => function($value, $field) {
if (strlen($value) < 5) {
return 'Value must be at least 5 characters.';
}
return true;
},
]
Storage API
The Storage class provides a unified interface for all WordPress data storage:
use KP\WPStarterFramework\Framework; $storage = Framework::getInstance()->getStorage(); // Options $storage->getOption('option_name', $default); $storage->updateOption('option_name', $value); $storage->deleteOption('option_name'); $storage->getOptionKey('option_name', 'key', $default); $storage->updateOptionKey('option_name', 'key', $value); // Post Meta $storage->getMeta($post_id, 'meta_key', $default); $storage->updateMeta($post_id, 'meta_key', $value); $storage->deleteMeta($post_id, 'meta_key'); $storage->getAllMeta($post_id, ['key1', 'key2']); // User Meta $storage->getUserMeta($user_id, 'meta_key', $default); $storage->updateUserMeta($user_id, 'meta_key', $value); $storage->deleteUserMeta($user_id, 'meta_key'); // Term Meta $storage->getTermMeta($term_id, 'meta_key', $default); $storage->updateTermMeta($term_id, 'meta_key', $value); $storage->deleteTermMeta($term_id, 'meta_key'); // Comment Meta $storage->getCommentMeta($comment_id, 'meta_key', $default); $storage->updateCommentMeta($comment_id, 'meta_key', $value); $storage->deleteCommentMeta($comment_id, 'meta_key'); // Generic (auto-detect type) $storage->get('post', $object_id, 'meta_key', $default); $storage->update('user', $object_id, 'meta_key', $value); $storage->delete('term', $object_id, 'meta_key'); // Transients $storage->getTransient('transient_name', $default); $storage->setTransient('transient_name', $value, $expiration); $storage->deleteTransient('transient_name'); // Cache Management $storage->clearCache(); $storage->clearCache('post_meta_'); // Clear by prefix $storage->setUseCache(false);
Advanced Usage
Manual Initialization
use KP\WPStarterFramework\Framework; $framework = Framework::getInstance(); $framework->init( 'https://example.com/wp-content/plugins/my-plugin/assets', '/var/www/html/wp-content/plugins/my-plugin/assets' );
Requirements Check
use KP\WPStarterFramework\Loader; $requirements = Loader::checkRequirements('6.8', '8.2'); if (!$requirements['valid']) { Loader::displayRequirementErrors($requirements['errors']); return; } $framework = Loader::bootstrap();
Without Composer Autoloader
// Manually include and register the autoloader. require_once 'path/to/kp-wp-starter-framework/src/Loader.php'; KP\WPStarterFramework\Loader::register(); $framework = KP\WPStarterFramework\Loader::bootstrap();
JavaScript Events
The framework triggers custom jQuery events for extensibility:
// Repeater row added. $(document).on('kp-wsf-repeater-row-added', function(event, $newRow, $repeater) { console.log('New row added:', $newRow); }); // Repeater row before removal. $(document).on('kp-wsf-repeater-row-before-remove', function(event, $row, $repeater) { console.log('Row about to be removed:', $row); }); // Repeater row removed. $(document).on('kp-wsf-repeater-row-removed', function(event, $repeater) { console.log('Row removed from:', $repeater); });
Global JavaScript Object
The framework exposes a global KpWsfAdmin object:
// Manually initialize components. KpWsfAdmin.initColorPickers(); KpWsfAdmin.initDatePickers(); KpWsfAdmin.initCodeEditors(); KpWsfAdmin.initRangeSliders(); KpWsfAdmin.initRepeaterSortable(); KpWsfAdmin.initGallerySortable(); // Add repeater row programmatically. KpWsfAdmin.repeaterAddRow($('.kp-wsf-repeater'));
Hooks & Filters
The framework integrates with standard WordPress hooks. Meta boxes use these hooks:
add_meta_boxes- Register meta boxessave_post- Save post metapersonal_options_update- Save user meta (own profile)edit_user_profile_update- Save user meta (other profiles)show_user_profile- Display user fields (own profile)edit_user_profile- Display user fields (other profiles)wp_nav_menu_item_custom_fields- Display nav menu fieldswp_update_nav_menu_item- Save nav menu meta
License
MIT License. See LICENSE file for details.
Author
Kevin Pirnie - iam@kevinpirnie.com
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please open an issue on GitHub.