Skip to main content

WordPress Custom Fields

Introduction

WordPress custom fields are a powerful feature that allows developers to store additional information (metadata) alongside posts, pages, and custom post types. While the standard WordPress editor provides fields for titles, content, and featured images, custom fields expand these capabilities by letting you store any type of additional data that your specific website or application may need.

Custom fields operate on a key-value system, where each piece of metadata has:

  • A key (or name) that identifies the type of data
  • A value that contains the actual data

In this guide, we'll explore how to create, retrieve, update, and display custom fields, as well as more advanced implementations using the WordPress Meta API.

Understanding Custom Fields

What Are Custom Fields Used For?

Custom fields are perfect for storing:

  • Product pricing and specifications
  • Event dates and locations
  • Rating scores for reviews
  • SEO metadata
  • Custom layout settings
  • Any additional data that doesn't fit in the standard content area

How Custom Fields Work

WordPress stores custom fields in the wp_postmeta table in the database with four columns:

  1. meta_id - A unique ID for each meta entry
  2. post_id - The ID of the post the metadata belongs to
  3. meta_key - The name/identifier of the custom field
  4. meta_value - The stored data

Basic Usage of Custom Fields

Enabling the Custom Fields Meta Box

By default, WordPress includes a simple interface for custom fields, but it may be hidden. To enable it:

  1. In the WordPress editor, click on the three dots in the top-right corner
  2. Select "Preferences"
  3. Click on "Panels"
  4. Enable "Custom Fields"
  5. Click "Apply"

Adding Custom Fields via the WordPress Admin

Once enabled, you'll see the Custom Fields meta box below the post editor:

  1. Enter a name for your custom field in the "Name" field
  2. Enter the value in the "Value" field
  3. Click "Add Custom Field"

Retrieving Custom Field Values

To retrieve a custom field value in your theme, use the get_post_meta() function:

php
<?php
$key_value = get_post_meta(get_the_ID(), 'key_name', true);

if (!empty($key_value)) {
echo '<p>' . esc_html($key_value) . '</p>';
}
?>

The parameters for get_post_meta() are:

  1. The post ID (often retrieved with get_the_ID())
  2. The meta key name
  3. A boolean that determines if you want a single value (true) or an array of values (false)

Advanced Custom Fields Usage

Adding Custom Fields Programmatically

For more control, you can add custom fields using WordPress functions:

php
<?php
// Add or update a custom field
update_post_meta($post_id, 'product_price', '99.99');

// Get a custom field value
$price = get_post_meta($post_id, 'product_price', true);

// Delete a custom field
delete_post_meta($post_id, 'product_price');
?>

Working with Multiple Values

Custom fields can store multiple values under the same key:

php
<?php
// Add values (without overwriting existing ones)
add_post_meta($post_id, 'product_color', 'Red');
add_post_meta($post_id, 'product_color', 'Blue');
add_post_meta($post_id, 'product_color', 'Green');

// Get all values (returns an array)
$colors = get_post_meta($post_id, 'product_color', false);

// Output each color
if (!empty($colors)) {
echo '<ul>';
foreach ($colors as $color) {
echo '<li>' . esc_html($color) . '</li>';
}
echo '</ul>';
}
?>

Creating a Custom Meta Box

While the default custom fields interface works, creating custom meta boxes offers better organization and user experience:

php
<?php
// Register meta box
function my_custom_meta_box() {
add_meta_box(
'product_details_meta', // ID
'Product Details', // Title
'product_details_callback', // Callback function
'product', // Screen (post type)
'normal', // Context
'high' // Priority
);
}
add_action('add_meta_boxes', 'my_custom_meta_box');

// Meta box display callback
function product_details_callback($post) {
// Add nonce for security
wp_nonce_field('product_details_save', 'product_details_nonce');

// Get current values
$price = get_post_meta($post->ID, 'product_price', true);
$sku = get_post_meta($post->ID, 'product_sku', true);

// Output fields
?>
<p>
<label for="product_price">Price:</label>
<input type="text" id="product_price" name="product_price"
value="<?php echo esc_attr($price); ?>" />
</p>
<p>
<label for="product_sku">SKU:</label>
<input type="text" id="product_sku" name="product_sku"
value="<?php echo esc_attr($sku); ?>" />
</p>
<?php
}

