Strangely I don't find much sources on the web talking about my desires. So I hope it is a trivial thing I'm asking (although I fear it's not and Wordpress is just..).

Naturally I need custom fields in my custom post types.

Wordpress' native way of handling custom fields is very inconvenient and I can't expect my clients to deal with these dropdowns, adding fields as they need it–

So far I manage my custom fields with the "Advanced Custom Fields" plugin.

I wanna understand how to do this on my own and therefore hope for more flexibility in customizing the work area / admin area of the "new post" and "edit post" pages.

There must be a way to set custom fields in code and influence how the inputs are accessible on the admin page.

Providing a simple example, I want to add a quicklink-feature to the page, where the admin can add posts containing nothing but a title and a url (I'm aware of the native "Links" area and purposely avoid using it)

So far I made a simple one-file-plugin containing the declaration for the custom post type:


    Plugin Name: Got Nexxt Quicklinks 
    Plugin URI: none
    Description: Quicklinks custom post type + special admin area
    Author: René Eschke
    Version: 1.0
    Author URI: http://www.eschke.info/

    function my_custom_post_quicklinks() {
      $labels = array(
          'name'               => _x( 'Quicklinks', 'post type general name' ),
          'singular_name'      => _x( 'Quicklink', 'post type singular name' ),
          'add_new'            => _x( 'Hinzuf&uuml;gen', 'book' ),
          'add_new_item'       => __( 'Quicklink hinzuf&uuml;gen' ),
          'edit_item'          => __( 'Quicklink bearbeiten' ),
          'new_item'           => __( 'Neue Quicklink' ),
          'all_items'          => __( 'Alle Quicklinks' ),
          'view_item'          => __( 'Quicklink ansehen' ),
          'search_items'       => __( 'Quicklink durchsuchen' ),
          'not_found'          => __( 'Kein Quicklink gefunden' ),
          'not_found_in_trash' => __( 'Keine Quicklinks im Papierkorb gefunden' ), 
          'parent_item_colon'  => '',
          'menu_name'          => 'Quicklinks'
      $args = array(
        'labels' => $labels,
        'menu_icon' => 'dashicons-admin-links',
        'description' => 'Quicklinks sind vor allem für Shortcuts zu bestimmten Podcast-positionen da',
        'public' => true,
        'menu_position' => 5,
        'supports' => array('title'), 
        'has_archive' => false,
      register_post_type('quicklink', $args);
    add_action( 'init', 'my_custom_post_quicklinks' );


Again: I want to implement this myself all in code without using a plugin.


done with Advanced Custom Fields

3 Answers 3


I think you are looking for "meta boxes".

You will work with add_meta_box() to create one or multiple new meta-boxes for your post-types. (Codex)

You have to set up some callback function which holds the HTML of the fields you want to show/display.

And you will also need a function to save these fields using add_post_meta() and update_post_meta(). (Codex to ADD and UPDATE)

If you want to remove some existing meta-boxes you can use remove_meta_box(). (Codex)

Some Details:

With the following code you can create a new Meta-box on the new/edit screen of your "quicklinks" post-type. (cause I entered "quicklink" as the post-type)

function create_custom_metabox() {
        'my_meta', // HTML 'id' attribute of the metabox
        __( 'My Setting', 'textdomain' ), // Title of metabox
        'my_fields_callback', // Function that prints out the HTML for metabox
        'quicklink', // The post-type of writing screen on which to show the edit screen section (example 'post' or 'page')
        'normal', // The part of the page where the metabox should be shown ('normal', 'advanced', or 'side')
        'high' // The priority within the context where the boxes should show ('high', 'core', 'default' or 'low')
add_action( 'add_meta_boxes', 'create_custom_metabox' );

After this code you will already see a new Meta-Box, but it will be empty, there are no fields yet to show.

So next we create a new callback function with our new fields: (see function name and callback argument in add_meta_box() above)

function my_fields_callback( $post ) {

    // creating a custom nonce
    wp_nonce_field( basename( __FILE__ ), 'my_custom_nonce' );

    // see and get if some meta is already saved
    $stored_meta = get_post_meta( $post->ID );

    <!-- Textfield START -->
        <span class="my-row-title">
            <label for="meta-text" class="my-row-title"><?php _e( 'Text', 'textdomain' )?></label>
        <div class="my-row-content">
            <input type="text" name="meta-text" id="meta-text" placeholder="Text..." value="<?php if ( isset ( $stored_meta['meta-text'] ) ) echo $stored_meta['meta-text'][0]; ?>" />

    <!-- Textfield END -->


After this code, you will already see a new input field and a label. But if you enter something it will still not get saved! So we need to add a save function to this:

function save_my_meta( $post_id ) {

    // Checks save status
    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ 'my_custom_nonce' ] ) && wp_verify_nonce( $_POST[ 'my_custom_nonce' ], basename( __FILE__ ) ) ) ? 'true' : 'false';

    // Exits script depending on save status
    if ( $is_autosave || $is_revision || !$is_valid_nonce ) {

    // save our new created field
    if( isset( $_POST[ 'meta-text' ] ) ) {
        // if there is some content in the field, we update it
        update_post_meta( $post_id, 'meta-text', sanitize_text_field( $_POST[ 'meta-text' ] ) );

add_action( 'save_post', 'save_my_meta' );

After that we can save the values of your new field.

If you want to show these values you can use the get_post_meta() function. (Codex) like this:

$my_meta_value = get_post_meta( get_the_ID(), 'meta-text', true );

if( !empty( $my_meta_value ) ) {
    echo $my_meta_value;

So, you see there are several functions needed. There are a lots of tutorials out there, here is an old one which still functions.

I couldnt find one in german in the moment, sorry.

Be also sure to have an eye an sanitize the different values of the fields on saving and retrieving.

I hope this helps!


When you create a meta-box with the add_meta_box() function, you can set where the meta-box should appear with the context parameter...

            __( 'My Setting', 'textdomain' ), 
            'normal', // The part of the page where the metabox should be shown ('normal', 'advanced', or 'side')

If you choose side, the box will be created on the right sidebar.

Regarding ACF and fields without the wrapper container:

It seems when you create an field with ACF without the meta-box-wrapper/container, some elements are just hidden, and some extra styles are added to hide the container!

So I think the best way would be to load some custom css and maybe jQuery, only on the edit/new page of the post-type which has your custom fields.

function add_scripts_and_styles()
    global $typenow;
    if( 'quicklink' != $typenow )

    echo "
<script type='text/javascript'>
    jQuery(document).ready( function($) {
        $('#my_meta h3').hide();
<style type='text/css'>
    /* CUSTOM RULES */
add_action( 'admin_head-post-new.php', 'add_scripts_and_styles' );
add_action( 'admin_head-post.php', 'add_scripts_and_styles' );

Source: This Stackexchange answer

Nonces: I try to explain it, but maybe the codex will be clearer to you:

A nonce in the WP envoirnment is a security feature. Its a random and unique combination of numbers and letters which is only valid a limited time and also per user.

In this case we are using it in the form to check that the input-data are coming from the genuine user. The system will check the nonce upon saving to see that nobody/something else is trying to save the data.

Or as the codex says it:

The nonce field is used to validate that the contents of the form request came from the current site and not somewhere else.

You can read here and here about nonces.

  • Although this comes close to what I'm looking for the question is not fully answered yet. add_meta_box() gives ma a new box per new setting; what if I don't want the box? I'd like to print out the HTML just like Advanced Custom Fields does it; plain without any other HTML wrapping around it. Commented Jan 11, 2017 at 8:10
  • I have edited my answer with some infos and a link on how you could do it. I could not find another way to do this.
    – LWS-Mo
    Commented Jan 11, 2017 at 9:33
  • All info I find in your post is add_meta_box() related; Sure you edited? The tutorial you posted ("old one which still funcitons") also goes the meta_box-route. Commented Jan 11, 2017 at 10:06
  • Could you explain why you used wp_nonce_field() in your example? Commented Jan 11, 2017 at 10:45
  • Yes I edited my first post with an update, to let you know how you can set the position of the new meta_box, and also how you could hide the container. As you said my answer is only related to add_meta_box(), because this seems to be the only way to create them. (using default WP-code) So my update was about how you can hide the container around your new fields.
    – LWS-Mo
    Commented Jan 11, 2017 at 11:29

What you are looking for is a custom meta box

You will need add_meta_box(), take a look. Also here is a nice tutorial which deals with the basics: https://www.smashingmagazine.com/2011/10/create-custom-post-meta-boxes-wordpress/

  • This is an example I stumbled upon in my research. And it only makes custom meta boxes happening in the sidebar– Commented Jan 10, 2017 at 14:12

I found a totally customizable solution. The general approach illustrated by LWS-mo remains the same. But instead of using add_meta_box() I used add_post_meta()

add_meta_box() requires a callback function that prints out the HTML. Using add_post_meta() a callback has to be hooked in manually.

The key action hook here is edit_form_after_editor. (Actually there are a lot of hooks for the admin-work-area > WP Codex)

Another important insight I had: All these changes apply to every post type unless you return the functions when the post type differs your desired one. Therefore I made a helper-function, which needs to be called first thing in the action hooks.

Furthermore I skipped calling function names in the add_action function and used anonymous functions as parameter. I find this way more convenient; please correct me if I miss something bigger here.

Here's the code in short version:

// helper function to prevent editing other post types than desired
function is_my_posttype() {
    global $post;
    if ($post->post_type == "my_custom_posttype") return true;
    else return false;

// create custom post type
add_action("init", function() {
    $labels = ... 
    $args = ...
    register_post_type("my_custom_posttype", $args); 

// add custom fields 
add_action("add_meta_boxes", function() {
    if (!is_my_posttype()) return;
    add_post_meta($post->ID, "url", "", true); // true creates a unique custom field; no initial value needed
    // create as many as you need

// insert the html in both ADD and EDIT forms
// $post parameter required
add_action("edit_form_after_editor", function( $post ) {
    if (!is_my_posttype()) return;

    wp_nonce_field( basename( __FILE__ ), 'my_custom_nonce' ); // to check for later in save function 

    $stored_meta = get_post_meta( $post->ID );

    // now do whatever magic you want; 
    // it gets inserted straight below the editor
    // without any wrapping html cage
        <label ...>Some Label</label>
        <input type="text" name="url" value="<?php if (isset($stored_meta["url"])) echo $stored_meta["url"][0]; ?>" />
        Add even more

// make the custom fields savable 
add_action("save_post", function( $post_id )) {
    if (!is_my_posttype()) return;

    // Checks save status
    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ 'my_custom_nonce' ] ) && wp_verify_nonce( $_POST[ 'my_custom_nonce' ], basename( __FILE__ ) ) ) ? 'true' : 'false';

    // Exits script depending on save status
    if ( $is_autosave || $is_revision || !$is_valid_nonce ) {

    update_post_meta($post_id, "url", $_POST["url"]);
    update_post_meta( ... )
    // update all the other post metas 


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.