Custom post type, no need for single view, plus want permalink rewrites that include hash in URI

Hi @daxitude:

Let me first suggest you reconsider. If you don’t have individual FAQ pages for each FAQ:

  1. You reduce your surface are for search engine optimization and reduce the potential traffic that you might get, and

  2. You make it impossible for someone to share a specific FAQ with a friend over email and/or share with their network on Facebook, Twitter, etc. (As a user I’m always frustrated by site developers who disallow me to have a direct URL to an item and instead force me to link to the page that lists all items.)

However, if you still want to do so then do two things:

1.) Use the 'post_type_link' hook

Use the 'post_type_link' hook to modify the URL like in the following example *(I’m assuming your custom post type is 'faq'). Add the following to your theme’s functions.php file:

add_action('post_type_link','yoursite_post_type_link',10,2);
function yoursite_post_type_link($link,$post) {
  $post_type="faq";
  if ($post->post_type==$post_type) {
    $link = get_post_type_archive_link($post_type) ."#{$post->post_name}";
  }
  return $link;
}

2.) unset($wp_rewrite->extra_permastructs['faq'])

This is a hack, but it’s a required hack to do what you want. Use an 'init' hook to unset($wp_rewrite->extra_permastructs['faq']). It removes the rewrite rule that register_post_type() adds. I’m including a call to register_post_type() so I can provide a complete example for both you and others:

add_action('init','yoursite_init');
function yoursite_init() {
  register_post_type('faq',array(
      'labels' => array(
      'name' => _x('FAQs', 'post type general name'),
      'singular_name' => _x('FAQ', 'post type singular name'),
      'add_new' => _x('Add New', 'faq'),
      'add_new_item' => __('Add New FAQ'),
      'edit_item' => __('Edit FAQ'),
      'new_item' => __('New FAQ'),
      'view_item' => __('View FAQ'),
      'search_items' => __('Search FAQs'),
      'not_found' =>  __('No FAQs found'),
      'not_found_in_trash' => __('No FAQs found in Trash'),
      'parent_item_colon' => '',
      'menu_name' => 'FAQs'
    ),
    'public' => true,
    'publicly_queryable' => true,
    'show_ui' => true,
    'show_in_menu' => true,
    'query_var' => true,
    'rewrite' => array('slug'=>'faqs'),
    'capability_type' => 'post',
    'has_archive' => 'faqs',
    'hierarchical' => false,
    'supports' => array('title','editor','author','thumbnail','excerpt')
  ));

  global $wp_rewrite;
  unset($wp_rewrite->extra_permastructs['faq']);  // Removed URL rewrite for specific FAQ 
  $wp_rewrite->flush_rules(); // THIS SHOULD BE DONE IN A PLUGIN ACTIVATION HOOK, NOT HERE!
}

That’s about it.

Of course the above use of $wp_rewrite->flush_rules() in an 'init' hook is really bad practice and should really only be done once so I’ve implemented a complete and self-contained plugin called FAQ_Post_Type to do it right. This plugin adds a FAQ post type with the URL rules that you want and it uses a register_activation_hook() to flush the rewrite rules; activation being obviously one of the few things that requires plugin code instead of code that can run in a theme’s functions.php file.

Here’s the code for the FAQ_Post_Type plugin; feel free to modify for your requirements:

<?php
/*
Plugin Name: FAQ Post Type
Description: Answers the question "Custom post type, no need for single view, plus want permalink rewrites that include hash in URI" on WordPress Answers.
Plugin URL: http://wordpress.stackexchange.com/questions/12762/custom-post-type-no-need-for-single-view-plus-want-permalink-rewrites-that-incl
*/
if (!class_exists('FAQ_Post_Type')) {
  class FAQ_Post_Type {
    static function on_load() {
      add_action('post_type_link', array(__CLASS__,'post_type_link'),10,2);
      add_action('init', array(__CLASS__,'init'));
    }
    static function post_type_link($link,$post) {
      if ('faq'==$post->post_type) {
        $link = get_post_type_archive_link('faq') ."#{$post->post_name}";
      }
      return $link;
    }
    static function init() {
      register_post_type('faq',array(
          'labels' => array(
          'name' => _x('FAQs', 'post type general name'),
          'singular_name' => _x('FAQ', 'post type singular name'),
          'add_new' => _x('Add New', 'faq'),
          'add_new_item' => __('Add New FAQ'),
          'edit_item' => __('Edit FAQ'),
          'new_item' => __('New FAQ'),
          'view_item' => __('View FAQ'),
          'search_items' => __('Search FAQs'),
          'not_found' =>  __('No FAQs found'),
          'not_found_in_trash' => __('No FAQs found in Trash'),
          'parent_item_colon' => '',
          'menu_name' => 'FAQs'
        ),
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'rewrite' => array('slug'=>'faqs'),
        'capability_type' => 'post',
        'has_archive' => 'faqs',
        'hierarchical' => false,
        'supports' => array('title','editor','author','thumbnail','excerpt'),
      ));
      global $wp_rewrite;
      unset($wp_rewrite->extra_permastructs['faq']);  // Remove URL rewrite for specific FAQ
    }
    static function activate() {
      global $wp_rewrite;
      $wp_rewrite->flush_rules();
    }
  }
  FAQ_Post_Type::on_load();
  register_activation_hook(__FILE__,array('FAQ_Post_Type','activate'));
}

You could also possibly kept the flush rules inside the 'init' by using a check for an option value if you prefer this:

// Add this code in your 'init' hook at your register_post_type('faq',...)
if (!get_option('faq_rewrite_rules_updated')) {
  global $wp_rewrite;
  unset($wp_rewrite->extra_permastructs['faq']);  // Remove URL rewrite for specific FAQ
  $wp_rewrite->flush_rules();
  update_option('faq_rewrite_rules_updated',true);
}

Your choice.

Anyway, let me know if there are use-cases you discover that this does not address.

Leave a Comment