// Save meta box data
function save_product_details($post_id) {
// Security checks
if (!isset($_POST['product_details_nonce']) ||
!wp_verify_nonce($_POST['product_details_nonce'], 'product_details_save')) {
return;
}

// Don't save on autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}

// Check permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}

// Save the price
if (isset($_POST['product_price'])) {
update_post_meta(
$post_id,
'product_price',
sanitize_text_field($_POST['product_price'])
);
}

// Save the SKU
if (isset($_POST['product_sku'])) {
update_post_meta(
$post_id,
'product_sku',
sanitize_text_field($_POST['product_sku'])
);
}
}
add_action('save_post', 'save_product_details');
?>

Displaying Custom Fields in Your Theme

In the Loop

To display custom fields within The Loop, use this pattern:

php
<?php
// Start the loop
if (have_posts()) :
while (have_posts()) : the_post();
// Regular post content
the_title('<h1>', '</h1>');
the_content();

// Get and display custom fields
$price = get_post_meta(get_the_ID(), 'product_price', true);
if (!empty($price)) {
echo '<p class="price">Price: $' . esc_html($price) . '</p>';
}

$sku = get_post_meta(get_the_ID(), 'product_sku', true);
if (!empty($sku)) {
echo '<p class="sku">SKU: ' . esc_html($sku) . '</p>';
}
endwhile;
endif;
?>

Custom Template for Post Types

For a custom post type with fields, you might create a template file like single-product.php:

php
<?php
/**
* Template for displaying single product posts
*/

get_header();
?>

<div class="product-container">
<?php
while (have_posts()) : the_post();
?>
<div class="product-main">
<h1><?php the_title(); ?></h1>

<?php if (has_post_thumbnail()) : ?>
<div class="product-image">
<?php the_post_thumbnail('large'); ?>
</div>
<?php endif; ?>

<div class="product-details">
<?php
// Get product price
$price = get_post_meta(get_the_ID(), 'product_price', true);
if (!empty($price)) :
?>
<div class="product-price">
<span class="label">Price:</span>
<span class="value">$<?php echo esc_html($price); ?></span>
</div>
<?php endif; ?>

<?php
// Get product SKU
$sku = get_post_meta(get_the_ID(), 'product_sku', true);
if (!empty($sku)) :
?>
<div class="product-sku">
<span class="label">SKU:</span>
<span class="value"><?php echo esc_html($sku); ?></span>
</div>
<?php endif; ?>
</div>

<div class="product-description">
<?php the_content(); ?>
</div>
</div>
<?php
endwhile;
?>
</div>

<?php
get_footer();
?>

Custom Fields in WP_Query

You can query posts based on custom field values using the meta_query parameter:

php
<?php
$args = array(
'post_type' => 'product',
'posts_per_page' => 10,
'meta_query' => array(
array(
'key' => 'product_price',
'value' => '50',
'compare' => '>=',
'type' => 'NUMERIC'
)
)
);

$product_query = new WP_Query($args);

if ($product_query->have_posts()) :
while ($product_query->have_posts()) : $product_query->the_post();
// Display products
the_title('<h2>', '</h2>');

$price = get_post_meta(get_the_ID(), 'product_price', true);
echo '<p>Price: $' . esc_html($price) . '</p>';
endwhile;
wp_reset_postdata();
endif;
?>

The meta_query accepts these comparison operators:

  • = (default)
  • !=
  • >, >=, <, <=
  • LIKE
  • NOT LIKE
  • IN
  • NOT IN
  • BETWEEN
  • NOT BETWEEN
  • EXISTS
  • NOT EXISTS

Real-World Example: Event Calendar

Let's create a simple event system with custom fields:

