1. Code
  2. PHP

Using WordPress to Create an FAQ System With Custom Post Types

If you're building a website for a person or an organization and the website needs to educate its visitors with small doses of knowledge, an FAQ page is one of the simplest solutions to do it. In this tutorial, we're going to see how we can build such a system.
Scroll to top
12 min read

I recently worked with a client of mine, who works as a professional consultant in her area of work. She asked if I could implement a Q&A system, or an FAQ page, to be exact. I said, "Sure, we can just create a page and paste the questions and answers there with different styling," but she said she would create different pages and categorize questions and answers, and for the sake of being more organized, she needed a different approach.

To that end, I'm going to show you how I handled her requests with some simple lines of code using custom post types, taxonomies, and shortcodes.

The Custom Post Type and the Taxonomy

What do we need to build an FAQ system? 

  • We need fields for questions and answers.
  • We need categories to classify and separate different types of questions and their answers.
  • In our case, we need a shortcode to embed these question groups or all questions in a page or a post.

Let's begin by creating the custom post type.

Step 1: Creating the Custom Post Type

Naturally, we're going to start off by setting up a custom post type for our FAQ items. We're going to create a new custom post type with the help of the register_post_type() function, but if you want a GUI for creating your post type, you can generate it with GenerateWP's Post Type Generator tool as I did in this example:

1
<?php
2
3
if ( ! function_exists( 'tuts_faq_cpt' ) ) {
4
5
// register custom post type

6
	function tuts_faq_cpt() {
7
8
		// these are the labels in the admin interface, edit them as you like

9
		$labels = array(
10
			'name'                => _x( 'FAQs', 'Post Type General Name', 'tuts_faq' ),
11
			'singular_name'       => _x( 'FAQ', 'Post Type Singular Name', 'tuts_faq' ),
12
			'menu_name'           => __( 'FAQ', 'tuts_faq' ),
13
			'parent_item_colon'   => __( 'Parent Item:', 'tuts_faq' ),
14
			'all_items'           => __( 'All Items', 'tuts_faq' ),
15
			'view_item'           => __( 'View Item', 'tuts_faq' ),
16
			'add_new_item'        => __( 'Add New FAQ Item', 'tuts_faq' ),
17
			'add_new'             => __( 'Add New', 'tuts_faq' ),
18
			'edit_item'           => __( 'Edit Item', 'tuts_faq' ),
19
			'update_item'         => __( 'Update Item', 'tuts_faq' ),
20
			'search_items'        => __( 'Search Item', 'tuts_faq' ),
21
			'not_found'           => __( 'Not found', 'tuts_faq' ),
22
			'not_found_in_trash'  => __( 'Not found in Trash', 'tuts_faq' ),
23
		);
24
		$args = array(
25
			// use the labels above

26
			'labels'              => $labels,
27
			// we'll only need the title, the Visual editor and the excerpt fields for our post type

28
			'supports'            => array( 'title', 'editor', 'excerpt', ),
29
			// we're going to create this taxonomy in the next section, but we need to link our post type to it now

30
			'taxonomies'          => array( 'tuts_faq_tax' ),
31
			// make it public so we can see it in the admin panel and show it in the front-end

32
			'public'              => true,
33
			// show the menu item under the Pages item

34
			'menu_position'       => 20,
35
			// show archives, if you don't need the shortcode

36
			'has_archive'         => true,
37
		);
38
		register_post_type( 'tuts_faq', $args );
39
40
	}
41
42
	// hook into the 'init' action

43
	add_action( 'init', 'tuts_faq_cpt', 0 );
44
45
}
46
47
?>

Tip: If your project is going to involve more custom post types that can be more complex than this simple FAQ post type, I can suggest a cool tool called SuperCPT which allows you to create new post types with even simpler code. I've written a tutorial about SuperCPT, too, you can check it out here.

Step 2: Creating the Custom Taxonomy

