Tania Rascia Web Design and Development

Skip Navigation
WordPress Theme Development: Creating Custom Fields and Meta Boxes

WordPress Theme Development: Creating Custom Fields and Meta Boxes

By Tania Rascia  /  111 responses

Learn how to create custom fields in WordPress – such as for images, text, and checkboxes – without a plugin.

In the first part of my WordPress tutorial series, we learned what WordPress is and how to create and use a basic theme. In part two, we learned more advanced concepts like adding comments and images.

In part three, we’re going to learn how to add basic custom fields to a post, save them in the database, and show them on the front end of the website, without a plugin. And it’s going to be easy, you’ll love it.



  • Create a custom post type.
  • Add a custom meta box.
  • Create the following custom fields:
    • Input text field
    • Textarea
    • Checkbox
    • Select Menu
    • Image (and image upload)

This tutorial is independent of the previous installments. If you’ve been following along, you can just add this on to what you already have. If not, you can still do this tutorial from scratch as long as you have a basic knowledge of WordPress.

I’m still confused. What’s going on?

There are a lot of WordPress-specific words going around already, and it’s more confusing than I’d like. The gist of it is, have you ever wanted to add an extra field to a WordPress post? Maybe a date, or a URL, or an e-mail address? We’re going to learn how to do that. Have you ever wanted to add another upload field for images outside of the “Featured Image” thumbnail option? In this article, we’ll also learn how to add an upload button and browse through the media gallery to insert an image.

It’s basically a simplified version of what Advanced Custom Fields does, but without any plugins.

I promise I’ll make it as simple as possible.

Now we’ll begin.

Anything throughout this article prefixed with your_ is custom, and you can change the name.

Create a Custom Post

I’m starting off with a completely empty WordPress theme, just like in part one. I’m going to create a custom post called Your Post, with the id your_post. You can call it whatever you want, but it might be easiest to practice the first time around with the same names I used.

Here’s the code that will go into functions.php.