php
<?php
// Register Event post type
function create_event_post_type() {
register_post_type('event',
array(
'labels' => array(
'name' => 'Events',
'singular_name' => 'Event'
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-calendar',
'supports' => array('title', 'editor', 'thumbnail')
)
);
}
add_action('init', 'create_event_post_type');

// Add meta box for event details
function event_details_meta_box() {
add_meta_box(
'event_details_meta',
'Event Details',
'event_details_callback',
'event',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'event_details_meta_box');

// Meta box callback
function event_details_callback($post) {
wp_nonce_field('event_details_save', 'event_details_nonce');

$event_date = get_post_meta($post->ID, 'event_date', true);
$event_time = get_post_meta($post->ID, 'event_time', true);
$event_location = get_post_meta($post->ID, 'event_location', true);
?>

<p>
<label for="event_date">Event Date:</label>
<input type="date" id="event_date" name="event_date"
value="<?php echo esc_attr($event_date); ?>" />
</p>
<p>
<label for="event_time">Event Time:</label>
<input type="time" id="event_time" name="event_time"
value="<?php echo esc_attr($event_time); ?>" />
</p>
<p>
<label for="event_location">Event Location:</label>
<input type="text" id="event_location" name="event_location"
value="<?php echo esc_attr($event_location); ?>" style="width:100%;" />
</p>
<?php
}

// Save event details
function save_event_details($post_id) {
if (!isset($_POST['event_details_nonce']) ||
!wp_verify_nonce($_POST['event_details_nonce'], 'event_details_save')) {
return;
}

if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;

$fields = array('event_date', 'event_time', 'event_location');

foreach ($fields as $field) {
if (isset($_POST[$field])) {
update_post_meta(
$post_id,
$field,
sanitize_text_field($_POST[$field])
);
}
}
}
add_action('save_post', 'save_event_details');

// Function to display upcoming events
function display_upcoming_events($count = 5) {
$today = date('Y-m-d');

$args = array(
'post_type' => 'event',
'posts_per_page' => $count,
'meta_key' => 'event_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => 'event_date',
'value' => $today,
'compare' => '>=',
'type' => 'DATE'
)
)
);

$events = new WP_Query($args);

ob_start();

if ($events->have_posts()) :
echo '<div class="upcoming-events">';
echo '<h2>Upcoming Events</h2>';
echo '<ul class="event-list">';

while ($events->have_posts()) : $events->the_post();
$event_date = get_post_meta(get_the_ID(), 'event_date', true);
$event_time = get_post_meta(get_the_ID(), 'event_time', true);
$event_location = get_post_meta(get_the_ID(), 'event_location', true);

// Format the date
$formatted_date = date('F j, Y', strtotime($event_date));

echo '<li class="event-item">';
echo '<h3><a href="' . get_permalink() . '">' . get_the_title() . '</a></h3>';
echo '<p class="event-meta">';
echo '<span class="event-date">' . esc_html($formatted_date) . '</span>';

if (!empty($event_time)) {
echo ' at <span class="event-time">' . esc_html($event_time) . '</span>';
}

if (!empty($event_location)) {
echo ' | <span class="event-location">' . esc_html($event_location) . '</span>';
}

echo '</p>';
echo '</li>';
endwhile;

echo '</ul>';
echo '</div>';
else :
echo '<p>No upcoming events found.</p>';
endif;

wp_reset_postdata();

return ob_get_clean();
}

// Register shortcode to display events
function events_shortcode($atts) {
$atts = shortcode_atts(array(
'count' => 5,
), $atts);

return display_upcoming_events($atts['count']);
}
add_shortcode('upcoming_events', 'events_shortcode');
?>

To use this in your theme, you would add:

php
<?php echo display_upcoming_events(3); ?>

Or via a shortcode in your content:

[upcoming_events count="3"]

While you can build custom field functionality from scratch, several plugins make the process much easier:

  1. Advanced Custom Fields (ACF) - The most popular option, offering a user-friendly interface to create various field types
  2. CMB2 - A developer-oriented toolkit for building custom meta boxes and fields
  3. Meta Box - Another developer-friendly option with excellent documentation

Best Practices for Custom Fields

  1. Use prefixes for your custom field names to avoid conflicts with other plugins
  2. Sanitize input data before saving to the database
  3. Escape output data when displaying values
  4. Use appropriate field types for different kinds of data
  5. Consider performance with large amounts of meta data
  6. Use a custom meta box instead of the default meta box for better user experience

Summary

WordPress custom fields provide a powerful way to extend the CMS beyond its basic functionality. With custom fields, you can:

  • Store additional data alongside your posts and pages
  • Create custom interfaces for data entry
  • Query content based on metadata
  • Display this data in your theme using WordPress's built-in functions

From simple use cases like adding a subtitle to complex implementations like product catalogs or event systems, custom fields are an essential tool in advanced WordPress development.

Additional Resources

  • WordPress Developer Documentation: Post Meta Functions
  • Practice Exercise: Create a "Recipe" custom post type with custom fields for ingredients, cooking time, and difficulty level
  • Challenge: Build a product review system that uses custom fields to store ratings, pros, and cons

The Meta API is also useful for user metadata (add_user_meta(), get_user_meta(), etc.) and term metadata (add_term_meta(), get_term_meta(), etc.), which follow similar patterns to the post meta functions we've explored in this guide.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)