In developing a job portal website, I faced the challenge of creating a flexible and manageable Job Listings CPT System. WordPress default post types were not sufficient for job portal needs requiring multiple criteria like location, education, experience, and job type. I decided to create a custom post type ‘lowongan’ with a complex taxonomy structure and advanced filtering system.
As an Example
- Before: Default WordPress posts with simple categories
- After: Custom post type ‘lowongan’ with 6 custom taxonomies and advanced filtering
- Result: Powerful, manageable, and user-friendly job portal
Understanding the Benefits
A job listings CPT system with complex taxonomy provides optimal data structure for job portals, enables powerful filtering, and more efficient content management.
The Challenge
I need a job listings CPT system that can be filtered by multiple criteria like location, education, experience, and job type. Default WordPress posts don’t provide enough flexibility for complex job portal needs.
Step-by-Step Implementation
Step 1: Creating Custom Post Type ‘Lowongan’
Implementation of job listings CPT system with optimal structure:
function create_job_listings_post_type() {
$labels = array(
'name' => 'Job Listings',
'singular_name' => 'Job Listing',
'menu_name' => 'Job Listings',
'add_new' => 'Add New Job',
'add_new_item' => 'Add New Job Listing',
'edit_item' => 'Edit Job Listing',
'new_item' => 'New Job Listing',
'view_item' => 'View Job Listing',
'search_items' => 'Search Job Listings',
'not_found' => 'No job listings found',
'not_found_in_trash' => 'No job listings found in trash'
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array('slug' => 'lowongan'),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
'show_in_rest' => true,
);
register_post_type('lowongan', $args);
}
add_action('init', 'create_job_listings_post_type');
Step 2: Creating Multiple Custom Taxonomies
Implementation of 6 custom taxonomies for our job listings CPT system:
function create_job_listings_taxonomies() {
// Taxonomy: Location
register_taxonomy('lokasi', 'lowongan', array(
'hierarchical' => true,
'labels' => array(
'name' => 'Location',
'singular_name' => 'Location',
'search_items' => 'Search Location',
'all_items' => 'All Locations',
'parent_item' => 'Parent Location',
'parent_item_colon' => 'Parent Location:',
'edit_item' => 'Edit Location',
'update_item' => 'Update Location',
'add_new_item' => 'Add New Location',
'new_item_name' => 'New Location Name',
'menu_name' => 'Location'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'lokasi'),
'show_in_rest' => true,
));
// Taxonomy: Education
register_taxonomy('pendidikan', 'lowongan', array(
'hierarchical' => true,
'labels' => array(
'name' => 'Education',
'singular_name' => 'Education',
'search_items' => 'Search Education',
'all_items' => 'All Education',
'edit_item' => 'Edit Education',
'update_item' => 'Update Education',
'add_new_item' => 'Add New Education',
'new_item_name' => 'New Education Name',
'menu_name' => 'Education'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'pendidikan'),
'show_in_rest' => true,
));
// Taxonomy: Experience
register_taxonomy('pengalaman', 'lowongan', array(
'hierarchical' => true,
'labels' => array(
'name' => 'Experience',
'singular_name' => 'Experience',
'search_items' => 'Search Experience',
'all_items' => 'All Experience',
'edit_item' => 'Edit Experience',
'update_item' => 'Update Experience',
'add_new_item' => 'Add New Experience',
'new_item_name' => 'New Experience Name',
'menu_name' => 'Experience'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'pengalaman'),
'show_in_rest' => true,
));
// Taxonomy: Job Type
register_taxonomy('jenis_pekerjaan', 'lowongan', array(
'hierarchical' => true,
'labels' => array(
'name' => 'Job Type',
'singular_name' => 'Job Type',
'search_items' => 'Search Job Type',
'all_items' => 'All Job Types',
'edit_item' => 'Edit Job Type',
'update_item' => 'Update Job Type',
'add_new_item' => 'Add New Job Type',
'new_item_name' => 'New Job Type Name',
'menu_name' => 'Job Type'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'jenis-pekerjaan'),
'show_in_rest' => true,
));
// Taxonomy: Position
register_taxonomy('posisi', 'lowongan', array(
'hierarchical' => true,
'labels' => array(
'name' => 'Position',
'singular_name' => 'Position',
'search_items' => 'Search Position',
'all_items' => 'All Positions',
'edit_item' => 'Edit Position',
'update_item' => 'Update Position',
'add_new_item' => 'Add New Position',
'new_item_name' => 'New Position Name',
'menu_name' => 'Position'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'posisi'),
'show_in_rest' => true,
));
// Taxonomy: Gender
register_taxonomy('gender', 'lowongan', array(
'hierarchical' => false,
'labels' => array(
'name' => 'Gender',
'singular_name' => 'Gender',
'search_items' => 'Search Gender',
'all_items' => 'All Gender',
'edit_item' => 'Edit Gender',
'update_item' => 'Update Gender',
'add_new_item' => 'Add New Gender',
'new_item_name' => 'New Gender Name',
'menu_name' => 'Gender'
),
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'gender'),
'show_in_rest' => true,
));
}
add_action('init', 'create_job_listings_taxonomies');
Step 3: Setting Default Terms
Implementation of default terms for easier content management in our job listings CPT system:
function set_default_job_listings_terms() {
// Default terms for Location
$lokasi_terms = array('Jakarta', 'Bandung', 'Surabaya', 'Medan', 'Semarang');
foreach ($lokasi_terms as $term) {
if (!term_exists($term, 'lokasi')) {
wp_insert_term($term, 'lokasi');
}
}
// Default terms for Education
$pendidikan_terms = array('High School', 'Diploma', 'Bachelor', 'Master', 'PhD');
foreach ($pendidikan_terms as $term) {
if (!term_exists($term, 'pendidikan')) {
wp_insert_term($term, 'pendidikan');
}
}
// Default terms for Experience
$pengalaman_terms = array('Fresh Graduate', '1-2 years', '3-5 years', '5-10 years', '10+ years');
foreach ($pengalaman_terms as $term) {
if (!term_exists($term, 'pengalaman')) {
wp_insert_term($term, 'pengalaman');
}
}
// Default terms for Job Type
$jenis_pekerjaan_terms = array('Full-time', 'Part-time', 'Contract', 'Freelance', 'Internship');
foreach ($jenis_pekerjaan_terms as $term) {
if (!term_exists($term, 'jenis_pekerjaan')) {
wp_insert_term($term, 'jenis_pekerjaan');
}
}
// Default terms for Position
$posisi_terms = array('Developer', 'Designer', 'Manager', 'Analyst', 'Support');
foreach ($posisi_terms as $term) {
if (!term_exists($term, 'posisi')) {
wp_insert_term($term, 'posisi');
}
}
// Default terms for Gender
$gender_terms = array('Male', 'Female', 'Male/Female');
foreach ($gender_terms as $term) {
if (!term_exists($term, 'gender')) {
wp_insert_term($term, 'gender');
}
}
}
add_action('init', 'set_default_job_listings_terms');
Step 4: Advanced Filtering System
Implementation of AJAX filtering with multiple taxonomy criteria for our job listings CPT system:
function rzwn_filter_job_listings() {
// Check nonce for security
if (!wp_verify_nonce($_POST['nonce'], 'lowongan-ajax-nonce')) {
wp_send_json_error(array('message' => 'Security check failed'));
}
// Get filter values
$lokasi = isset($_POST['lokasi']) ? array_map('intval', $_POST['lokasi']) : array();
$pendidikan = isset($_POST['pendidikan']) ? array_map('intval', $_POST['pendidikan']) : array();
$pengalaman = isset($_POST['pengalaman']) ? array_map('intval', $_POST['pengalaman']) : array();
$jenis_pekerjaan = isset($_POST['jenis_pekerjaan']) ? array_map('intval', $_POST['jenis_pekerjaan']) : array();
$posisi = isset($_POST['posisi']) ? array_map('intval', $_POST['posisi']) : array();
$gender = isset($_POST['gender']) ? array_map('intval', $_POST['gender']) : array();
// Build tax query
$tax_queries = array();
if (!empty($lokasi) && !in_array('all', $lokasi)) {
$tax_queries[] = array(
'taxonomy' => 'lokasi',
'field' => 'term_id',
'terms' => $lokasi,
'operator' => 'IN',
);
}
if (!empty($pendidikan) && !in_array('all', $pendidikan)) {
$tax_queries[] = array(
'taxonomy' => 'pendidikan',
'field' => 'term_id',
'terms' => $pendidikan,
'operator' => 'IN',
);
}
if (!empty($pengalaman) && !in_array('all', $pengalaman)) {
$tax_queries[] = array(
'taxonomy' => 'pengalaman',
'field' => 'term_id',
'terms' => $pengalaman,
'operator' => 'IN',
);
}
if (!empty($jenis_pekerjaan) && !in_array('all', $jenis_pekerjaan)) {
$tax_queries[] = array(
'taxonomy' => 'jenis_pekerjaan',
'field' => 'term_id',
'terms' => $jenis_pekerjaan,
'operator' => 'IN',
);
}
if (!empty($posisi) && !in_array('all', $posisi)) {
$tax_queries[] = array(
'taxonomy' => 'posisi',
'field' => 'term_id',
'terms' => $posisi,
'operator' => 'IN',
);
}
if (!empty($gender) && !in_array('all', $gender)) {
$tax_queries[] = array(
'taxonomy' => 'gender',
'field' => 'term_id',
'terms' => $gender,
'operator' => 'IN',
);
}
// Build query args
$args = array(
'post_type' => 'lowongan',
'post_status' => 'publish',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC',
);
// Add taxonomy filter if any filters are selected
if (!empty($tax_queries)) {
$args['tax_query'] = array(
'relation' => 'AND',
$tax_queries
);
}
$query = new WP_Query($args);
ob_start();
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
get_template_part('template-parts/lowongan/card');
}
wp_reset_postdata();
$response = array(
'html' => ob_get_clean(),
'max_pages' => $query->max_num_pages,
);
wp_send_json_success($response);
} else {
wp_send_json_error(array('message' => __('No job listings found', 'loker-website')));
}
wp_die();
}
add_action('wp_ajax_filter_job_listings', 'rzwn_filter_job_listings');
add_action('wp_ajax_nopriv_filter_job_listings', 'rzwn_filter_job_listings');
Step 5: Admin Meta Boxes
Implementation of custom meta boxes for enhanced content management in our job listings CPT system:
function add_job_listings_meta_boxes() {
add_meta_box(
'lowongan_details',
'Job Details',
'lowongan_details_callback',
'lowongan',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'add_job_listings_meta_boxes');
function job_listings_details_callback($post) {
wp_nonce_field('lowongan_meta_box', 'lowongan_meta_box_nonce');
$salary_min = get_post_meta($post->ID, '_salary_min', true);
$salary_max = get_post_meta($post->ID, '_salary_max', true);
$deadline = get_post_meta($post->ID, '_deadline', true);
echo '<table class="form-table">';
echo '<tr><th><label for="salary_min">Minimum Salary</label></th>';
echo '<td><input type="number" id="salary_min" name="salary_min" value="' . esc_attr($salary_min) . '" /></td></tr>';
echo '<tr><th><label for="salary_max">Maximum Salary</label></th>';
echo '<td><input type="number" id="salary_max" name="salary_max" value="' . esc_attr($salary_max) . '" /></td></tr>';
echo '<tr><th><label for="deadline">Deadline</label></th>';
echo '<td><input type="date" id="deadline" name="deadline" value="' . esc_attr($deadline) . '" /></td></tr>';
echo '</table>';
}
function save_job_listings_meta_box_data($post_id) {
if (!isset($_POST['lowongan_meta_box_nonce'])) {
return;
}
if (!wp_verify_nonce($_POST['lowongan_meta_box_nonce'], 'lowongan_meta_box')) {
return;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
if (isset($_POST['salary_min'])) {
update_post_meta($post_id, '_salary_min', sanitize_text_field($_POST['salary_min']));
}
if (isset($_POST['salary_max'])) {
update_post_meta($post_id, '_salary_max', sanitize_text_field($_POST['salary_max']));
}
if (isset($_POST['deadline'])) {
update_post_meta($post_id, '_deadline', sanitize_text_field($_POST['deadline']));
}
}
add_action('save_post', 'save_job_listings_meta_box_data');
Step 6: Testing and Validation
Comprehensive testing to ensure our job listings CPT system works optimally:
- CPT Registration: Custom post type successfully created with all taxonomies
- Default Terms: Default terms successfully set for all taxonomies
- Filtering System: AJAX filtering works with all criteria
- Admin Interface: Meta boxes and admin columns work properly
Libraries and Technologies Used
Our job listings CPT system utilizes:
- WordPress Core: Custom Post Types, Taxonomies, Meta Boxes
- AJAX Implementation: Dynamic filtering and content loading
- Custom PHP Functions: CPT registration and taxonomy management
- Security Features: Nonce verification, capability checking
Results and Impact
The job listings CPT system implementation successfully provided:
- Powerful Filtering: Filtering based on 6 different criteria
- Better Organization: More efficient content management
- User Experience: User-friendly interface for job seekers
- Scalability: System that can handle large numbers of job listings
Conclusion
The job listings CPT system with complex taxonomy structure successfully provided an optimal solution for the job portal. With 6 custom taxonomies, advanced filtering system, and user-friendly admin interface, this system became a powerful and scalable backbone for the job portal.
This job listings CPT system demonstrates how WordPress can be extended beyond its default capabilities to create sophisticated, scalable solutions for specific business needs.