function create_post_your_post() {
	register_post_type( 'your_post',
			'labels'       => array(
				'name'       => __( 'Your Post' ),
			'public'       => true,
			'hierarchical' => true,
			'has_archive'  => true,
			'supports'     => array(
			'taxonomies'   => array(
	register_taxonomy_for_object_type( 'category', 'your_post' );
	register_taxonomy_for_object_type( 'post_tag', 'your_post' );
add_action( 'init', 'create_post_your_post' );

You can go more in-depth about all the options of creating a custom post here. I made a very simple one. The most important things that it does:

  • Registers a post type called Your Post, with the id your_post.
  • Supports a titlethe_title(), editorthe_content(), excerptthe_excerpt(), and thumbnail/featured image – the_post_thumbnail(). (Must add theme support for the thumbnail).
  • Supports taxonomies, or ways to group posts, with tags and categories.

Displaying the Custom Post

Now, to display the post on the front end. You can display the contents of a custom post anywhere. I’m just going to put it in page.php for testing purposes, so any page I make will display my custom loop.


$args = array(
	'post_type' => 'your_post',
$your_loop = new WP_Query( $args ); 

if ( $your_loop->have_posts() ) : while ( $your_loop->have_posts() ) : $your_loop->the_post(); 
$meta = get_post_meta( $post->ID, 'your_fields', true ); ?>

<!-- contents of Your Post -->

<?php endwhile; endif; wp_reset_postdata(); ?>

The variable $your_loop can be anything, I’m just sticking to a common theme of your_ throughout this article so you know what you can change. $meta = get_post_meta( $post->ID, 'your_fields', true ); is not necessary right now, but will be essential later.

Back End

Updating the title and editor fields like in a regular post…

Screen Shot 2016-08-09 at 3.16.33 PM


Inserting the template codes like normal…

<?php the_title(); ?>

<?php the_content(); ?>
Front End

And here we are. I have no styles or anything, because it’s not necessary for the point of the article, and I don’t like adding unnecessary complexity.

Screen Shot 2016-08-09 at 3.22.47 PM

Alright, so the custom post is all set up now.

Create a Meta Box

From here, the original source for much of the code comes from WordPress Meta Boxes: a Comprehensive Developer’s Guide by Alex Mansfield, and Create WordPress Post Custom Meta Boxes by Paulund. Both of those are very good, detailed articles, and I’ve attempted to simplify some of those processes here.

Here’s the code to add a meta box.

function add_your_fields_meta_box() {
		'your_fields_meta_box', // $id
		'Your Fields', // $title
		'show_your_fields_meta_box', // $callback
		'your_post', // $screen
		'normal', // $context
		'high' // $priority
add_action( 'add_meta_boxes', 'add_your_fields_meta_box' );

More detail on this function can be found here. The actual function here is add_meta_box( $id, $title, $callback, $screen, $context, $priority ). The most important ones are $title, which is Your Fields, and $screen (or page), which is where the meta box will be added. You can add it to a regular post or page, among other things, but I chose to add it to your_post, our custom post from earlier.

Now if you go back into your post, you’ll see this below the editor.

Screen Shot 2016-08-09 at 3.54.09 PM

It’s an empty meta box! Additionally, if you click on Screen Options at the top of the post, you’ll see Your Fields in the options.

Screen Shot 2016-08-09 at 3.56.09 PM

Neat. Now it’s time to start putting stuff in there.

Save Fields in the Database

First, the function that will display all your custom fields.

function show_your_fields_meta_box() {
	global $post;  
		$meta = get_post_meta( $post->ID, 'your_fields', true ); ?>

	<input type="hidden" name="your_meta_box_nonce" value="<?php echo wp_create_nonce( basename(__FILE__) ); ?>">

    <!-- All fields will go here -->

	<?php }

We’ll return to <!-- All fields will go here --> in a moment. For now, directly below the above function, we’re going to paste in this big chunk of code (modified from Paulund) that will save all your_fields to the database.

function save_your_fields_meta( $post_id ) {   
	// verify nonce
	if ( !wp_verify_nonce( $_POST['your_meta_box_nonce'], basename(__FILE__) ) ) {
		return $post_id; 
	// check autosave
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return $post_id;
	// check permissions
	if ( 'page' === $_POST['post_type'] ) {
		if ( !current_user_can( 'edit_page', $post_id ) ) {
			return $post_id;
		} elseif ( !current_user_can( 'edit_post', $post_id ) ) {
			return $post_id;
	$old = get_post_meta( $post_id, 'your_fields', true );
	$new = $_POST['your_fields'];

	if ( $new && $new !== $old ) {
		update_post_meta( $post_id, 'your_fields', $new );
	} elseif ( '' === $new && $old ) {
		delete_post_meta( $post_id, 'your_fields', $old );
add_action( 'save_post', 'save_your_fields_meta' );

Make sure your_meta_box_nonce matches the name attribute, and you’ve specified your_fields in the meta box function. This code is verifying the Nonce from the first function, making sure the user has the correct permissions to update the fields, and updating the post meta fields.

It’s a confusing block of code at first, but fortunately you don’t have to do much with this one besides make sure the id of the post matches the custom meta box you created.

Create Custom Fields

Now we’re going to return to <!-- All fields will go here -->. This is where we’re going to make our input text field, textbox, checkbox, select menu, and image upload. Right above that, we created a variable called $meta. This reaches into the your_fields table in the database and retrieves the information: $meta = get_post_meta( $post->ID, 'your_fields', true );. We’re going to make an array and put all of our custom fields in it.

You can use custom CSS to make your admin panel look nice, which I can possibly go into further if requested, but getting the basic functionality is more important to me for now, so I’m just going to use the default styles. Wrapping everything in a <p> isn’t semantically correct, but it’s keeping it organized for now.

Text Input

I’m going to add a regular text input. The regular-text class is just a built in WordPress admin style. Whatever you put in the straight brackets will be the code for your custom field. If I wanted to make this text field an e-mail address, for example on a custom post called “Team Members” with contact information for each team member, I might call it your_fields[email] instead of your_fields[text].

	<label for="your_fields[text]">Input Text</label>
	<input type="text" name="your_fields[text]" id="your_fields[text]" class="regular-text" value="<?php echo $meta['text']; ?>">

The code for the text area is almost the same as the input, except the value is echoed out between the tags instead of as an attribute. The rows, cols, and style placed on it doesn’t really matter. It’s important to leave no space between the tags, to ensure no extra space ends up in your textbox.

	<label for="your_fields[textarea]">Textarea</label>
	<textarea name="your_fields[textarea]" id="your_fields[textarea]" rows="5" cols="30" style="width:500px;"><?php echo $meta['textarea']; ?></textarea>

There might be several ways to implement the checkbox, but this is one way that worked for me.

	<label for="your_fields[checkbox]">Checkbox
		<input type="checkbox" name="your_fields[checkbox]" value="checkbox" <?php if ( $meta['checkbox'] === 'checkbox' ) echo 'checked'; ?>>
Select Menu

You can include as many options as you want here, but I’m just doing two for the example.

	<label for="your_fields[select]">Select Menu</label>
	<select name="your_fields[select]" id="your_fields[select]">
			<option value="option-one" <?php selected( $meta['select'], 'option-one' ); ?>>Option One</option>
			<option value="option-two" <?php selected( $meta['select'], 'option-two' ); ?>>Option Two</option>

The image upload is going to be the most complicated one. The actual display code isn’t any more complicated than the other fields have been. It’s important to include the meta-image class on the text input, and image-upload class on the submit button. I just put some quick max-width styling on the image for a simple preview.

	<label for="your_fields[image]">Image Upload</label><br>
	<input type="text" name="your_fields[image]" id="your_fields[image]" class="meta-image regular-text" value="<?php echo $meta['image']; ?>">
	<input type="button" class="button image-upload" value="Browse">
<div class="image-preview"><img src="<?php echo $meta['image']; ?>" style="max-width: 250px;"></div>

Actually getting this to do anything is the more complicated part. Directly below, you can place this JavaScript snippet (modified from Theme Foundation).

This code will open the built in WordPress media gallery when you click browse, and insert the selected URL into the input field.

    jQuery(document).ready(function ($) {
      // Instantiates the variable that holds the media library frame.
      var meta_image_frame;
      // Runs when the image button is clicked.
      $('.image-upload').click(function (e) {
        // Get preview pane
        var meta_image_preview = $(this).parent().parent().children('.image-preview');
        // Prevents the default action from occuring.
        var meta_image = $(this).parent().children('.meta-image');
        // If the frame already exists, re-open it.
        if (meta_image_frame) {
        // Sets up the media library frame
        meta_image_frame = wp.media.frames.meta_image_frame = wp.media({
          title: meta_image.title,
          button: {
            text: meta_image.button
        // Runs when an image is selected.
        meta_image_frame.on('select', function () {
          // Grabs the attachment selection and creates a JSON representation of the model.
          var media_attachment = meta_image_frame.state().get('selection').first().toJSON();
          // Sends the attachment URL to our custom image input field.
          meta_image_preview.children('img').attr('src', media_attachment.url);
        // Opens the media library frame.

And that’s everything that needs to go into the functions.php file! As with the CSS styling from earlier, we can place this JavaScript in a separate file and call it on the admin pages, but for simplicity I’m just including it in the functions.php file for now. At the end of the article, I’ll include the entire code in case you got lost anywhere along the way.

Display the Output

Now, we have all our fields, and they all appear in the Your Fields meta box. I’m going to fill them all out, check the checkbox, select an option, upload an image of the majestic Doge Lion, and publish the post.

These will all go in the $your_loop query. Make sure $meta = get_post_meta( $post->ID, 'your_fields', true ); is in your loop.

Screen Shot 2016-08-10 at 12.51.25 PM

Text Input

Simple output of the text field.

<h1>Text Input</h1>
<?php echo $meta['text']; ?>

Simple output of the textarea.

<?php echo $meta['textarea']; ?>

I’m going to check if the checkbox has been checked or not, and display a message based on it.

<?php if ( $meta['checkbox'] === 'checkbox') { ?>
Checkbox is checked.
<?php } else { ?> 
Checkbox is not checked. 
<?php } ?>

I’ll show you two things you can do with the select. The basic output will be the value attribute.

<h1>Select Menu</h1>
<p>The actual value selected.</p>
<?php echo $meta['select']; ?>

You can also use a PHP switch statement to display a specific message based on the value selected.

<p>Switch statement for options.</p>
	switch ( $meta['select'] ) {
		case 'option-one':
			echo 'Option One';
		case 'option-two':
			echo 'Option Two';
			echo 'No option selected';

And finally, displaying the image.

<img src="<?php echo $meta['image']; ?>">

Here is the final output of everything I filled out earlier.

Screen Shot 2016-08-10 at 1.14.30 PM


If you had any trouble following along the way, here are the final files for page.php and functions.php on GitHub Gist.

Functions Page

This article only goes over the most basic way to add custom fields and update the database. There are a thousand more ways to do it, and plugins like ACF that will do it for you as well. I think it’s useful to know how to interact with the database and learn how to do it without a plugin.

It’s also a good idea to sanitize the output of all your fields.

If you’ve found any errors, or have any additional information that would make this better or more simple to understand, please let me know!

Email List

Get friendly updates, infrequently.

Tania Rascia

Hiya. I'm Tania, a web developer from Chicago. Hope you enjoyed my ad-free, bullshit-free site. If you liked it, tell someone about it!

 GitHub  Twitter

Write a response

Your email address will not be published.


  • Khalil says:

    Good jobs think you

  • sujata says:

    Hi Tania ,

    This is really great tutorial. Thank you so much. It is very helpful for me.

    But I have one question. I want to add remove image functionality for image field. How to do that? and do you have any tutorial on creating “repeater” custom field?

  • Mahdi Pakravan says:

    Verry Thankkkss !
    So Nice!

  • dzikri says:

    super helping for my project tania, many thanx

  • Remco says:

    Your tutorials have been super useful! Very clear and just… well… useful! Thanks ?

  • akash says:

    hello mam i have make a custom field in wordpress below post using acf plugin. i want to show post title in frontend but hide in source code and want show the custom field value inplace of post title in source code.

  • justme says:

    Data not display after I saved, can anyone tell me what was wrong?

  • Stephen G. says:

    Hi Tania,

    Your tutorial is AMAZING!!!! The only issue I found, and that other people seem to have, is when switching from a your_post, your_fields etc… to some other format they tend to get the Illegal string offset ‘text’ in (…) functions.php on line (…) error. The way I was able to get this to work is as follows.

    1. Set up the functions.php as described in the tutorial

    2. in the functions.php in the show_xxx_fields_meta_box() function change $meta property from true to false

    3. In the admin area go to the custom post and input you data in the text field input (or whatever) and update. Nothing will happen as you expect.

    4. Go back to the functions.php file and change false back to true. If you followed the tutorial exactly you should be able to save input data.

    Don’t know why this works and perhaps someone has a better solution. This is what I discovered spending sometime trouble shooting.

    • Stephen G. says:

      Just an update. I figured out the issue. There has to be a check to make sure the $meta is in an array.

      • justme says:

        sample code pls…

      • Stephen G. says:

        here’s a sample input field. the Values contains the check for an array and will fix the illegal string error

        Input Text

        <input type="text" name="your_fields[text]" id="your_fields[text]" class="regular-text" value="”>

  • Jackie Emery says:

    Hi Tania,

    thank you for all the great resources!

    I’ve followed the first two parts but decided to start with a new wp install so I don’t get any unnecessary css.

    please could you tell me what you had in your index.php file for an empty wp install?

    I have created the page.php and functions but they don’ show on the front end, I wonder if you could give me some guidance so I can follow along.

  • Abdul Rehman says:

    Data is not displayed on page on front end!! Can you help me in that

  • rishabh jhalani says:

    how to pass a page id in $screen in meta-box

  • North says:

    I have been looking for this for ages. You are amazing, thank you!

  • Alex says:

    Thanks for this great tutorial. It is short and easy to use. It helped me a LOT!

    Keep up the great work.


  • Samuel says:

    Thank you very much for this article. I work in an SEO firm and we’re trying to create a WordPress theme that could score 90+ in Google pagespeed. This article was so helpfull! Thank you again! You can see the work in progress here : https://www.premiersurgoogle.ca/optimisation/

  • kellig says:

    Thank you very much for this clear and easy to follow tutorial ! Good job 🙂

  • Tomi says:

    Wish you best. Thanks for this tutorial! keep going Tania.

  • Anwar Khan says:

    Excellent tutorial it helps me a lot.
    Thanks for such a wonderful tutorial.

  • Barjinder says:

    Hi, I am getting problem to show custom field information, as I have all the display code in page.php but it’s ignoring that and taking single.php to show output. Is there need ant change there?


  • mahesh says:


  • Pierre Simard says:

    Hi Tania,

    I just found your site and I actually learned all of this already but I looked at your work and I seems to be even better than the tuts I did. I think I’m gonna look over my first theme and see if I can improve it with the things you say here. Pretty sure I’ll succeed to it.

    Therewith I’m writing to you cause I’m at the following step. My theme is on an Alpha mode. I’m developing it on a server and then when I have a new version ready, I download it and upload it on another server by FTP.

    I would like to simply make the update possible via the Dashboard (on the production server) like any other themes.

    Not sure that I’m clear here. Ask me any questions if you don’t understand what I’m trying to ask with my non-native English.

    Thanks and regards 🙂

    PS : We can’t sell on ThemeForest yet cause the theme’s not ready and we don’t want to put the theme on the WP repository cause we don’t want to give it away right now.

  • Satish gupta Gupta says:

    Hello mam,
    I want to put an images slideshow with name as custom field in post. Please explain me code for this.
    Thanking you.

  • Ken says:

    This was such an amazing write up! Thank you for taking the time to outline the process right down to the label breakdown for various id’s, etc. I know enough to be dangerous and this just upped my level.

  • Michael J. Wiebe says:

    Hey Tania,
    Thanks again for your work on this, its a great tutorial! I was having trouble with the checkbox and I discovered that the value of checkbox gets set to ‘on’ when it is checked. I figured this out by using a console_log function written for PHP. This resolved my issue on the frontend and in the functions.

  • Leonardo Apolinário says:

    Thanks for your tutorial.
    I have the following errors:
    1) Undefined index: your_meta_box_nonce in
    2) Input text: Warning: Illegal string offset ‘text’ in (…) functions.php on line (…) Notice: Uninitialized string offset:
    3) Textarea: Warning: Illegal string offset ‘textarea’ in
    4) Checkbox: Warning: Illegal string offset ‘checkbox’ in
    This is because the values are used without been checked.
    The solution is wrap the code “$meta[‘something’] with:
    “if (is_array($meta) && isset($meta[‘something’])){”
    This was tested in Xubuntu 16.04.
    My git https://github.com/leonardoapolinario/wordpress-custom

  • V.O says:

    i loved this tutorial, thanks. U maked everything simple and easy to understand, Im new in wordpress but i have website developing knowledge and that threat helped me from going over the wordpress documentation and spend a lot of time reading. 😀

  • Tom says:

    Thanks for the help.
    So everything works for me, but how to i get seperat posts of my new post type (Your Post)?

  • SAEED says:

    hi my dear
    how i can make a repeatable meta box field and show it in wordpress theme
    please help me

  • SHOLAR says:

    Am really very happy to find you, your tutorial is absolutely educative,
    pls i will love you to recommend a nice book for me to learn PHP very well because i still have that interest in understanding the language very well

  • Wordpress Programmer says:

    I absolutely like this tutorial. This is really helpful to used my business site development.
    Thank you very much.

  • Deco says:

    In this part 3, on the single page I’m seeing a loop of what i published, instead of one single entry content.
    Would be nice to show us how to implement this function into the 1 and 2 tutorial, and not in a new install. That will help a lot.

  • Scott says:

    Awesome blog! Do you have any hints for aspiring writers? I’m planning to start my own site soon but I’m a little lost on everything. Would you propose starting with a free platform like WordPress or go for a paid option? There are so many choices out there that I’m totally overwhelmed .. Any tips? Bless you!


  • Cristian says:

    “God” job with these series of tutorials.

    I think it’s very useful either you want to start developing your first theme (see my first one here https://goo.gl/tS4BZ1 ) or just wanna make an idea about how it’s should be done.

  • john says:

    Part 1 pretty straight forward I can do this

    Part 2 starting to lose me a little, getting to be a bumpy ride

    Part 3 erm ok , think I have thi…

    Error 500 …

    not too bad for day 1 🙂 hah 3:18 am , no wonder I crashed

  • Erlebach Tomas says:

    Thank you very much. I’m teacher and I’m very glad to see so good tutorial.

  • Ricardo Mota says:

    Thanks for another great article.

  • Viktor says:

    Hi, firstly I love your tuts and your whole blog. It’s been in my bookmarks fow a while now.

    Now the question if you don’t mind.

    Do I understand it correctly that if you are building a static website and you store all the content in posts (in wordpress) and display them the way yo udescribbed above mit echo $meta..? I’m still a little bit confused how to approach this so I’ll re-read your tutorials again and again.

    Right now I’m using ACF plugin because I’m not sure how to approach it. Let’s say a single page website for I don’t know a waffle company or whatever (not exactly waffle companies but some quick google examples: https://departures-international.com/, http://www.adidas.cz/climazone). I’m just curious how would you tackle this structuraly? I have to say I’m new to wordpress.

    Thank you very much and I wish you all the best to the future!

    • Tania says:

      Hi there,

      If you’re new to WordPress, you should start with the first part of this 3 part tutorial, “Developing a WordPress Theme from Scratch”. It should give you more of an idea how to tackle any website you see with WordPress. You can also use ACF plugin, I would probably recommend them over doing every little custom field from scratch, but it’s also helpful to know how.