Grow your CSS skills. Land your dream job.

Creating a “Meet The Team” Page in WordPress

Published by Guest Author

The following is a guest post by Kevin Leary. I was pretty stoked to get this guest post submission from Kevin because it's my favorite kind of tutorial: practical and detailed.

Almost every custom theme I develop with WordPress requires a Management Team or Meet the Team page. If I had to guess, I'd say that I've built just around 50 different setups. It occurred to me that their must be many other WordPress developers out there creating similar systems as well. For this reason, I'll share the approach I typically use to build and manage a "Meet The Team" page in WordPress.

For those of you that just want the final product, check out the PHP Class on Github or see an example.

See Example   Repo on Github

Creating and managing a team page like this in WordPress involves the following combination of tools:

  • Custom post type (e.g. team)
  • Custom taxonomy for filtering (e.g. department)
  • A meta box UI for managing custom fields (e.g. position, email, phone, and social media links)

Using these tools, let's walk through the process of creating a Meet Our Team template for a custom WordPress theme.

First & Foremost

Before we begin, I should clarify a few things. In the Github example for this tutorial I'm using an object oriented approach, and have stored my code in a separate .php file that I typically include in a theme's functions.php file.

For the sake of clarity, I'll be describing the process step by step procedurally and will refer to setting this up inside of your functions.php instead.

Create Post Type & Taxonomy

The first step is to register a new post type (e.g. team). You can also register a taxonomy (e.g. department) if filtering or categorization is required.

This post type will add a new Team Profiles menu to the WordPress admin, separating all team posts from Posts and Pages for easier content management.

The taxonomy will add a custom category to the team posts, allowing you to filter or categorize your team. I find that this is appropriate when you have a team that is more than ten people. It's often handy for filtering staff members by office location or department.

Post Type

/**
 * Register `team` post type
 */
function team_post_type() {
   
   // Labels
	$labels = array(
		'name' => _x("Team", "post type general name"),
		'singular_name' => _x("Team", "post type singular name"),
		'menu_name' => 'Team Profiles',
		'add_new' => _x("Add New", "team item"),
		'add_new_item' => __("Add New Profile"),
		'edit_item' => __("Edit Profile"),
		'new_item' => __("New Profile"),
		'view_item' => __("View Profile"),
		'search_items' => __("Search Profiles"),
		'not_found' =>  __("No Profiles Found"),
		'not_found_in_trash' => __("No Profiles Found in Trash"),
		'parent_item_colon' => ''
	);
	
	// Register post type
	register_post_type('team' , array(
		'labels' => $labels,
		'public' => true,
		'has_archive' => false,
		'menu_icon' => get_stylesheet_directory_uri() . '/lib/TeamProfiles/team-icon.png',
		'rewrite' => false,
		'supports' => array('title', 'editor', 'thumbnail')
	) );
}
add_action( 'init', 'team_post_type', 0 );

Optional Taxonomy

/**
 * Register `department` taxonomy
 */
function team_taxonomy() {
	
	// Labels
	$singular = 'Department';
	$plural = 'Departments';
	$labels = array(
		'name' => _x( $plural, "taxonomy general name"),
		'singular_name' => _x( $singular, "taxonomy singular name"),
		'search_items' =>  __("Search $singular"),
		'all_items' => __("All $singular"),
		'parent_item' => __("Parent $singular"),
		'parent_item_colon' => __("Parent $singular:"),
		'edit_item' => __("Edit $singular"),
		'update_item' => __("Update $singular"),
		'add_new_item' => __("Add New $singular"),
		'new_item_name' => __("New $singular Name"),
	);

	// Register and attach to 'team' post type
	register_taxonomy( strtolower($singular), 'team', array(
		'public' => true,
		'show_ui' => true,
		'show_in_nav_menus' => true,
		'hierarchical' => true,
		'query_var' => true,
		'rewrite' => false,
		'labels' => $labels
	) );
}
add_action( 'init', 'team_taxonomy', 0 );
In this example, we're not actually using the department taxonomy for anything. I've included it in the tutorial because it's useful to understand that it can be used to filter team members.

Meta Box for Custom Fields

Now that we have a new *Team Profiles* menu in WordPress, we need to customize the data that we store with each team post. In my experience most team profiles have the following fields:

  • Position
  • Email
  • Phone
  • Twitter
  • LinkedIn

To manage this content, I like to customize the Add New and Edit UI for the team post type, allowing site admins and authors to intuitively update this information without training.

My tool of choice for creating custom field meta box UIs is currently the Advanced Custom Fields (ACF) plugin.

To create this meta box you'll need to install the ACF plugin, creating your fields under the Custom Fields admin menu. Below is a look at the fields and settings I've used for this tutorial.

If you're lazy like me, you can import my XML export file to automate the field creation process. Here's how:

  1. Download my 'Team Details' field group export: acf-export-team-details.xml.zip
  2. Navigate to Tools » Import and select WordPress
  3. Install WP import plugin if prompted
  4. Upload and import the .xml file
  5. Select your user and ignore Import Attachments
  6. That's it!

The ACF plugin stores its data inside of a custom post type, so the standard WordPress XML import tool can be used. Quite a smart move by the plugin author, Elliot Condon.

Extras

In my PHP Class I've added an admin notice that will prompt you to install the ACF plugin if you haven't already. This provides a nice reminder that you need it to get the team post type working properly.

Custom Template

Now that we have our Team management system setup, we'll need to output our team profiles somewhere on the site. To do this, I usually create a custom theme template (e.g. template-team.php) that alters the view for a specific WordPress page. View the docs on WordPress.org for details about custom templates.

Loop to Display Team Posts

To output our team posts inside of our custom template, we'll use the following code.

<?php
/**
 * Template Name: Team
 */

the_post();

// Get 'team' posts
$team_posts = get_posts( array(
	'post_type' => 'team',
	'posts_per_page' => -1, // Unlimited posts
	'orderby' => 'title', // Order alphabetically by name
) );

