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

We are using CPT’s to manage a frequently asked questions page on a site, where the question is the post title and the answer is the post content. There is a main page for the FAQs that shows all posts (FAQ archive page). With this structure we really have no need for the single view for any FAQ and in fact would like to omit it from the site structure. To address permalinks we’d like to set them to be something like example.com/faq/#uniqueIdentifier, thinking that we’ll use the #uniqueIdentifier to match a div on the archive page containing the answer and call attention to it in some fashion. The uniqueIdentifier could be post ID, faq question title, data from a meta box, or something else.

So let me recap what I need we need to accomplish:

(1) rewrite the faq permalinks to be /faq/#something, and

(2) make sure all /faq/ links route to archive template and not single

I’m mostly a noob but pretty good at fumbling my way through things. Have never attempted any rewrites though so would appreciate some particular direction on that.

Thank you.

Solutions Collecting From Web of "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.