NOTE: I’ve moved this into its own project page, where you can now find the download link and leave comments. This post will no longer be updated and comments are now off.
WordPress 3.0 is almost ready for prime-time, and with it full support for custom post types. You can read about the technical details of adding a custom post type, or why you should use them. The WP team have done a great job with bringing this functionality into the upcoming 3.0 release, but one thing that’s puzzled me is (current) lack of good support for 1) custom URLs for that particular post type only (something like http://yourdomain.com/movies ) and 2) custom templates for that post type. (I’ll be using this to move portfolio items to a “portfolio” post type.)
But, that’s nothing a little code can’t fix. With the helper class I’ve made, the following is possible:
- Custom URLs for a landing page for your post type, with full pagination & feed support. (eg http://yourdomain.com/movies/, http://yourdomain.com/movies/page/2/, http://yourdomain.com/movies/feed/ )
- Custom landing page templates: if you registered “movie” as your post type, you can use
movie/index.php
ormovie.php
in your theme directory (falls back toindex.php
if they don’t exist) - Custom single page templates: WP already looks for
single-movie.php
(and falls back tosingle.php
). This function allows you to usemovie/single.php
— great alongsidemovie/index.php
for better theme organization. - adds classes to
body_class()
andpost_class()
for that post type.
Usage
After including the SD_Register_Post_Type
class and the helper function sd_register_post_type()
, all you need in your functions.php file (or wherever you choose to run this) is the following line:
sd_register_post_type( 'movie' );
That’s it. Call it again with a new argument for another post type. Repeat as many times as you need post types. I’ve set good default arguments to pass to register_post_type()
, but you can override them with your own. (Read more about the $args here, here and here.)
sd_register_post_type( 'movie', $args_array );
Also, grammar pedants know that adding an “s” suffix is not appropriate for all plurals. In the case of post type “movie”, our URL landing structure is http://yourdomain.com/movies, which works fine. But, for irregular plurals, the function accepts an optional third argument:
sd_register_post_type( 'person', $args_array, 'people' );
The above URL structure would then be http://yourdomain.com/people for the post_type “person”.
One note that’s very, very important: your custom URLs won’t work until you go to Options → Permalink in wp-admin and re-save your current URL structure. This will flush WP’s current URL structure and add our new rewrite rules. This is computationally expensive and you don’t want it happening every time, which is why I’m leaving it as a manual operation. You’ll only need to do it once (or after changing the third plural argument).
42 Comments
Really great, thanks, I have been following custom post types with excitement and have read a few of the posts made so far, but yours dug-in a little deeper. Great.
I have a question about feeds. Am I missing something, or do we have to do some extra work to get feeds working for custom post types?
@Paul Feeds are a good idea. Didn’t even think about it. I’ll have to put that in.
That’d be amazing, I’d love to see it. From what I’ve read, the feeds are neither generated for the custom post type, nor are they included in your sites’ main feed. That’s a shame, as both are important options in my opinion.
Paul
@Paul Feeds are in there now. This still won’t include your custom post type in the main feed, but I’m sure someone out there already has a solution for that.
Great, looks good.
There’s not a lot of info on getting the custom post types into the main feed, but I did find this one: http://justintadlock.com/archives/2010/02/02/showing-custom-post-types-on-your-home-blog-page
I’m pretty sure most of this is already in core — endpoints, body/post classes, and single-$post_type.php templates.
@Nacin Thanks for stopping by. single-$post_type.php template are supported, as are post classes. I’ll remove those as they’re redundant.
However, I double-checked and there’s definitely no current way to set a permalink structure for a particular post_type, and therefore also no way to set specific templates for those.
Some of the endpoint/rewrite stuff is indeed unfinished. I’d like to see enhancements to endpoint support however: http://core.trac.wordpress.org/ticket/12779.
I’m glad you’ve warned that generating rewrite rules are expensive. They’re best left to an activation hook for sure.
@Nacin It’d definitely be great if the rewrite stuff was brought into core. I’m afraid that the endponit stuff makes my eyes glaze over. 🙂
I stumbled here via google. The rewrite rules for Custom Posts have got me in a frenzy today. Have you looked into how you would add the category (i.e. custom taxonomy) into the url?
(Really impressive work, by the way)
@Lane Custom taxonomies already have good rewrite support by default.
I’ve downloaded the helper but how do I include it? I have pasted it into the top of the functions.php and then at the bottom, below the post_type definition I have included the sd_register_post_type( ‘typename’ );
What do I need to have in the permalinks structure?
Without using the helper, leaving the permalink structure on default the website works perfect. But if I change the permalinks to my standard “/%category%/%postname” I get page not found!
Thanks 🙂
@Tom including it in functions.php the way you are should work fine.
The permalink structure for the single custom post itself is already baked into WP (my class has nothing to do with that part). It doesn’t, however, pay attention to your permalink settings — it’ll only produce http://yourdomain.com/posttype/slug-goes-here/ permalinks. This is because there’s no guarantee that your custom post type will even have a category, unlike normal posts. You can probably change this by setting proper $args which my helper function passes to
register_post_type()
, but I haven’t looked into it.Thanks for the reply Matt. Without the helper the single post pages work fine http://yourdomain.com/posttype/slug, with any permalinks structure.
I only run into a problem when I want to go to http://yourdomain.com/posttype/ which would be a listing page for all posts within that post type. That works fine with Default permalink structure but as soon as I change it to a custom one, I get “Not Found” regardless of including your helper or not.
Is this a separate problem that needs looking into as I’m yet to find a working solution.
Hi there … and thanks so much for your work on this issue. You are one of only 2 people that I have been able to find over the last uhm 4 months that has tackled anything to do with the rewrites on custom post types for an archival type template. That said, I am curious if you would consider making a version of your helper class that deals only with the permalink rewrites and template redirects.
To explain:
There are now some very good GUIs for registering and managing your custom post types. I went from doing it myself in functions to using CMS Press, which is very well written and provides a great interface so that you don’t have to have pages and pages of code in your functions if your using a lot of … I digress.
Anywho, using myself as an example, I’ve already gone two … maybe ten … rounds with registering and setting up my custom post types. I’ve even made templates that query all my custom post types for lack of being able to find this info previously, and because regular expressions and rewrite rules make my head spin.
Your code is beautifully done and handles exactly what is, I think, the missing link in everyone else’s code/explanation/plugin.
Problem is, it handles too much you see? I can’t re-register my post types at this stage in the game, and I’m not sure how to dissect your code to remove what’s redundant. I don’t want to be “experimental” with it.
Only other option is to just do my own rewrites from scratch in functions, but, I would have to do each post type manually, and that wouldn’t be anywhere near as clever as your code 😉
What do you say?
Reading through this, it seems it will do everything that I want it to!!
Trying it though, doesn’t do what I expect. I have included your code and registered a post type and all works fine.
I created new one called ‘persons’ using:
sd_register_post_type(‘person’);
which works fine. Sooo simple to do. Thank you! I have a custom permalink of:
/%category%/%postname%/
and added a new post in ‘person’ called ‘test’ gives me URL of:
mywebsite.com/person/test/
Great, also loads up the custom template from /person/single.php. However, going to:
mywebsite.com/person/
Gives me a 404, should that give me a ‘landing’ page calling in /person/index.php.
Am I not understanding this right?
Thanks for your help Matt, I finally solved what I was trying to do and I didn’t have to use your helper in the end.
Gurjeet — I was trying to do the exact same thing, having a listing page for all posts within the custom post type, and the single custom post type page.
Create the custom post types, then create your permalinks, then add data. If you start to add data and then change the custom post types the database doesnt like it. I had to clear out my database and now everything works perfectly without the need of the sd_ helper!
Thanks Tom,
Have got a test install of wordpress so I just cleared the database.
Functions file already sets up custom post type of ‘person’ so that bit is done. Went in and updated permalinks. Then went and added content into ‘person’ custom post type.
When viewing, the actual page works (/person/test/) but /person/ still gives 404. Tried updating permalinks again and no joy.
What steps did you take to get it to work Tom?
Some more documentation on this would be amazing because it says it will do all I need — If only I could get it to work lol.
@10SexyApples Just comment out or remove this line:
add_action( 'init', array($this, 'register_post_type'));
@Gurjeet I don’t know what Tom’s talking about regarding clearing the database, but I spell out where the custom post landing page will occur above. If you use
sd_register_post_type("person")
(and then go refresh your permalinks as I explain above), your landing page for the “person” post type will be at http://yourdomain.com/persons/ <– note the plural. I explain what I’m doing with plurals above, and I also note that there’s a way to explicitly override this with a third argument.hahahah. Thanks so much Tom for the quick response. I feel like an idiot for not knowing that all I needed to remove was the action. Well, live and learn right?
Quick helper for the others here struggling with this.
After you have included Tom’s helper functions, and have added the appropriate functions to register your post types with his helper class –>
If you intend to have an archive type page just for your custom post types and have created the $custom_post_type_name.php file, you still need to run a query like:
•query_posts(‘post_type=$custom_post_type&posts_per_page=-1’)•
prior to your loop in your $custom_post_type_name.php file to bring in the posts. They won’t just automatically show up there. You will get a 404 not found without this.
I think this is where a couple people are getting confused, so, thought I would mention it.
@10SexyApples First off, my name is Matt, and I wrote the class, not Tom. And secondly, that should all be unnecessary, as the helper class should be doing that all for you. Are you using WP 3.0?
Oh my goodness, I’m so sorry. My face is all red. And yes, I’m using 3.0, but, I think I have just realized what is happening, so, sorry for the attempt to be helpful, I failed miserably at that.
I am using twenty ten as a parent theme and building my theme as a child of it.
It seems that the template that is being pulled for my single-$custom_post_type.php is the 404 template from the twenty-ten theme. I’m not sure why, but, assuming it has something to do with the locate_template function as this was working fine before.
I am going to assume that that is probably the reason I was getting the 404 on my $custom_post_type.php template as well, and only got results when I put the query in there.
Any ideas?
My custom post type is “lesson”.
I removed the custom query from lesson.php and just have the default loop.
When I go to /lessons
It redirects to:
wp-content/themes/twentyten/404.php
with no posts found
When I go to a single post of custom post type “lesson” it redirects to:
wp-content/themes/cota/lesson.php
with just that single post returned.
which is where it should be redirecting for /lessons correct?
I am using CMS Press to handle management of my custom post types and have the permalinks there set up as:
%identifier%/%year%/%postname%-%post_id%.html
I have the register_post_type action commented out in your helper function as instructed.
And my normal permalink settings are:
/%year%/%postname%-%post_id%.html
Could this get any more confusing? I’m completely lost now. Does this make any sense to you? If it does, please clue me in as I’m really starting to pull my hair out 😉
@10SexyApples Sorry, but I have no idea how CMS Press is registering its custom post types, and I can’t speak to how they’d interoperate. There’s obviously some kind of incompatibility between their methodology and mine.
I see. That’s actually what I should have been more clear about in my first comment. CMS Press is only handling the permalink for the single posts from what I understand.
Can you tell me what to comment out to remove the helper function from rewriting anything to do with the single post? Perhaps that would take care of this.
I have just finished removing my theme from being a child to see if that has any effect, as it was only when I added the helper function that anything began not making it past the parent theme.
I’ll report back in a sec on that.
Hang on a sec, you realize that I am registering the custom post types via CMS Press, and not using the register_post_type action from the helper function right?
These post types were registered long ago and working perfectly with the “fake” archive and WP default single-lesson.php templates.
I’m just trying to get a proper “archive” page of custom post types, it’s the only thing that is missing, so, from what I understand I just need a rewrite of ?post_type= and a redirect to /lessons of any query var that has the post type, but, not the single name.
Man, I sure wish that discussion in trac about including this in core had taken a different turn. I really think leaving this out is going to open a can of worms for everyone who gets near it.
Great to learn from though 😉
Eliminating the child theme cleared up the redirect going to the wrong theme file 404, but, nothing else.
On to the next test
I wrote all of my custom post type args myself, and then when I decided to use CMS Press for it’s GUI et al, I actually constructed the args in that code to match the way that I wanted them to be, as well as updated them, as they were not quite up to par with what was current in trac for options in the support array.
The only thing I don’t understand is the rewrite stuff.
So, if you think it might help myself or anyone else to get this cleared up, ask away please. I’ll look up and test whatever is necessary to get this solved~
Okay, at the risk of looking like a complete maniac being the only one talking here, I will continue to document what I find here in the hopes that it will save someone else from the hours/days … months now working all of this out.
Latest development is that the guy that wrote CMS Press is also one of the main developers working on all of the custom post type functions in the core. He has a diff up on the trac right now to have WP first look for $post_type.php to see if it exists before defaulting to single-$post_type.php. It hasn’t been merged yet, so, he has written it temporarily into CMS Press.
So, if you are using CMS Press and attempting to use this helper function, your single posts are going to try to access the same template as your archive.
I would suggest leaving the functionality of having $post_type.php for singles as it will probably end up core behavior.
Matt, can we get your helper function to look for $post_types.php as the template, just like the plural on the url, instead?
Okay, finally, so changing the helper function to look for a post_types.php instead of a post_type.php template worked beautifully, and this is the last you’ll hear from me. Hopefully my ramblings will come in handy for someone. I now have working single custom post type pages as well as a custom post type archive.
Thanks Tom! … Just kidding, Thanks Matt!!!!
Thanks all for your help and Thanks Matt for this great bit of code that sorts this stuff out.
When reading through your post, I didn’t exactly get what the whole plural bit was on about but I do now 🙂 BTW, how can this be switched around so it doesn’t do plural but you can override to make it plural?
Few suggestions though please:
1) Would you be able to do an example of an ‘sd_register_post_type’ passing in args array to see how its supposed to be done?
2) You have done a great job doing this — if you could write something that could make it just as easy/easier to include custom meta boxes (First name, last name etc for persons — movie name, year for movies etc) then that would be AMAZING! Would defo be like a full custom CMS system then!!!
Thanks in advance!
Actually, just a heads up… someone has already written a Custom Meta Type system over at: http://www.deluxeblogtips.com/2010/05/howto-meta-box-wordpress.html
I’m using this and that together and I now have the ability to make WordPress my very own custom CMS.
Thanks!
Very nice approach. I love the idea of template structure. It’s great for websites that have many post types.
Your code need to be look very carefully to understand :). I like it. Thanks for sharing.
I’m a little stuck on this one.
I started by copying all the included code into my theme functions.php file.
Below that I add the following:
Problem is I am not getting albums to show up in my admin interface. If I get rid of the sd_ then it works.
Must be doing something really stupid here.
@dave The reason it’s failing is pretty simple: my class already does all the action hooks for you. Since you’ve called
sd_register_post_type()
within a function that gets called atinit
, it’s too late for the helper class to register its owninit
code.Solution: take the
sd_register_post_type()
out ofmy_custom_init1()
and put it inline. Or, if you’re attached to the idea of having your own init hook, make sure your action gets a very high priority like this:add_action('init', 'my_custom_init1', 0);
. An action without a number gets called at priority 10 by default, so 0 would make sure it ran early enough to get all of my actions hooked up.hi matt, great class.
btw, is possible to make post_type archive
eg: wp/post_type/2010/05/14/my_post
how to make the correct rule 😛
Hi
I see on a recent post on the wp-hackers list ( http://groups.google.com/group/wp-hackers/browse_thread/thread/60eba1cd65758237# ) that the way labels and capabilities are handled by register_post_type is changing.
Will you be updating your helper class to reflect this?
@Simon Yeah, it’ll be edited. The benefit here is that I’m using it myself, so it gets my attention.
@angga I’m sure it’d be possible, but it’d require more regex-fu than I have.
NOTE: I’ve moved this into its own project page, where you can now find the download link and leave comments. This post will no longer be updated and comments are now off.
2 Trackbacks