if ( $team_posts ):
?>
<section class="row profiles">
	<div class="intro">
		<h2>Meet The Team</h2>
		<p class="lead">&ldquo;Individuals can and do make a difference, but it takes a team<br>to really mess things up.&rdquo;</p>
	</div>
	
	<?php 
	foreach ( $team_posts as $post ): 
	setup_postdata($post);
	
	// Resize and CDNize thumbnails using Automattic Photon service
	$thumb_src = null;
	if ( has_post_thumbnail($post->ID) ) {
		$src = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'team-thumb' );
		$thumb_src = $src[0];
	}
	?>
	<article class="col-sm-6 profile">
		<div class="profile-header">
			<?php if ( $thumb_src ): ?>
			<img src="<?php echo $thumb_src; ?>" alt="<?php the_title(); ?>, <?php the_field('team_position'); ?>" class="img-circle">
			<?php endif; ?>
		</div>
		
		<div class="profile-content">
			<h3><?php the_title(); ?></h3>
			<p class="lead position"><?php the_field('team_position'); ?></p>
			<?php the_content(); ?>
		</div>
		
		<div class="profile-footer">
			<a href="tel:<?php the_field('team_phone'); ?>"><i class="icon-mobile-phone"></i></a>
			<a href="mailto:<?php echo antispambot( get_field('team_email') ); ?>"><i class="icon-envelope"></i></a>
			<?php if ( $twitter = get_field('team_twitter') ): ?>
			<a href="<?php echo $twitter; ?>"><i class="icon-twitter"></i></a>
			<?php endif; ?>
			<?php if ( $linkedin = get_field('team_linkedin') ): ?>
			<a href="<?php echo $linkedin; ?>"><i class="icon-linkedin"></i></a>
			<?php endif; ?>
		</div>
	</article><!-- /.profile -->
	<?php endforeach; ?>
</section><!-- /.row -->
<?php endif; ?>

To get a loop of our team posts, I've used the get_posts function. Its simple, easy to use and efficient. I've used the following arguments to target and organize the query results:

  • 'post_type' => 'team' will only query team posts
  • 'posts_per_page' => 50 will return all of our team profiles, assuming you have less than 50. If you plan to have more adjust this accordingly.
  • 'orderby' => 'title' will order the results by name
  • 'order' =>; 'ASC' will start the order alphabetically

Once we have our query object, we loop through each team post, outputting data and content into our HTML structure.

The get_field and the_field functions are built-in to the ACF plugin. These are probably the two most commons functions that you will find yourself using when working with it. They output the value of a given custom field.

Once we have the loop complete, we can create a new Page in WordPress, selecting Team from the custom templates dropdown. When you view this page, you should see a list of your team's profiles.

Notes on Performance

Without any caching, this bit of code adds a whopping 26 queries to the page. If you have a high volume site, it's a requirement that you leverage the Transients API to cache the output of an intensive custom query like this. I've included a static display() method in my PHP Class that handles the transient caching.

if ( $team_profiles = TeamProfiles::display() )
  echo $team_profiles;

The display() method uses output buffering and transient caching to store and cache the HTML generated by our team loop.

Using this approach instead of our previous loop reduces the number of queries to 1, saving us 25 hits to the database. This also reduces the initial page load by 400-500ms. Not bad!

Styling The Template with CSS

Now that we have our team page management system in place and our HTML structure output, we need to add some style to our new template.

Conditionally Load in CSS

To include a stylesheet for our custom template only (template-team.php), we can use the following conditional check.

/**
 * Load CSS for template-team.php
 */
function team_styles() {
	if ( is_page_template('template-team.php') )
   	wp_enqueue_style( 'team-template', get_stylesheet_directory_uri() . '/assets/css/team.css' );
}
add_action( 'wp_enqueue_scripts', 'team_styles', 101 );

This will enqueue a CSS file (/assets/css/team.css) when template-team.php is in use. Using this method helps to keep your core stylesheet lean.

Example Styles

Here are the styles I've used for the example that accompanies this tutorial:

/* ==============================================
   Team profiles
   ============================================== */
.profiles {
  margin-bottom: -20px;
}
.intro {
  padding-left: 140px;
}
.intro h2 {
  margin: 0 0 7px;
}
.intro .lead {
  line-height: 120%;
  font-size: 1.1em;
  font-style: italic;
  margin: 0 0 35px;
}
.profile {
  position: relative;
  margin: 0 0 20px;
}
.profile:nth-child(even) {
  clear: left;
}
.profile-header {
  position: absolute;
  top: 0;
}
.profile-header img {
  float: left;
}
.profile-content {
  font-size: 14px;
  padding: 27px 20px 0 0;
  line-height: 1.4em;
  margin: 0 0 0 125px;
}
.profile-content h3 {
  margin: 0;
}
.profile-content .lead {
  font-size: 1.3em;
  line-height: 100%;
  font-style: italic;
  margin: 3px 0 20px;
}
.profile-content:before {
  content: '';
  width: 36px;
  height: 3px;
  background: #dededc;
  position: absolute;
  top: 0;
}
.profile-content p {
  margin: 0 0 10px;
}
.profile-footer {
  position: absolute;
  top: 121px;
  width: 100px;
  text-align: center;
}
.profile-footer a {
  line-height: 18px;
  margin: 0 3px;
  display: inline-block;
}
.profile-footer a:hover i {
  color: #595959;
}
.profile-footer a:active i {
  color: #000;
}
.profile-footer i {
  font-size: 18px;
  position: relative;
}
.profile-footer i.icon-envelope {
  font-size: 16px;
  top: -1px;
}
.profile-footer i.icon-linkedin {
  font-size: 16px;
  top: -1px;
}

In my project workflow I use LESS to pre-process my CSS. If you use would like to use LESS in your project, below is the CSS in LESS format.

/* ==================================================
   Team profiles
   ================================================== */
  
// Mixins
.border-radius(@radius) {
	-webkit-border-radius: @radius;
	-moz-border-radius: @radius;
	border-radius: @radius;
}

// Global
.profiles {
	margin-bottom: -20px; // Offset adjustment
}
.intro {
	padding-left: 140px;
	h2 {
		margin: 0 0 7px;
	}
	.lead {
		line-height: 120%;
		font-size: 1.1em;
		font-style: italic;
		margin: 0 0 35px;
	}
}
.profile {
	position: relative;
	margin: 0 0 20px;
	&:nth-child(even) {
		clear: left;
	}
}

