How To Make Guides (Collections of Content) in WordPress

A blog post can be anything you want. You could easily write one that links up a bunch of articles on your site. Titles, summaries, links... all hand-crafted HTML. A "guide", as it were. It will likely be appreciated by your readers, I find, especially when you're linking up old evergreen content that is still relevant and useful today.

But let's say you want to programmatically create these "guides". That might make them faster to create, easier to maintain, and give you a nice level of control over them. Let's look at a way to do that.

Guides on CSS-Tricks

I'm writing about this because guides are something we've just started to do here on CSS-Tricks. For example, I wanted to make a guide of our content that is well suited for folks just starting out, so I made Our Guide To Just Starting Out with CSS & HTML.

That wasn't built by hand, it was built by programmatically attaching a variety of content to a Custom Post Type we created just for Guides.

Programmatically Attaching Posts to Posts

You know how you can put images into blog posts? But in WordPress, there is also a concept of a featured image, which is one specific programmatically attached image for that post.

That image is programatically attached to this Post.

Enabling that feature in WordPress is like:

add_theme_support('post-thumbnails', array('post', 'page', 'whatever'));

But we're talking associating Posts with Posts not Images to Posts. There is no built-in WordPress way of doing that, so we'll reach for plugins.

CMB2 and Friends

CMB2 (i.e. the second version of "Custom Meta Boxes") is a free, open source plugin for adding better UI and functionality around custom fields. If you're familiar with Advanced Custom Fields, it's a bit like that, only I guess entirely free and a bit more modular.

With that installed, now you can install (I guess we'll call them sub-plugins?) that make CMB2 do stuff. The one we're after is CMB2 Attached Posts Field which has the explicit job of attaching Posts to Posts (or really, post type to any post type).

It gives you this two-column UI on post types you activate it for:

Move anything from the left to right, and it's now programatically attached. This is exactly what we're after. Now we can hand select, and hand order, any type of post to attach to any other.

Configuring Things

Before you get to the UI you can see above, you not only need to install and activate those two plugins, but also tell CMB2 to create the custom meta boxes and apply them to the types of posts you want.

In our case, our Guides are a custom post type. That's easy enough to enable:

register_post_type( 'guides',
    'labels'        => array(
      'name'          => __( 'Guides' ),
      'singular_name' => __( 'Guide' ),
      'add_new'       => __( 'Add Guide' ),
      'add_new_item'  => __( 'Add New Guide' ),
      'edit_item'     => __( 'Edit Guide' ),
    'public'      => true,
    'has_archive' => true,
    'rewrite'     => array( 'slug' => 'guides' ),
    'supports'    => array( 'title', 'editor', 'thumbnail', 'excerpt' )

Then we apply this new custom meta box only to that custom post type (so we don't have to see it everywhere):

$cmb = new_cmb2_box( array(
  'id'            => 'guide_metabox',
  'title'         => __( 'The Guide Metabox', 'cmb2' ),
  'object_types'  => array( 'guides', ), // Post type
  'context'       => 'normal',
  'priority'      => 'high',
  'show_names'    => true, // Show field names on the left
  // 'cmb_styles' => false, // false to disable the CMB stylesheet
  // 'closed'     => true, // Keep the metabox closed by default
) );

// Regular text field
$cmb->add_field( array(
  'name'       => __( 'Things for the Guide', 'cmb2' ),
  'id'         => 'attached_cmb2_attached_posts',
  'type'       => 'custom_attached_posts',
  'show_on_cb' => 'cmb2_hide_if_no_cats',
  'options' => array(
    'show_thumbnails' => true, // Show thumbnails on the left
    'filter_boxes'    => true, // Show a text box for filtering the results
    'query_args'      => array(
      // 'posts_per_page' => 2,
      'post_type' => array('post', 'page')
    ), // override the get_posts args
) );

We nestle all this code nicely into a functionality plugin, rather than a `functions.php` file, so that changing themes has no bearing on this content.

A Template for Guides

Now that a custom post type exists for our guides, adding a file called `single-guides.php` into our active theme is enough to make that the file that renders for like `/guide/example/`.

In that file, we do whatever normal template-y stuff we'd do on any other template file (e.g. `page.php`, but also loop through all these posts we've attached!


  $attached = get_post_meta(get_the_ID(), 'attached_cmb2_attached_posts', true);

  foreach ($attached as $attached_post) {
    $post = get_post($attached_post); ?>

  <?php include("parts/article-card.php"); ?>

<?php } ?>

All in all, not that much to it!

It feels great to have some kind of mechanism for surfacing evergreen content like this. That can be quite a challenge for sites with a huge amount of content!

High five to Rebekah Monson, whom I ripped this idea off of, who uses this to build guides on The New Tropic, like these neighborhood guides.