Quick answer: WordPress already stores custom fields as post meta in the database. You can show the native Custom Fields panel, save values with update_post_meta, read them with get_post_meta, and register fields more formally with register_meta. For simple needs that is enough. When non-technical editors need tidy UI, repeatable field groups or relational fields, a dedicated plugin is still the better tool.
Custom fields are how you attach structured data to a post, page or custom post type: a subtitle, a button URL, an event date, a product badge, a video ID. Themes and plugins read those values and render them in templates.
This article focuses on doing that with core WordPress features, so you understand the machinery even if you later choose Advanced Custom Fields or similar.
Where custom field data lives
WordPress keeps meta in the wp_postmeta table: meta_key, meta_value, tied to a post_id. Values are stored as strings; arrays and objects are serialised. That design is flexible and easy to misuse (huge serialised blobs, unbounded keys). Keep keys namespaced (iws_subtitle rather than subtitle) to avoid clashes with plugins.
User meta and term meta exist too (term meta arrived in WordPress 4.4). The patterns below focus on post meta, which is what most people mean by custom fields.
Show the built-in Custom Fields panel
In the classic editor, open Screen Options and tick Custom Fields. You get a simple key/value UI at the bottom of the edit screen. Editors can add a key, enter a value and update the post.
Limitations you will feel quickly:
- No field types (everything is a text box)
- Easy for editors to typo keys
- No instructions beside the field
- Awkward for repeatable rows or related posts
Still, for a developer-only field or a one-off value, it works and needs zero plugins.
If you cannot see existing keys, WordPress hides keys that start with an underscore (protected meta). Plugins often prefix internal keys that way on purpose.
Reading and writing meta in code
Read a value in a theme template
$subtitle = get_post_meta( get_the_ID(), 'iws_subtitle', true );
if ( $subtitle ) {
echo '<p class="subtitle">' . esc_html( $subtitle ) . '</p>';
}
The third argument true returns a single value. Use false when one key may have multiple rows.
Save a value programmatically
update_post_meta( $post_id, 'iws_subtitle', sanitize_text_field( $value ) );
Related helpers: add_post_meta, delete_post_meta, get_post_custom. Always sanitise on write and escape on output.
Hook into post save for a custom meta box
A typical pattern:
add_meta_boxto draw inputs on the edit screen- On
save_post, verify nonce, check capabilities, sanitise, thenupdate_post_meta
That gives editors a labelled form without a plugin. You own the code, so you also own testing when WordPress updates.
register_meta for a cleaner contract
WordPress 4.6+ improved register_meta(). You can declare a meta key’s type, description, and whether it shows in the REST API. As the block editor work progresses (Gutenberg is still a beta plugin today), REST-visible meta matters more for editing UIs.
Example shape:
register_meta( 'post', 'iws_subtitle', array(
'type' => 'string',
'description' => 'Short subtitle under the title',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
Registering meta does not by itself draw a pretty UI in the classic editor, but it documents the field and helps API clients behave.
Querying posts by meta
meta_query in WP_Query filters posts by meta keys and values. It is powerful and easy to make slow. Index-friendly patterns:
- Keep compared values simple
- Avoid
LIKE %...%on large sites when you can - Do not store lists you need to query inside serialised arrays
If you need heavy filtering (faceted product attributes, complex event calendars), plan the data model carefully or use a plugin built for that query pattern.
A small worked example
Say every service page needs a short “starting from” price shown under the title. Register iws_price_from with register_meta, add a meta box with one text input on the page edit screen, save it with a nonce and capability check, then print it in page-services.php with esc_html( get_post_meta( get_the_ID(), 'iws_price_from', true ) ). That is roughly forty lines of code, no plugin, and an editor-friendly field.
When a plugin is the wiser choice
Skip the heroics and use a field plugin when:
- Editors need date pickers, image fields, true/false toggles, repeaters
- You want field groups per template or per category
- Non-developers will manage the definitions
- You need relationships between post types with a usable UI
Advanced Custom Fields (Free and Pro), CMB2, Pods and others exist for this reason. The point of learning core meta is not to reject plugins; it is to know what they wrap and to debug when values do not save.
For theme structure around custom data, understanding the template hierarchy helps you place get_post_meta calls in the right files.
Security and capability checks
Meta boxes without capability checks are a common vulnerability pattern. On save:
- Verify a nonce
- Confirm
current_user_can( 'edit_post', $post_id )(or stricter) - Do not blindly loop
$_POSTinto meta - Sanitise by field type (
absint,esc_url_raw,wp_kses_post, etc.)
Protected meta (underscore prefix) stays out of the native Custom Fields UI, but it is not encryption. Anyone with database access can read it. Do not store secrets in post meta.
Performance habits
- Cache expensive lookups if you read many keys on archive pages
- Prefer one structured key over dozens of tiny keys when the data is always used together (balance this against query needs)
- Clear or update caches when meta changes if you add object caching later
- Watch autoloaded options separately; that is not post meta, but teams often confuse the two when chasing bloat. Tools that list the largest autoloaded options help you find the real cause of a slow admin.
Custom fields versus custom post types
If “field” is actually a whole kind of content (events, staff members, portfolio items), a custom post type plus a few meta keys is cleaner than one post type with an explosion of fields. Registering types and taxonomies is a different API (register_post_type, register_taxonomy); meta then hangs off each item. See also WordPress taxonomies when grouping is the real need rather than a single value.
FAQ
Do custom fields slow WordPress down?
Not at small scale. Poorly planned meta_query usage and enormous serialised values do.
Can I use custom fields on pages and products?
Yes. Any post type that supports custom-fields (or where you add meta boxes yourself) can store meta. WooCommerce products already use meta extensively.
Will Gutenberg change custom fields?
The block editor work makes REST-registered meta more important. Meta boxes still exist in Gutenberg’s beta, but the experience is evolving. Test your meta boxes on a staging site with the Gutenberg plugin active, and see our checklist to prepare for Gutenberg.
Is the native UI enough for clients?
Rarely for ongoing editorial work. It is fine for developers and for tiny sites.
Use core first, then decide
WordPress custom fields without a plugin are really just post meta with a thin UI. Learn get_post_meta, safe saves, and register_meta so you can build small features cleanly and debug larger ones. When editors need a polished interface, add a field plugin on purpose rather than by panic. If you want help designing a tidy content model for a business site, we can help you turn visitors into customers with structure that stays maintainable.