// Header
.profile-header {
	position: absolute;
	top: 0;
	img {
		float: left;
	}
}

// Content
.profile-content {
	font-size: 14px;
	padding: 27px 20px 0 0;
	line-height: 1.4em;
	margin: 0 0 0 125px;
	h3 {
		margin: 0;
	}
	.lead {
		font-size: 1.3em;
		line-height: 100%;
		font-style: italic;
		margin: 3px 0 20px;
	}
	// Top separator
	&:before {
		content: '';
		width: 36px;
		height: 3px;
		background: #dededc;
		position: absolute;
		top: 0;
	}
	p {
		margin: 0 0 10px;
	}
}

// Footer
.profile-footer {
	position: absolute;
	top: 121px;
	width: 100px;
	text-align: center;
	a {
		line-height: 18px;
		margin: 0 3px;
		display: inline-block;
	}
	a:hover i { color: #595959; }
	a:active i { color: #000; }
	i {
		font-size: 18px;
		position: relative;
	}
	i.icon-envelope {
		font-size: 16px;
		top: -1px;
	}
	i.icon-linkedin {
		font-size: 16px;
		top: -1px;
	}
}

Awesome, You Made It!

Thanks for reading, I hope this tutorial gives you a solid understanding of how to create a team bio/meet the team/team profile management system in WordPress. If you understand the underlying concepts explained in this article, you should now have a valuable new approach to managing custom content in WordPress.

Custom post types, taxonomies, and meta box managed custom fields provide a powerful approach to managing many complex WordPress CMS scenarios.

Comments

  1. Awesome tutorial, quick question why did you use get_posts instead of using a custom WP_Query? The later seems simpler

    • I opted for get_posts() over WP_Query because I actually believe it’s the simpler of the two. It’s totally up to you, WP_Query can work well also. I don’t see a significant difference in one vs. the other.

    • Permalink to comment#

      Awesome tut! ACF rules forever. Sometimes when I feel the laziest I even use custom post types UI. It’s like you don’t have to code any more. Combine it with bootstrap. You’re like sleeping while creating your wordpress site. he he!

    • MacK
      Permalink to comment#

      Always always always always use WP_Query over get_posts and if you want to go for get_posts use wp_reset_query. I’ll repeat this infinite times with my coworkers, it’ll save a lot of hassle later

  2. Connie
    Permalink to comment#

    only male team members?`

    Shame on you!

    • There are four fictional people on an example page to demonstrate some code. I’m going to bury this as I don’t think this is the place to start a conversation about gender.

  3. Beelal
    Permalink to comment#

    Thanks for this detailed tutorial! I have been searching custom post types tuts since past 3 days and this one is very good in terms of detail and codes. I have one question though, I wonder if I can use this method to display a detailed list of artists/designers on a page, that would link to their profiles on a separate page. In terms of load/queries would this method be feasible?

    • Indeed! You would just need to provide a link to the proper url created by your custom post type and create a template for the user profile page.

    • I’m glad you enjoyed the article Beelal,

      This is very easy to do. You may notice that each Team post you create doesn’t have a rewritten slug. Instead they have something like this: http://www.kevinleary.net/?team=jonathan-ive.

      To create individual pages for each team post set the rewrite parameter to true in the register_post_type() function. Then your URL’s will look something like this: http://www.kevinleary.net/team/jonathan-ive/.

    • Beelal
      Permalink to comment#

      Thanks Kevin & Adam, Im trying this right now! :)

    • The only thing you’ll need to do to make the profiles work themselves is to create the single template to go along with it when they click the permalink. Let’s say you had a custom post type of “artist”. You’d just make a permalink on the “team” page that would go to the individual post and you would create a new file called “single-artist.php” and then add your Advanced Custom Fields to it and you’re all set.

    • Micah

      For those following what Kevin and Josh said but are still stuck, make sure you go to Settings > Permalinks and resave this page. You don’t even have to change your setting for this page, just hit or tap the save button and you should be golden. I spent an hour or two wondering why the individual pages weren’t showing up! Hope that helps someone.

  4. Quick question about your use of the Transients API.

    You use set_transient($transient_label, $html); to set the data with no expiry. If you were to add a new user to the team, would it not display? Not quite clear on how that works.

    • Good catch Adam,

      I’ve updated the Github repo to include set_transient($transient_label, $html, DAY_IN_SECONDS); instead. This was definately an unintended oversight on my part.

    • Paul
      Permalink to comment#

      You could hook into save_post to refresh the cache also instead of using a time limit.

    • Good idea Paul, I actually like this idea better. Why refresh a cache if nothing has changed., right?

  5. Permalink to comment#

    i love this

  6. tomek
    Permalink to comment#

    Great tutorial! For the ones of you that doesn’t want to create custom post types by hand there’s nice plugin called Types, which does it for you (but it has to be installed even after the creation, so commercial realisations may want to pass on this).

    Also – and Javier already asked this – why get_posts instead of WP_Query? Is there any real improvement on the performance by using it?

    • See Javier’s answer at the top of the comments list. I don’t see a significant difference in one vs. the other. get_posts() is a simpler interface for WP_Query wrapped in a single function, it uses WP_Query itself.

  7. Permalink to comment#

    Awesome tutorial, thanks! I’ve used the same combo of custom post types and ACF very often recently in client projects, it rocks! I learned a few details from your examples, loving especially the little caching hint!

  8. Niek
    Permalink to comment#

    Great post, thanks especially for the performance snippet! I often find I need to sort the results for different functions within an organisation, ie: director, administrative staff, assistants etc. where they have to be displayed hierarchically, not alphabetically. I’ve been creating separate loops in these cases, looping through one custom taxonomy at a time, but this seems overkill, and bad for performance, within academic organisations for instance I might have 10 of these loops. I’m probably missing a very obvious solution, but any advice on how to handle a case like that?

    • Hi Niek,

      The most efficient way would probably involve using $wpdb to craft your own query. Second to that you I’d probably query all of the posts in your post type with get_posts(), then iterate through each one, getting the terms attached to it with something like this:

      $post_terms = wp_get_post_terms($team_post->ID, 'departments', array("fields" => "names") );

      From there, I’d creating an array for each taxonomy term, then store each posts in their respective array(s). After all that you should end up with an array for each term, maybe something like $directors, $administrators, $staff, $assistants, etc. At this point, you should have enough to generate the output.

      I hope this helps, be sure to leverage transients. When taxonomy terms come into play things can quickly become resource/query intensive.

    • Niek
      Permalink to comment#

      Kevin, thanks a lot, great advice, really appreciate it!

  9. Ah! I’ve been using ACF for over a year now, and it’s changed my life!

    Clever use of custom post types for the team members. I probably would have used the Repeater Field on a regular page for it, but if you don’t want to pay for that AFC add-on, that’s a pretty awesome free way of accomplishing pretty much the same thing.

    You mention performance and how many queries this method produces. Have you ever compared performance of the custom post type vs. repeater field?

    • ACF is a great time saver and it helps with development a lot. The repeater field is a great plugin which I also use in our clients’ projects.

      The performance difference between ACF’s repeater field and custom post types depends on how many queries you make to the Database, how much information you select from it and how much is returned. It also depends on the way you access the information – if you loop trough all of it searching for something or if you target a specific post.

      For small to medium sized projects there is no significant performance difference in using one or the other. It all comes down to what “feels” more intuitive in a project.

    • Good point Anna,

      I generally prefer post types vs. repeaters for a few reasons. Storing all that information in custom fields seems inefficient, especially if the team grows over time.

      I’ve come across numerous circumstances where it’s handy to setup a post relationship for team members. Maybe a group of team members were all contributors to an Ebook, or part of a company accomplishment for example. When the data is in post types I can use an ACF relationship to handle situation like this.

      It’s also easier to create individual team member pages with post types, a common scenario that often comes up as well. I have to admin that those repeater fields are handy though.

  10. Erin Bell
    Permalink to comment#

    Nice tutorial. I notice a lot of designers using ACF or other plugins for this kind of thing – why not hand-code the custom metaboxes?

    Is there a downside to hand-coding and/or a benefit to using a plugin to create custom fields (other than the obvious issue of time)?

    It seems to me that relying on a plugin for a core functionality is a maintenance risk but maybe there’s something I’m not considering.

    • Is there a downside to hand-coding and/or a benefit to using a plugin to create custom fields (other than the obvious issue of time)?

      Time is the exact reason. I could spend a considerable amount of time hand-coding them – or I could spend 2 minutes to build some pretty advanced things instead. The options that come with ACF (such as the date/time picker and repeater) are enough for me to trust it.

      It seems to me that relying on a plugin for a core functionality is a maintenance risk but maybe there’s something I’m not considering.

      I get this, but ACF seems to be a tool that gets updated frequently and has enough of a user base that makes it a reliable choice.

    • Marcus
      Permalink to comment#

      I was going to ask the same. Anyone?

    • Erin Bell
      Permalink to comment#

      The options that come with ACF (such as the date/time picker and repeater)…

      True. Those features are no fun to create.

    • I hear where you’re coming from, and to be honest I’m very cautious about this Erin. In the past I’ve had epic failures with using plugins for functionality like this, such as ‘More Fields’ or ‘Custom Field Templates’.

      For a while the WP Alchemy MetaBox Class was my weapon of choice for meta box creation, and I still use it occasionally for custom scenarios.

      When I started using ACF I was weary about switching back to using a plugin, so I thoroughly audited the underlying code. My consensus is that it’s very well developed, stable, and leverages WordPress’ best practices and API’s. I’m comfortable using it now, and have been for about 6 months.

      In fact, I feel that it’s more efficient from a maintenance stand point to use a centralized tool. If you have 20 sites with meta boxes, and WordPress makes an update that effects them all, you can effectively patch or fix those 20 sites in one place.

      My only concern is that the plugin author may eventually burn out. Elliot Condon has created an incredible tool that so many people use for free. I imagine that the more people use it, the more frequent and demanding his support requests become.

    • Permalink to comment#

      My only concern is that the plugin author may eventually burn out. Elliot Condon has created an incredible tool that so many people use for free. I imagine that the more people use it, the more frequent and demanding his support requests become.

      Elliot has been excitedly tweeting about going full-time in developing ACF. He also has great plans for Version 5 with free and pro model, so I don’t think development will be stopping any time soon :-)

      ACF is my tool of choice as well and I would love if WordPress would just hire Elliot and integrate ACF into vanilla WordPress.

    • Permalink to comment#

      I’d be worried about the plugin breaking with a WordPress update, all of the sudden every Meet the Team page you’ve ever made isn’t going to work. A client isn’t going to care that it’s just a temporary situation.

      Seems to me that a smarter way to do it when you are working with simple layouts would be to use the plugin in the admin for the users, but use standard WordPress functions (or your own) to retrieve the data and display it in your templates. That way, if the plugin is somehow broken by a WordPress update, you aren’t left with a bunch of broken or empty pages while you wait for the plugin to be updated. At worst, clients would be temporarily unable to update individual team members.

      Great tutorial. Custom Post Types are useful in so many situations. I wish they weren’t so hidden in the WordPress core.

  11. This is one of the best posts I’ve ever seen.. Detailed and really easy to understand.. Thanks to Kevin Leary to share it..!

  12. Permalink to comment#

    Really useful tutorial.
    Also, is there a way to associate a team member with a WordPress user? So each team member could have a list of posts underneath his description, etc.

    Thanks,

    • I suppose you could have a field that you could enter in the user name (“perhaps dynamically populate it?”)… then in your template, run a WP_Query getting posts by authors that match the previously mentioned field?

      I believe I remember seeing some plugins that would enhance the author profiles by adding additional fields – that may be worth looking into as well…

    • ACF Relationship fields are perfect for this.

      The Relationship field creates a very attractive version of the post object field. With a Relationship field, you can select from pages + posts + custom post types. This field is useful for advanced linking to another page / post object.

      Add a “Related Articles” field to your team posts, or add a “Related Team Members” field to your posts.

    • Philip Benton
      Permalink to comment#

      What would be the best way to display latest posts from each user instead of having to manually select them using the ACF Relationship Fields?

      If all team members were WordPress users could you not create the custom fields within the User Profile screen using ACF?

  13. Dmitry
    Permalink to comment#

    Thank you, for the great tutorial.
    Tell me please, is there any way to unset single pages for each team member? In me project I need only one page where the whole team is shown, without sepearate pages like http://site.com/team/barack-obama/.

  14. Chris, any reason for not using a repeater for this in ACF? Could create a “profile” repeater with these fields and then just do a while loop on the field itself without a need for additional taxonomies.

    Even better could make a flex field for it so they could be included anywhere.

    • Repeater fields is a paid plugin, as Anna Funk mentioned above.
      This is a free way to do stuff.

    • andyunleash
      Permalink to comment#

      Ah very true, it’s only a few bucks though and totally worth it. ACF is the only reason I started using WordPress for client projects, without it, WordPress was simply not good enough as a CMS.

    • Repeater fields work, but post types are often better choice for many reasons. See my response to Anna Funk above.

  15. Jermaine
    Permalink to comment#

    Great Article, you should do this more often.

  16. Làm Web
    Permalink to comment#

    I found what I was looking for. It’s great!

  17. Permalink to comment#

    I’ve been looking for an alternative meta box plug-in for a while and i’m blown away by ACF. Thanks for introducing it!

  18. Permalink to comment#

    GREAT ARTICLE ! Right on time :D Thanks a lot!

  19. Permalink to comment#

    Great post! I want to adapt this for a corporate company’s intranet employee directory which runs on WordPress.

    • I’ve done similar projects, it works great for that. If it’s a large database of people, a relevant search can be a great addition. I’d suggest checking out SwiftType

  20. Awesome post! The perfect example for those who need to understand how to use custom post types, taxonomies and custom fields.

  21. Aaron Stanley
    Permalink to comment#

    I happen to be working on a meet the team page today using ACF and a custom post type! I ran into a hiccup. I’m trying to automate the process a little by having the about page like the one above link to each members’ generated bio page. I’m sending through the permalink, but then I have no way of building that page with custom fields. Is this even possible to do?
    Thanks so much!

  22. Permalink to comment#

    Awesome article. Gonna save me a lot of fiddling and experimentation on something I’m currently working on.

  23. Jon
    Permalink to comment#

    Thanks Kevin,
    I wish I had seen this a few months ago but oh well, I like your solution better!
    Certainly going to check out both the ACF plugin and the repeater ideas nonetheless!
    Cheers,

    Jon

    PS. the href on the custom templates has an extra “s” on the beginning, you might want to update that:

    shttp://codex.wordpress.org/Theme_Development#Custom_Page_Templates

  24. Hello Kevin,

    First of all, awesome tutorial.

    I have some questions about your route, just wanted to know if perhaps I should “change my tactics” so to speak.

    1) You use a template for a custom post. Isn’t it better to ride the page-hiearchy of WP and create an “archive-team.php” page on the theme and insert the code there? I am just not very keen on clients changing this setting per se (from normal template to team template).

    2) You mention a plug in to cache stuff due to the custom metafield plug in. Is it better or not to use our own metabox or not?

    • I see the second question has been asked already so good to know. First one still stands though :)

    • You could also use a post type archive instead, for the purposes of this demo a custom template works well given that all of the profiles are displayed on a single page.

  25. Tobias
    Permalink to comment#

    Hi Kevin, thanks for your post – but there is one pressing question for me:
    Why did you go for a custom-post type to represent a team member when in fact wordpress already has a data-object to represent people: its user system.
    It is quite easy to extend the user profile for one user with additional fields and to read those fields in a view.
    IMO that leads to a much cleaner data-structure … with the added benefit that the author-pages already have all the information they need to represent the details-view of one author.

    Thanks for your reply.
    Tobias

    • Philip
      Permalink to comment#

      Tobias, this is something I queried in a comment above. To me adding fields to the user system seems the logical way.

      The only occasion I can see where this may not be the best solution is if team members do not require access to the WordPress system.

    • That’s a valid point Tobias. I think a custom post type works well for corporate/business sites where team members aren’t authors in any way. In my experience most “Meet The Team” pages and profiles function as static content. In this circumstance it seems overkill to manage a user account for each person if they aren’t authoring content or logging into the system.

      That said, there are definitely notable advantages to the method you’ve described. Providing team members with the ability to log in and update their profile could be an enormous time saver for a large directory of profiles.

      For other readers out there, if your team members are writing content I’d consider Tobias’ approach.

    • Guys,

      First Kevin, t hank you very much for responding. I will definitely look into the plug in next time.

      Tobias, regarding your question, I think this approach is best (custom post type) because not all employees should be WP users. It’s a security breach if you ask me to allow everybody for access even if it’s low level stuff and you can set permissions. You never know when someone might get the wrong access, etc. It’s best to hope for the best, prepare for the worst :).

      Furthermore, it’s cumbersome.

  26. Lars Inge Holen
    Permalink to comment#

    I’m trying to learn some wp …
    Where should I put the files – I have tried but can’t get it to work …

  27. Permalink to comment#

    Great post, learning a lot here, enjoying you guys having a pretty high level discussion. Thanks for sharing all of that with us!

  28. Lars Inge Holen
    Permalink to comment#

    Marco,

    I did put the files in wp-content/themes/name-of-your-theme/twentythirteen but Team Profiles is not showing in the menu …

  29. Philip
    Permalink to comment#

    Just a thought on this whilst playing around with it.

    It may be worth adding "publicly_queryable" => false to theregister_post_type function if you do not want users to be able to view individual team profile pages.

    With the current setup your profiles can be viewed individually at /?team=mark-zuckerberg which may cause a problem if you do not have an appropriate template file.

    Of course it would be highly unlikely for them to stumble across the url anyway, but I like to cover all bases.

    Any thoughts on this? Is there a better way to do it?

    P

  30. Philip
    Permalink to comment#

    Can anyone tell me how to get flush_rewrite_rules(); to work with this?

    I am trying to create a simple ‘projects’ custom post type plugin based on this but for the life of me I cannot get the flush_rewrite_rules(); to work!

    I have this call:

    register_activation_hook( __FILE__, 'Project_Custom_Post_Type::activate' );
    

    With my activate function like this:

    public function activate() {
        flush_rewrite_rules();
     }
    
    • Steffen
      Permalink to comment#

      Try this:

      register_activation_hook( __FILE__, array( 'Project_Custom_Post_Type', 'activate' ) );
      

      and replace your public function with public static function

      Maybe that helps.

  31. Permalink to comment#

    Very nice tutorial… first time I hear about the Transients API.
    Awesome team BTW ;)

  32. Is all PHP codes include in functions.php file? Question about create social icons in a custom field. Did you link it to font awesome? I was wondering how to link it.

  33. Excellent tutorial, thanks for posting this!

  34. Rob
    Permalink to comment#

    Is there a simple way to create a plugin version of this CPT? How do I make it into a plugin? Any guidance it appreciated.

    GREAT tutorial, thank you so much!

  35. Permalink to comment#

    Amazing tutorial. Very well detailed. You could also use this type of code to create other custom post types in wordpress such as portfolio, services and others. Anyhow, keep up the good work.

  36. Jim
    Permalink to comment#

    This is a great article and I’ve immediately decided to work it into some of my projects. However, being new to PHP could someone help me to change the orderby value please?

    Basically, I don’t want to order my page by alphabetical.

    I’d like to create an order index, possibly using a custom field for each team profile (could that work?), and then for each team profile I’d enter a value (1,2,3..) and that in my head would be the way to order them.

    My problem is, I’ve no idea what I need to put in the team-template PHP page to replace this:-

    'orderby' => 'Title', // Order alphabetically by name

    Could anyone be kind enough to point me in the right direction, or at least tell me I’m going about this the wrong way?

    How would I replace ‘Title’ in the code to use my custom field, say ‘index_no’ ?

    Thanks

    • Permalink to comment#

      Hi Jim. Here are all the parametrs for order by:

      orderby (string) – Sort retrieved posts by parameter. Defaults to ‘date’. One or more options can be passed.

      ‘none’ – No order (available with Version 2.8).

      ‘ID’ – Order by post id. Note the captialization.

      ‘author’ – Order by author.

      ‘title’ – Order by title.

      ‘name’ – Order by post name (post slug).

      ‘date’ – Order by date.

      ‘modified’ – Order by last modified date.

      ‘parent’ – Order by post/page parent id.

      ‘rand’ – Random order.

      ‘comment_count’ – Order by number of comments (available with Version 2.9).

      ‘menu_order’ – Order by Page Order. Used most often for Pages (Order field in the Edit Page Attributes box) and for Attachments (the integer fields in the Insert / Upload Media Gallery dialog), but could be used for any post type with distinct ‘menu_order’ values (they all default to 0).

      ‘meta_value’ – Note that a ‘meta_key=keyname’ must also be present in the query. Note also that the sorting will be alphabetical which is fine for strings (i.e. words), but can be unexpected for numbers (e.g. 1, 3, 34, 4, 56, 6, etc, rather than 1, 3, 4, 6, 34, 56 as you might naturally expect).

      Use ‘meta_value_num’ instead for numeric values.

      ‘meta_value_num’ – Order by numeric meta value (available with Version 2.8). Also note that a ‘meta_key=keyname’ must also be present in the query. This value allows for numerical sorting as noted above in ‘meta_value’.

      ‘post__in’ – Preserve post ID order given in the post__in array (available with Version 3.5).

  37. Jim
    Permalink to comment#

    Many thanks HeroWP for taking the time to post all this great info!

    Extremely helpful!

  38. James
    Permalink to comment#

    Hi all. I’m following this tutorial to the letter, and I’m having terrible luck with getting the custom CSS to appear.

    To include a stylesheet for our custom template only (template-team.php), we can use the following conditional check.

    Where, specifically, do I insert this PHP code? In the functions.php of my theme? Somewhere else? I cannot get the page to load the css despite being told to use a template.

    An otherwise flawless tutorial, though!

  39. Niamh
    Permalink to comment#

    This is exactly what I was looking for! Thank you Kevin I’m really excited to use it on my website. My only issue is probably redundant here as I am a newbie at all this but my custom page template will not show up on my dropdown menu in WP I’ve changed the theme and put it back again checked the php of the page title etc and it’s driving me nuts if you have any advice I’d be super grateful!

    • Stig Bratvold
      Permalink to comment#

      I had the same problem. Have you put the template-team.php in the right directory?

      You should save it in wp-content\themes(your-theme-name)/template-team.php

      When I did that it worked. Hope it helps :)

  40. Permalink to comment#

    All very simple, well explained, but one thing: I cannot make appear the icons from the fields reported in the ACF. I have not the icons in my directories. Where do I find them?
    Thank you.

  41. Jay
    Permalink to comment#

    Great Tutorial, really useful and exactly what i was looking for. I was wondering though is there a way that you can display them in blocks by department for example everyone in sales would be displayed together with the title sales above? thanks for the help.

  42. For some reason I am not getting the “team” page under page attributes when I am trying to create a new page. I have followed the tutorial all the way to where I finish the loop and then youn talk about “notes about performance”.

    Where are you supposed to save the “template-team.php” file? Currently I saved it in the “wp-includes” folder. I am trying it out on a localhost so I can not provide a link sadly.

    • Jay
      Permalink to comment#

      Hi Stig,

      you save the template-team.php file in the theme folder for example:
      wp-content\themes\your-theme-name

      I hope that helps :)

    • Or forget about it. I read in some earlier comments that I am supposed to put the “template-team.php” file in the theme directory and it worked like a charm :)

    • Stig Bratvold
      Permalink to comment#

      Thanks for your reply Jay. I finally figured that out.

  43. It does not load the rest of the wordpress theme page. When I create a new page using the new “team” template it just loads the body-tag with a section containing the team content I just created. I am not getting the header, footer, navbar etc.

    Does anyone know why this happens?

    • Jay
      Permalink to comment#

      you have to include:

      get_header();
      get_sidebar();
      get_footer();

      include the header at the top and the sidebar and footer at the bottom and that should call in all your other content.

    • Stig Bratvold
      Permalink to comment#

      Perfect! Thanks Jay :)

  44. Alex
    Permalink to comment#

    Hey,
    i didn’t understand the get_posts section.
    Can somebody post a example how it should be look
    in the single.php?
    Sorry, I’m an absolute beginner.
    Regards,
    Alex

  45. Alistair
    Permalink to comment#

    HI Great tut, one problem I am having is that it is pulling the page I created ‘Meet your team’ into the position field instead of the name of the person any ideas? I am using WP3.7.1 and a custom genesis v2 theme site is under construction at the moment I can send you any files if you require also only displays in 1 coloum instead of 2 thank you Alistair

    • JohnC
      Permalink to comment#

      I too am trying to work this out too for a Genesis theme. Have everything per the tutorial but when I add the final section for displaying the post, the page breaks ( totally white space). Commenting out that section repairs the page but there is no post information. Perhaps the Genesis Framework needs specific syntax. I like this solution however and want to get it working. So even though this isn’t working for me yet, I’m pretty excited about the prospects.

    • JohnC
      Permalink to comment#

      Slight mod to my comment. I’m putting everything in my main functions.php and left out the last line (thinking it was part of a stand alone file) <?php endif; ?> oops. Si I now have my team but they are on my home page and not styled. Will keep digging but evidently this us user error. :)

  46. Stig Bratvold
    Permalink to comment#

    Where do I find /assets/css/team.css to add the css? I can not find assets in the theme directory. Is that directory somewhere else?

  47. Great idea but its so much easier, flexible and more efficient to create a custom page template with widgets split 50% width and populate the widget area’s with any type of user profile widget.

    Coded this recently which took around 20 minutes and is very easy to customize with your own styling.

  48. Fredrik P
    Permalink to comment#

    Nice script.

    I have created a list and its working greate, but i can not get this working with taxonomy!
    (Im building a new own theme)

    I tried to set ‘Regioner’ => ‘en’, in the get_post but it doesnt get it.

    Im using the q_translate, so the Region is set to “sv” “en” and so on.

    What am i missing here?

    The labels for the taxonomy in the functions.php is:

    function team_taxonomy() {
    // Labels
    $singular = 'Region';
    $plural = 'Regioner';
    $labels = array(
    

    and heres the code for the output:

    <?php
    the_post();
    
    // Get 'team' posts
    $team_posts = get_posts( array(
    
        //'Regioner' => 'en',
        'post_type' => 'team',
        'posts_per_page' => -1, // Unlimited posts
        'meta_key' => 'team_sortorder', //what it should sorting by - this is is custom post
        'orderby' => 'meta_value', // "meta_value" using the meta_key parameter -  "title" Order alphabetically by name
        'order' => 'ASC',
    
    
    if ( $team_posts ):
    
    
    ?>
    
    
    <section>
    
    
        <?php 
        foreach ( $team_posts as $post ): 
        setup_postdata($post);
    
        // Resize and CDNize thumbnails using Automattic Photon service
        $thumb_src = null;
        if ( has_post_thumbnail($post->ID) ) {
            $src = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'team-thumb' );
            $thumb_src = $src[0];
        }
        ?>
        <div style="float:left; margin-right:20px; height:200px; min-width:350px;">&nbsp;
            <div style="background-image:url(<?php if ( $thumb_src ): ?><?php echo $thumb_src; ?><?php endif; ?>); " class="TeamProfileImage"></div>
            <div class="TeamProfileText">
                <h2><?php the_title(); ?></h2>
                <p><b><?php the_field('team_position'); ?></b></p>
                <?php the_content(); ?>
    
                <i class="icon-mobile-phone"></i> <?php the_field('team_phone'); ?><br>
                <i class="icon-envelope"></i> <a href="mailto:<?php echo antispambot( get_field('team_email') ); ?>"><?php echo antispambot( get_field('team_email') ); ?></a><br>
                <?php if ( $twitter = get_field('team_twitter') ): ?>
                    <a href="<?php echo $twitter; ?>"><i class="icon-twitter"></i></a><br>
                <?php endif; ?>
                <?php if ( $linkedin = get_field('team_linkedin') ): ?>
                    <a href="<?php echo $linkedin; ?>"><i class="icon-linkedin"></i></a><br>
                <?php endif; ?>
             </div>
        </div>
    
    
        <?php endforeach; ?>
    </section><!-- /.row -->
    <?php endif; ?>
    
  49. Patrick
    Permalink to comment#

    This is by far the clearest tutorial on CPTs I have found to date and being relatively new to WordPress design & theme development, it has been a massive help in ‘shallowing’ out the learning curve.

  50. Shubham
    Permalink to comment#

    Very very Nice

  51. TheCurryGuy
    Permalink to comment#

    I’m having an issue similar to what Stig has posted.

    There is no directory called “assets/css” where I can place the team.css file. I just put it in the same directory as all my theme files (wpcontent/themes/mytheme)

    Which file do I even put the conditional CSS loading code into? Functions.php ? Team-template.php?

    • Hi @TheCurryGuy. I figured it out. It was in the directory of the theme I was using. I think this might be different depending on what theme you are using because that folder does not exist in the default themes like twentyfourteen.

      On the theme is was using I found it in
      my theme/admin/assets/css

      But I guess if it does not exist in your theme you can just create a folder with that name using ftp.

      And the conditional CSS loading code you put in to the functions.php.

      Hope this helps! Good luck

  52. Fredrik Pilegård
    Permalink to comment#

    i have a problem with the taxonomy. i have posted the code before, but no respons. Is there anyone that can help to get me in to the right direction?

    QUESTION:
    I have created a list and its working greate, but i can not get this working with taxonomy!
    (Im building a new own theme)

    I tried to set ‘Regioner’ => ‘en’, in the get_post but it doesnt get it.

    Im using the q_translate, so the Region is set to “sv” “en” and so on.

    What am i missing here?

    THE CODE:

    The labels for the taxonomy in the functions.php is:

    function team_taxonomy() {
    // Labels
    $singular = 'Region';
    $plural = 'Regioner';
    $labels = array(

    and heres the code for the output:

    ` ‘en’,
    ‘post_type’ => ‘team’,
    ‘posts_per_page’ => -1, // Unlimited posts
    ‘meta_key’ => ‘team_sortorder’, //what it should sorting by – this is is custom post
    ‘orderby’ => ‘meta_value’, // “meta_value” using the meta_key parameter – “title” Order alphabetically by name
    ‘order’ => ‘ASC’,

    if ( $team_posts ):

    ?>

    <?php 
    foreach ( $team_posts as $post ): 
    setup_postdata($post);
    
    // Resize and CDNize thumbnails using Automattic Photon service
    $thumb_src = null;
    if ( has_post_thumbnail($post->ID) ) {
        $src = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'team-thumb' );
        $thumb_src = $src[0];
    }
    ?>
    <div style="float:left; margin-right:20px; height:200px; min-width:350px;">&nbsp;
        <div style="background-image:url(<?php if ( $thumb_src ): ?><?php echo $thumb_src; ?><?php endif; ?>); " class="TeamProfileImage"></div>
        <div class="TeamProfileText">
            <h2><?php the_title(); ?></h2>
            <p><b><?php the_field('team_position'); ?></b></p>
            <?php the_content(); ?>
    
            <i class="icon-mobile-phone"></i> <?php the_field('team_phone'); ?><br>
            <i class="icon-envelope"></i> <a href="mailto:<?php echo antispambot( get_field('team_email') ); ?>"><?php echo antispambot( get_field('team_email') ); ?></a><br>
            <?php if ( $twitter = get_field('team_twitter') ): ?>
                <a href="<?php echo $twitter; ?>"><i class="icon-twitter"></i></a><br>
            <?php endif; ?>
            <?php if ( $linkedin = get_field('team_linkedin') ): ?>
                <a href="<?php echo $linkedin; ?>"><i class="icon-linkedin"></i></a><br>
            <?php endif; ?>
         </div>
    </div>
    
    
    <?php endforeach; ?>
    


    `

  53. Nicole
    Permalink to comment#

    Hi there!

    This tutorial is awesome! I’m just getting back into coding after about a 2 year break and this tutorial isn’t making my brain hurt at all! Although I’m having a small problem.

    After doing all the steps (except creating the taxonomy) up to creating “template-team.php”, the fields to enter information under Team Profiles are not appearing. Anyone else having this problem? Help? Please?

    Thanks,

    Nicole

  54. Matt S
    Permalink to comment#

    As others have said, a great tutorial, thank you.

    I’m reluctantly having to use WP again for a particular project I’m working on due to it having a brilliant plugin to create a membership site (S2 Member Pro). I’ve avoided WP for a year or two instead I’ve been using MODX which is in my opinion far superior than WP for creating your own templates and custom fields. The main reasons I stopped using WP was because it was so frustrating to create what I wanted it to do and most of the tutorials prior to this one were contradictory, misinforming, too basic or almost useless, not that it doesn’t happen with MODX tuts either to be fair..

    It looks like ACF is the answer to what I need when having to use WP for some of the projects I do. And your tutorial is a great insight into WP, I can see there is more potential now with WP along with ACF, as without it WP is far to messy to reasonably code for.

  55. Permalink to comment#

    Hi,

    I followed the tutorial . But in final steps of creating a team page, I am getting a plain white page where a list of team members is shown without any header footer and sidebars. Neither are the links visible which you have shown in demo(twitter, facebook, email etc). I would be glad to know where I am going wrong.

    I have created the team-template function, functions.php and style.css for template in child theme folder.

    FYI, I am a newbie who is trying hands in wordpress.

    Link for my team-page: meet our team page

  56. Viki
    Permalink to comment#

    Great tutorial, thank you for posting… by using the title for the person’s name, you can only sort alphabetically by first name. Sorting by last name usually makes more sense — if I add fields for first & last name, and remove support for “title” – then I have no way to edit the posts from the admin area’s list of posts (only hovering over the title gives me the edit, view, trash options). Do you know of a solution for this? thanks!

  57. Michael
    Permalink to comment#

    I think you could be a little more clear on exactly what files you are editing and where exactly those files need to reside. having zero experience in this it took me several hours and several re-dos to figure out what files you were editing and where they are located on my server. The styles part specifically. where exactly does the conditional part go? I put it in functions and created the /assets/ccs/team.css file but it isn’t working. i’ll figure it out eventually but it could be made more clear where things are supposed to go. for instance, in the panels with the code it just says simply “php” or “css”. putting the whole filename in there would make it a lot more helpful. thanks again!

  58. Great code, and great teacher. I am having a bit of trouble figuring out the icon calls, but is there anyway to order the list, i.e. ‘me’, ‘editor’, ‘contributor 1′, ‘contributor 2′, etc.?

  59. Hi, thank you for this tutorial. It is something I have seen on the backend of sites before, but have never seen the setup process broken down so accessibly.

    The section titled “Notes on Performance” provides some additional php code. I am not certain where to add this. I went to the link on transients API but am still lost. Any help?

    • : / I am a bit lost beyond this. Any help would be much appreciated. I’ve created a new template with the code you supplied… I will do a bit of research into this. I get that I need to someone incorporate the “content-page.php” template or one of the templates that will bring in the navigation and general site layout. I’m not sure where to drop in your css in addition to which template? I’m using Canvas if anyone has done this with Canvas.

      In the example directory for the css. it is /assets/css/team.css Is assets just used an example, or should there be a directory titled assets attached to my site. If so what should /proceed it.

      I have the bad feeling I am the person on the forum that is hopeless… anything will be appreciated!

    • saied
      Permalink to comment#

      sorry to be spammy. I’ve figured out the adding of the header, footer, sidebar. Styling is a bit of a question mark but I think I can resolve it. Still uncertain where to place the “Notes on Performance”

  60. Aoki
    Permalink to comment#

    Great resources! One question how do I change the image display from circle to square display.

  61. JG
    Permalink to comment#

    Do you have an installer for this meet team page ‘plugin’?

    I have the team page, but style is wrong

    My configuration.
    Files in theme folder:
    - template-team.php
    - /assets/css/team.css
    - /assets/css/team.less
    - functions.php (register team and department)

    How do I fix it?

  62. Radj

    How to load the team.css in the template-team.php?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".