Mike Cho Development

A “Departments” Custom Post Type, with Custom Fields

My goal was to describe a (fictional) university’s departments, each in its own page. The elements would be:

  • The title
  • An image
  • Text (called Summary)
  • Campus location
  • Language Requirement? Yes/No
  • Admission Requirements

I was working from a template I’d already built out in Drupal:

My goal: something like this

Create Custom Post Type: Departments

The first step is to create a custom post type called “Departments.” This is a fairly simple process, if you don’t mind doing a bit of copy-paste. Essentially, we’re going to create a new function that will then be hooked into an action.

function create_department_posttype() {
  // a labels array called $labels
  // an argument array called $args
  register_post_type('departments', $args);
//hook the function into the 'init' action
add_action('init', 'create_department_posttype', 0);

Go to the bottom of this post for the completer code.

Once this is declared in functions.php, there will be a new Post Type, similar to Add Post or Add Page, that will say Add Department. We will be working with this new post type.

Add Custom Fields

This section relies on having Advanced Custom Fields installed on your site. If you don’t have this, you should probably go ahead and install it–I’ll wait. It’s just a very old, well-vetted, and useful plugin that you should at least have tried. It allows us to add extra fields to the our custom post that we can then output in a systematic way, making it easier for content-creators to put out a consistent output.

We are going to use this plugin to create the following fields: Location, Second Language Requirement, and Admission Requirements.

Once ACF is installed, go ahead and add a new field group: Custom Fields/Add New.

This will create a Field Group, which I think of as a collection of field inputs for our custom post. So I title it, “Department Fields.”

The next step is to +Add Field, which opens up a form with lots of options for your new field. The ones I’m using are Field Label (“Campus Location”) and Field Type (“Radio Button”). In the Choices dialogue, I add this:

East Campus
West Campus

This will give us these two choices. Note they must be on separate lines.

This completes the first custom field. I’ll go on to make the other two, Second Language Requirement and Admission Requirements. Second Language Requirement will also be a Radio Button, with:


as the choices, and Admission Requirements will simply be a Text Area, to give room to write or paste a paragraph of stuff.

Once this is done, we have need to go to the form area below, Location, and indicate the field group should be shown if Post Type is equal to Department.

That’s it for creating the custom fields.

Note that we could do all this in code, but it’s tedious and there’s hardly any reason to go there at this point. ACF is considered almost part of core in some circles. In any case, if you want to delve deeper into the coding, feel free! But perhaps try it this way first, so you at least understand the larger functionality you’re trying to achieve.

Add Content

I would want to make up some content now, so that there’s something to work with and see when I develop the templates that will output this stuff. Here, I recommend another plugin, called FakerPress.

FakerPress will generate fake content for your posts! It will add paragraphs and images and use lots of tags and stuff. A real time-saver. It won’t put content in the custom fields, sadly, so you’ll need to circle back and do that manually.

Once you have the fake content and fake custom fields content, you’re ready to build out the display templates.

Templates for Departments Custom Post Type

I made two templates for my new post type, one for the single post (representing the full content for a single department), and one that shows a list of teasers or short bits of each as a sort of menu or overview. I am only going to talk about the single one today.

First, we will create a new template called single-departments.php. The WordPress hierarchy means that a file with this name will automatically display the departments post type. For starting content, I use the single.php, the basic template for a blog post.

By the way, I should mention that I used Underscores as a starter theme. You might consider downloading it if you want to follow along.

The single.php is a surprisingly short file. That’s because it calls another template within its body, a template-parts/content. Because I don’t like messing about with multiple files, I just copy that template (from the beginning of the <article> tag to the </article> tag, so I can see everything I’m working on. This may or may not be best practices, but in any case, you can always refactor later.

Now, we have something to work with.


I’m going to give an overview of the changes I made, and I’ll add the code at the bottom.

First, I clear out the comments area that starts with // If comments are open … I don’t need any comments on the Departments page.

Then I remove the call to get_sidebar() at the bottom. None of that stuff is needed for my purposes.

I also clear out the theme_posted_on() and theme_posted_by() lines, as I don’t need the publication date or author.

So much for the clearing out. Now it’s time to add stuff.

Output custom fields

We have three custom fields. You’re going to laugh at how easy they are to display. It’s three lines for three fields.

echo $post->location;
echo $post->second_language_requirement;
echo $post->admission_requirements;

That’s it! Note that these are post meta information fields, but this is really all you have to do. If you try it, you’ll see the stuff outputted on the page, but not in a very nice way.

To match the formatting of my target, I will do the following.

  • Wrap the custom field in a <p> tag
  • Wrap the <p> tag in a <div>
  • Echo out an <h3> tag enclosing the title of the custom field

As an example:

//custom field: Location
		echo '<div class="department-content">';
		echo '<h3 class="department-heading">Location:</h3>';
		echo '<p>' . $post->location . '</p>';
		echo '<div>';

With all three fields done, it outputs something like this:


East Campus

Second Language Requirement:


Admission Requirements:

top 25% in high school graduating class 3.00 GPA in competency courses (4.00 = “A”) ACT: 22 (24 nonresidents) SAT: 1120 (1180 nonresidents) 4 years math 4 years English (non-ESL/ELL courses) 3 years lab sciences (1 year each from biology, chemistry, earth science, integrated sciences or physics) 2 years social sciences (including 1 year American history) 2 years same second language 1 year fine arts or 1 year career and technical education

One problem is that the regular, non-custom-field content is not being outputted in the same way. It’s just plopped on the screen. Since I want it to match my custom fields, I wrap the content in a div.

echo '<div class="department-content">';
		echo '<h3 class="department-heading">Summary:</h3>';
		echo '</div>';

Now the regular content will also have a similar formatting no the custom fields.

Wrapping up: formatting

The rest is css, and pretty simple besides. The only change I needed to make was to add some spacing between the sections, and this was accomplished with a combination of structure (the divs) and a css trick.

.department-paragraph:last-of-type {
	margin-bottom: 40px;

.department-content p:last-of-type {
	margin-bottom: 40px;

This does the trick. It makes sure that there’s extra padding between sections.

Here is the result:

It doesn’t look exactly the same, but close enough that you get the idea. The rest is just css.

/** Departments Post Type */
/* in functions.php */

function create_department_posttype()
	$labels = array(
		'name'                => _x('Departments', 'Post Type General Name', 'drupalpress'),
		'singular_name'       => _x('Department', 'Post Type Singular Name', 'drupalpress'),
		'menu_name'           => __('Departments', 'drupalpress'),
		'parent_item_colon'   => __('Parent department', 'drupalpress'),
		'all_items'           => __('All departments', 'drupalpress'),
		'view_item'           => __('View department', 'drupalpress'),
		'add_new_item'        => __('Add New department', 'drupalpress'),
		'add_new'             => __('Add New', 'drupalpress'),
		'edit_item'           => __('Edit department', 'drupalpress'),
		'update_item'         => __('Update department', 'drupalpress'),
		'search_items'        => __('Search department', 'drupalpress'),
		'not_found'           => __('Not Found', 'drupalpress'),
		'not_found_in_trash'  => __('Not found in Trash', 'drupalpress'),
	//other options
	$args = array(
		'label'               => __('department', 'drupalpress'),
		'description'         => __('University department', 'drupalpress'),
		'labels'              => $labels,
		// Features this CPT supports in Post Editor
		'supports'            => array('title', 'editor', 'excerpt', 'author', 'thumbnail', 'comments', 'revisions', 'custom-fields',),
		'hierarchical'        => false,
		'public'              => true,
		'show_ui'             => true,
		'show_in_menu'        => true,
		'show_in_nav_menus'   => true,
		'show_in_admin_bar'   => true,
		'menu_position'       => 5,
		'menu_icon'           => 'dashicons-networking',
		'can_export'          => true,

		'has_archive'         => true,
		'exclude_from_search' => false,
		'publicly_queryable'  => true,
		'capability_type'     => 'post',
		'show_in_rest' => true,


	register_post_type('departments', $args);

add_action('init', 'create_department_posttype', 0);

 * single-departments.php
 * The template for displaying department posts
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
 * @package Fake_Drupal


	<main id="primary" class="site-main">

		while ( have_posts() ) :
			<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
		//don't show title
		the_title( '<h1 class="entry-title">', '</h1>' ); ?>
	</header><!-- .entry-header -->

	<?php fake_drupal_post_thumbnail(); ?>

	<div class="entry-content">
		echo '<div class="department-content">';
		echo '<h3 class="department-heading">Summary:</h3>';
		echo '</div>';

		//custom field: Location
		echo '<div class="department-content">';
		echo '<h3 class="department-heading">Location:</h3>';
		echo '<p>' . $post->location . '</p>';
		echo '<div>';

		//custom field: Second Language Requirement
		echo '<div class="department-content">';
		echo '<h3 class="department-heading">Second Language Requirement:</h3>';
		echo '<p>'; 
		echo $post->second_language_requirement;
		echo '</p>';
		echo '</div>';

		//custom field: Admission Requirements
		echo '<div class="department-content">';
		echo '<h3 class="department-heading">Admission Requirements:</h3>';
		echo '<p>' . $post->admission_requirements . '</p>';
		echo '</div>';

				'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'fake-drupal' ),
				'after'  => '</div>',
	</div><!-- .entry-content -->

	<?php if ( get_edit_post_link() ) : ?>
		<footer class="entry-footer">
						/* translators: %s: Name of current post. Only visible to screen readers */
						__( 'Edit <span class="screen-reader-text">%s</span>', 'fake-drupal' ),
							'span' => array(
								'class' => array(),
					wp_kses_post( get_the_title() )
				'<span class="edit-link">',
		</footer><!-- .entry-footer -->
	<?php endif; ?>
</article><!-- #post-<?php the_ID(); ?> -->
					'prev_text' => '<span class="nav-subtitle">' . esc_html__( 'Previous:', 'fake-drupal' ) . '</span> <span class="nav-title">%title</span>',
					'next_text' => '<span class="nav-subtitle">' . esc_html__( 'Next:', 'fake-drupal' ) . '</span> <span class="nav-title">%title</span>',


		endwhile; // End of the loop.

	</main><!-- #main -->