HowTo: develop WP template with dynamic background image

I assume you would like to end up with something like this:
enter image description here

Theme way

If you want to put this logic in your template, as you state in your question, there is quite a bit of coding.

You need to:

  • create metabox
  • create content (field and description) in that metabox – that’s called “callback” in the meta_box docs
  • create logic that saves data from the meta box
  • retrieve the data on front end

Create metabox

/**
 * Adds a box to "advanced" part on the Post and Page edit screens.
 * - See the different screens defined in $screens array.
 */
function mybackground_add_meta_box() {

  $screens = array( 'post', 'page' );

  foreach ( $screens as $screen ) {

    // https://codex.wordpress.org/Function_Reference/add_meta_box - add_meta_box(), see for further params
    add_meta_box(
      'background_of_box',                          // HTML 'id' attribute of the edit screen section
      __( 'Box background', 'mytheme_textdomain' ), // Title of the edit screen section, visible to user
      'mybackground_meta_box_callback',             // Function that prints out the HTML for the edit screen section.
      $screen                                       // Which writing screen ('post','page','dashboard','link','attachment','custom_post_type','comment')
    );

  }
}
add_action( 'add_meta_boxes', 'mybackground_add_meta_box' );

Here you can see, that add an action, which is called in add_meta_boxes – that means, when WordPress is building up the meta boxes for writing screen, it’s gonna call our custom mybackground_add_meta_box() function as well.

Create content

/**
 * Prints the box content.
 * 
 * @param WP_Post $post The object for the current post/page.
 */
function mybackground_meta_box_callback( $post, $box ) {

  // Add a nonce field so we can check for it later.
  wp_nonce_field( 'mybackground_meta_box_data', 'mybackground_meta_box_nonce' );

  /*
   * Use get_post_meta() to retrieve an existing value
   * from the database and use the value for the form.
   */
  $value = get_post_meta( $post->ID, '_my_background', true );

  // Enqueues all scripts, styles, settings, and templates necessary to use all media JavaScript APIs.
  wp_enqueue_media();

  ?>
  <p>
      <label for="meta-image"><?php _e( 'Background pick', 'mytheme_textdomain' )?></label>
      <?php if ( isset ( $value ) ) : ?>
        <img src="https://wordpress.stackexchange.com/questions/193832/<?php echo $value ?>" alt="Preview" width="50" height="50" id="background-image-preview">
      <?php endif; ?>
      <input type="hidden" name="background-image" id="background-image" value="<?php echo ( isset ( $value ) ? $value : '' ); ?>" />
      <input type="button" id="background-image-button" class="button" value="<?php _e( 'Choose or Upload background', 'mytheme_textdomain' )?>" />
  </p>
  <script type="text/javascript">
    /*
     * Attaches the image uploader to the input field
     */
    jQuery(document).ready(function($){

        // Instantiates the variable that holds the media library frame.
        var background_image_frame;

        // Runs when the image button is clicked.
        $('#background-image-button').click(function(e){

            // Prevents the default action from occuring.
            e.preventDefault();

            // If the frame already exists, re-open it.
            if ( background_image_frame ) {
                background_image_frame.open();
                return;
            }

            // Sets up the media library frame
            background_image_frame = wp.media.frames.background_image_frame = wp.media({
                title: '<?php _e( 'Background selection', 'mytheme_textdomain' )?>',
                button: { text: '<?php _e( 'Pick background image', 'mytheme_textdomain' )?>' },
                library: { type: 'image' }
            });

            // Runs when an image is selected.
            background_image_frame.on('select', function(){

                // Grabs the attachment selection and creates a JSON representation of the model.
                var media_attachment = background_image_frame.state().get('selection').first().toJSON();

                // Sends the attachment URL to our custom image input field.
                $('#background-image').val(media_attachment.url);

                // Sets the preview image to the same value
                $('#background-image-preview').attr('src', media_attachment.url);
            });

            // Opens the media library frame.
            background_image_frame.open();
        });
    });
  </script>
  <?php
}

Okay, here it comes with quite a bit of code, because, there is also javascript logic used for the media picker. This particular WP API is documented on Codex, but for this specific example i’ve used this guide, where it’s described in very nice way. Furthermore, you might find that loading all this javascript logic plain in that function is bad approach and nicer would be to put it aside as a standalone file. Notice, that this does not have to be hooked, because it is defined as a callback of add_meta_box_function

Saving the data

/**
 * When the post is saved, saves our custom data.
 *
 * @param int $post_id The ID of the post being saved.
 */
function mybackground_meta_box_box_data( $post_id, $post ) {

  // SECURITY AND AUTOSAVE

  /*
   * We need to verify this came from our screen and with proper authorization,
   * because the save_post action can be triggered at other times.
   */

  // Check if our nonce is set.
  if ( ! isset( $_POST['mybackground_meta_box_nonce'] ) ) {
    return;
  }

  // Verify that the nonce is valid.
  if ( ! wp_verify_nonce( $_POST['mybackground_meta_box_nonce'], 'mybackground_meta_box_data' ) ) {
    return;
  }

  // If this is an autosave, our form has not been submitted, so we don't want to do anything.
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
    return;
  }

  // Check the user's permissions.
  if ( isset( $_POST['post_type'] ) && 'page' == $_POST['post_type'] ) {

    if ( ! current_user_can( 'edit_page', $post_id ) ) {
      return;
    }

  } else {

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

  /* OK, it's safe for us to save the data now. */

  // Make sure that it is set.
  if ( ! isset( $_POST['background-image'] ) ) {
    return;
  }

  // HERE STARTS THE ACTUAL FUNCTIONALITY

  // Sanitize user input.
  $my_data = sanitize_text_field( $_POST['background-image'] );

  // Update the meta field in the database.
  update_post_meta( $post_id, '_my_background', $my_data );
}
add_action( 'save_post', 'mybackground_meta_box_box_data' );

Here’s another hook, save_post and our custom logic to it. Basically, it’s 1:1 what’s found on Codex to the add_meta_box() function just swapped the POST field names.

Retrieving data

Finally when you need to grab your data on the frontend, you can go to template and use something like:

$background_image_url = get_post_meta( get_the_ID(), '_my_background', true );

get_post_meta() uses your current post ID, key of the field you want to retrieve (optional) and whatever you want only one or all meta (optional – not set returns all custom fields).

This said, i’d actually go the plugin way.

Plugin way

For cases like this, i’d pick Advanced Custom Fields plugin. Which can basically do the same, what is coded above.

Setting up the meta box is done via user interface.

Retrieving data

For retrieving data you use functions get_field() or the_field() – whatever you want to return the value or print the value right away (same naming convention as in WP all around).

get_field('your_field_name');

Sources and further reading

http://themefoundation.com/wordpress-meta-boxes-guide/
https://codex.wordpress.org/Function_Reference/add_meta_box
http://www.smashingmagazine.com/2011/10/04/create-custom-post-meta-boxes-wordpress/