Permalinks, custom post types and taxonomies, conflicts

Around the various forums, I see a number of concerned folk struggling with permalinks.  There is a variety of advice, most of which is about flushing your permalinks (goto settings > permalinks).  This does not always work and sometimes there are weird clashes or conflicts.  There is an important point that is not visibly made and perhaps not obvious to many:

Unique permalink URLS

However you setup your permalinks, custom post types and taxonomies, you need to ensure that the resulting permalink urls will be unique so that the system can identify the post or custom post content correctly and the url’s generated do not conflict.  Else the first rule to fire will determine what is returned.

I first encountered this problem way before post types and taxonomies when a client had an image named the same as a post, with some weird results in certain situations – took me a while to figure that out!

To demonstrate the problem, I

  1. defined my permalink structure using /%category%/%postname%/
  2. created a category called event and
  3. activated my event plugin that has a ‘event’ custom post type.
  4. I then created two posts called the same “CPT 99999″

A standard post in a category called event

When I click down from the standard post category archive instead of getting the standard post, I get the custom post type event with the same name.  There are two rules leading to/from the same url.  The first will win.

Clicking down on the standard post takes me to the custom post type event

Solutions

1. for new systems, or where you can still change the permalink structure:

Make sure that you have something that will guarantee uniqueness in your permalink structure.  Since I usually do not feel the need to have the category name in my permalinks, I use /%post_id%/%postname%/ which will always give me a unique url ! – and since my audience is mostly technical, I am not concerned about the post id in the url.

2. for existing systems with many posts

Essentially you must ensure that each url somehow generates a unique url – this may mean changing some slugs somewhere (posts, custom, taxonomies or categories)

Consider also reversed words

I had the sense in my taxonomy code for the amr-events plugin to check for wordpress “reserved” words and prevent prevent creation of a taxonomy that uses them.  This sort of  checking may need to go further, although as pointed out  here in response to this question, it is not much different from the risk of plugin function names or input form names overlapping with reserved words.  Plugin, theme authors and web developers need  to bear these risks in mind.

More information:

Key points extracted from above:

  • Custom Post Types have a URL of /slug/postname.   The “with_front” option lets you make that into /something/slug/postname, if you happen to have a post permalink structure with a prefix on it (like /text/%postname% or something)
  • Don’t setup a permalink structure that will clash with the archive structure. e.g. say you only had one post a day and wanted to use: %year%%monthnum%%day%, links so generated will be interpreted as the archive of all posts for that day.
  • Add taxonomies slowly, one by one – verify that permalinks continue to work each step of the way.
  • For performance reasons, it is not a good idea to start your permalink structure with the category, tag, author, or postname fields
  • Google looks at the words in the URLs of your posts and uses them as factor in determining relevance, and therefore he (Matt) advises using %postname% in your WordPress permalinks. He doesn’t advise using ONLY %postname%.   There’s nothing wrong with using postname. Just use something else at the beginning of the string.
  • There is a LOT of benefit of having postname in url if you are using adsense. Try this: Take any new domain timbuktu.com and if you have a post for which the url is timbuktu.com?p=7 you will get junk ads.   Now try timbuktu.com/car-insurance-save-on-car-insurance – You will get beautiful insurance ads.