In order to separate different types of questions (like my client's questions and answers about miscarriage and postpartum depression), we're going to need a category system. As many of you already know, WordPress provides this functionality with custom taxonomies.

The essential function here is register_taxonomy() but again, you can use GenerateWP's Taxonomy Generator tool if you need a graphical interface. 

Here's the code:

1
<?php
2
3
if ( ! function_exists( 'tuts_faq_tax' ) ) {
4
5
	// register custom taxonomy

6
	function tuts_faq_tax() {
7
8
		// again, labels for the admin panel

9
		$labels = array(
10
			'name'                       => _x( 'FAQ Categories', 'Taxonomy General Name', 'tuts_faq' ),
11
			'singular_name'              => _x( 'FAQ Category', 'Taxonomy Singular Name', 'tuts_faq' ),
12
			'menu_name'                  => __( 'FAQ Categories', 'tuts_faq' ),
13
			'all_items'                  => __( 'All FAQ Cats', 'tuts_faq' ),
14
			'parent_item'                => __( 'Parent FAQ Cat', 'tuts_faq' ),
15
			'parent_item_colon'          => __( 'Parent FAQ Cat:', 'tuts_faq' ),
16
			'new_item_name'              => __( 'New FAQ Cat', 'tuts_faq' ),
17
			'add_new_item'               => __( 'Add New FAQ Cat', 'tuts_faq' ),
18
			'edit_item'                  => __( 'Edit FAQ Cat', 'tuts_faq' ),
19
			'update_item'                => __( 'Update FAQ Cat', 'tuts_faq' ),
20
			'separate_items_with_commas' => __( 'Separate items with commas', 'tuts_faq' ),
21
			'search_items'               => __( 'Search Items', 'tuts_faq' ),
22
			'add_or_remove_items'        => __( 'Add or remove items', 'tuts_faq' ),
23
			'choose_from_most_used'      => __( 'Choose from the most used items', 'tuts_faq' ),
24
			'not_found'                  => __( 'Not Found', 'tuts_faq' ),
25
		);
26
		$args = array(
27
			// use the labels above

28
			'labels'                     => $labels,
29
			// taxonomy should be hierarchial so we can display it like a category section

30
			'hierarchical'               => true,
31
			// again, make the taxonomy public (like the post type)

32
			'public'                     => true,
33
		);
34
		// the contents of the array below specifies which post types should the taxonomy be linked to

35
		register_taxonomy( 'tuts_faq_tax', array( 'tuts_faq' ), $args );
36
37
	}
38
39
	// hook into the 'init' action

40
	add_action( 'init', 'tuts_faq_tax', 0 );
41
42
}
43
44
?>

That's it! Now you have an FAQ post type with a taxonomy called "FAQ Categories" linked to each other! Check your administration panel and you'll see the "FAQ Categories" menu item under the "FAQ". 

Just like regular post categories, you can add, edit or remove them in the "FAQ Categories" page, or you can add new categories while you're writing a new FAQ item.

Step 3: Creating the [faq] Shortcode

Here comes the fun part: building the shortcode. (If you've read my previous posts, you know that I'm a huge fan of WordPress shortcodes.) We're basically going to make the FAQ items embeddable into posts and pages. 

Here's what's going to happen:

  • query inside our new custom post type,
  • filter its categories with a shortcode parameter,
  • display the questions and answers as titles and content,
  • show an excerpt of the answer with a "More..." link, controlled by another shortcode parameter.

Let's begin building the shortcode. Like the code above, I'm going to include some helpful comments:

1
<?php
2
3
if ( ! function_exists( 'tuts_faq_shortcode' ) ) {
4
5
	function tuts_faq_shortcode( $atts ) {
6
		extract( shortcode_atts(
7
				array(
8
					// category slug attribute - defaults to blank

9
					'category' => '',
10
					// full content or excerpt attribute - defaults to full content

11
					'excerpt' => 'false',
12
				), $atts )
13
		);
14
		
15
		$output = '';
16
		
17
		// set the query arguments

18
		$query_args = array(
19
			// show all posts matching this query

20
			'posts_per_page'	=>	-1,
21
			// show the 'tuts_faq' custom post type

22
			'post_type'			=>	'tuts_faq',
23
			// show the posts matching the slug of the FAQ category specified with the shortcode's attribute

24
			'tax_query'			=>	array(
25
				array(
26
					'taxonomy'	=>	'tuts_faq_tax',
27
					'field'		=>	'slug',
28
					'terms'		=>	$category,
29
				)
30
			),
31
			// tell WordPress that it doesn't need to count total rows - this little trick reduces load on the database if you don't need pagination

32
			'no_found_rows'		=>	true,
33
		);
34
		
35
		// get the posts with our query arguments

36
		$faq_posts = get_posts( $query_args );
37
		$output .= '<div class="tuts-faq">';
38
		
39
		// handle our custom loop

40
		foreach ( $faq_posts as $post ) {
41
			setup_postdata( $post );
42
			$faq_item_title = get_the_title( $post->ID );
43
			$faq_item_permalink = get_permalink( $post->ID );
44
			$faq_item_content = get_the_content();
45
			if( $excerpt == 'true' )
46
				$faq_item_content = get_the_excerpt() . '<a href="' . $faq_item_permalink . '">' . __( 'More...', 'tuts_faq' ) . '</a>';
47
			
48
			$output .= '<div class="tuts-faq-item">';
49
			$output .= '<h3 class="tuts-faq-item-title">' . $faq_item_title . '</h3>';
50
			$output .= '<div class="tuts-faq-item-content">' . $faq_item_content . '</div>';
51
			$output .= '</div>';
52
		}
53
		
54
		wp_reset_postdata();
55
		
56
		$output .= '</div>';
57
		
58
		return $output;
59
	}
60
61
	add_shortcode( 'faq', 'tuts_faq_shortcode' );
62
63
}
64
65
?>

That's it! Now we have a neat shortcode to embed our questions and answers. You can style it with the class names tuts-faq, tuts-faq-item, tuts-faq-item-title, and tuts-faq-item-content. Although, it should be fine even if you don't include additional styling.

Step 4: Wrapping Up the Code

Since these bits of code aren't just about styling the front-end but also introducing new functionality, it counts as plugin territory. That's why we must save the code as a plugin. And while we're at it, we should also flush the rewrite rules upon activation and deactivation.

Here's the full code:

1
<?php
2
/*

3
Plugin Name: Simple FAQ System

4
Plugin URI: http://code.tutsplus.com/

5
Description: Helps you create an FAQ section for your WordPress website. Shortcode usage: <code>[faq]</code>

6
Version: 1.0

7
Author: Barış Ünver

8
Author URI: http://hub.tutsplus.com/authors/baris-unver

9
License: Public Domain

10
*/
11
12
if ( ! function_exists( 'tuts_faq_cpt' ) ) {
13
14
// register custom post type

15
	function tuts_faq_cpt() {
16
17
		// these are the labels in the admin interface, edit them as you like

18
		$labels = array(
19
			'name'                => _x( 'FAQs', 'Post Type General Name', 'tuts_faq' ),
20
			'singular_name'       => _x( 'FAQ', 'Post Type Singular Name', 'tuts_faq' ),
21
			'menu_name'           => __( 'FAQ', 'tuts_faq' ),
22
			'parent_item_colon'   => __( 'Parent Item:', 'tuts_faq' ),
23
			'all_items'           => __( 'All Items', 'tuts_faq' ),
24
			'view_item'           => __( 'View Item', 'tuts_faq' ),
25
			'add_new_item'        => __( 'Add New FAQ Item', 'tuts_faq' ),
26
			'add_new'             => __( 'Add New', 'tuts_faq' ),
27
			'edit_item'           => __( 'Edit Item', 'tuts_faq' ),
28
			'update_item'         => __( 'Update Item', 'tuts_faq' ),
29
			'search_items'        => __( 'Search Item', 'tuts_faq' ),
30
			'not_found'           => __( 'Not found', 'tuts_faq' ),
31
			'not_found_in_trash'  => __( 'Not found in Trash', 'tuts_faq' ),
32
		);
33
		$args = array(
34
			// use the labels above

35
			'labels'              => $labels,
36
			// we'll only need the title, the Visual editor and the excerpt fields for our post type

37
			'supports'            => array( 'title', 'editor', 'excerpt', ),
38
			// we're going to create this taxonomy in the next section, but we need to link our post type to it now

39
			'taxonomies'          => array( 'tuts_faq_tax' ),
40
			// make it public so we can see it in the admin panel and show it in the front-end

41
			'public'              => true,
42
			// show the menu item under the Pages item

43
			'menu_position'       => 20,
44
			// show archives, if you don't need the shortcode

45
			'has_archive'         => true,
46
		);
47
		register_post_type( 'tuts_faq', $args );
48
49
	}
50
51
	// hook into the 'init' action

52
	add_action( 'init', 'tuts_faq_cpt', 0 );
53
54
}
55
56
if ( ! function_exists( 'tuts_faq_tax' ) ) {
57
58
	// register custom taxonomy

59
	function tuts_faq_tax() {
60
61
		// again, labels for the admin panel

62
		$labels = array(
63
			'name'                       => _x( 'FAQ Categories', 'Taxonomy General Name', 'tuts_faq' ),
64
			'singular_name'              => _x( 'FAQ Category', 'Taxonomy Singular Name', 'tuts_faq' ),
65
			'menu_name'                  => __( 'FAQ Categories', 'tuts_faq' ),
66
			'all_items'                  => __( 'All FAQ Cats', 'tuts_faq' ),
67
			'parent_item'                => __( 'Parent FAQ Cat', 'tuts_faq' ),
68
			'parent_item_colon'          => __( 'Parent FAQ Cat:', 'tuts_faq' ),
69
			'new_item_name'              => __( 'New FAQ Cat', 'tuts_faq' ),
70
			'add_new_item'               => __( 'Add New FAQ Cat', 'tuts_faq' ),
71
			'edit_item'                  => __( 'Edit FAQ Cat', 'tuts_faq' ),
72
			'update_item'                => __( 'Update FAQ Cat', 'tuts_faq' ),
73
			'separate_items_with_commas' => __( 'Separate items with commas', 'tuts_faq' ),
74
			'search_items'               => __( 'Search Items', 'tuts_faq' ),
75
			'add_or_remove_items'        => __( 'Add or remove items', 'tuts_faq' ),
76
			'choose_from_most_used'      => __( 'Choose from the most used items', 'tuts_faq' ),
77
			'not_found'                  => __( 'Not Found', 'tuts_faq' ),
78
		);
79
		$args = array(
80
			// use the labels above

81
			'labels'                     => $labels,
82
			// taxonomy should be hierarchial so we can display it like a category section

83
			'hierarchical'               => true,
84
			// again, make the taxonomy public (like the post type)

85
			'public'                     => true,
86
		);
87
		// the contents of the array below specifies which post types should the taxonomy be linked to

88
		register_taxonomy( 'tuts_faq_tax', array( 'tuts_faq' ), $args );
89
90
	}
91
92
	// hook into the 'init' action

93
	add_action( 'init', 'tuts_faq_tax', 0 );
94
95
}
96
97
if ( ! function_exists( 'tuts_faq_shortcode' ) ) {
98
99
	function tuts_faq_shortcode( $atts ) {
100
		extract( shortcode_atts(
101
				array(
102
					// category slug attribute - defaults to blank

103
					'category' => '',
104
					// full content or excerpt attribute - defaults to full content

105
					'excerpt' => 'false',
106
				), $atts )
107
		);
108
		
109
		$output = '';
110
		
111
		// set the query arguments

112
		$query_args = array(
113
			// show all posts matching this query

114
			'posts_per_page'	=>	-1,
115
			// show the 'tuts_faq' custom post type

116
			'post_type'			=>	'tuts_faq',
117
			// show the posts matching the slug of the FAQ category specified with the shortcode's attribute

118
			'tax_query'			=>	array(
119
				array(
120
					'taxonomy'	=>	'tuts_faq_tax',
121
					'field'		=>	'slug',
122
					'terms'		=>	$category,
123
				)
124
			),
125
			// tell WordPress that it doesn't need to count total rows - this little trick reduces load on the database if you don't need pagination

126
			'no_found_rows'		=>	true,
127
		);
128
		
129
		// get the posts with our query arguments

130
		$faq_posts = get_posts( $query_args );
131
		$output .= '<div class="tuts-faq">';
132
		
133
		// handle our custom loop

134
		foreach ( $faq_posts as $post ) {
135
			setup_postdata( $post );
136
			$faq_item_title = get_the_title( $post->ID );
137
			$faq_item_permalink = get_permalink( $post->ID );
138
			$faq_item_content = get_the_content();
139
			if( $excerpt == 'true' )
140
				$faq_item_content = get_the_excerpt() . '<a href="' . $faq_item_permalink . '">' . __( 'More...', 'tuts_faq' ) . '</a>';
141
			
142
			$output .= '<div class="tuts-faq-item">';
143
			$output .= '<h2 class="faq-item-title">' . $faq_item_title . '</h2>';
144
			$output .= '<div class="faq-item-content">' . $faq_item_content . '</div>';
145
			$output .= '</div>';
146
		}
147
		
148
		wp_reset_postdata();
149
		
150
		$output .= '</div>';
151
		
152
		return $output;
153
	}
154
155
	add_shortcode( 'faq', 'tuts_faq_shortcode' );
156
157
}
158
159
function tuts_faq_activate() {
160
	tuts_faq_cpt();
161
	flush_rewrite_rules();
162
}
163
164
register_activation_hook( __FILE__, 'tuts_faq_activate' );
165
166
function tuts_faq_deactivate() {
167
	flush_rewrite_rules();
168
}
169
register_deactivation_hook( __FILE__, 'tuts_faq_deactivate' );
170
171
?>

Room for Improvement

My client was happy with the results when I showed her how to use it. But here, we can expand the code with more functionality, like...

  1. Accordion Effect: If you'd like to make your FAQ sections more attractive with some toggle effects, you can use some terrific jQuery plugins. If you want to use jQuery UI, there's an amazing tutorial by Shane Osbourne that shows how to do it.
  2. Pagination: If you have a lot of questions and answers for a category and don't want to display all the items at once, you can limit the number of posts by changing the posts_per_page parameter in the custom query of our shortcode, and add the required code for pagination links below the line with the wp_reset_postdata(); code. Remember to remove the 'no_found_rows' => true, line, though - pagination will not work if you don't remove that!
  3. Random Question: Let's say you want to display one random question and answer on homepage and you want it to change every page refresh. All you need to do is to head to the custom query, change the posts_per_page parameter from -1 to 1 and add another line with the code 'orderby' => 'random', and you're good to go!

Conclusion

This is how you build a simple FAQ system in WordPress through the use of custom post types, custom taxonomies, and shortcodes. I hope you enjoyed this tutorial and you can utilize it in your next project. Don't forget to share the article, if you liked it!

Do you have any ideas to improve this FAQ system? Share your comments below!