How-to: iCal with Custom Post Types

In line with my previous custom post type tutorials for events (backend & frontend), I’d like to show how easy it is to create an iCal feed of those events (download the full file here). There are a few iCal for WordPress plug-ins out there, but none that will handle custom post types right off the bat. Besides, you’ll be setup in no time.

1) iCal requirements

iCal is Apple’s calendar application, but over time has gained widespread support & functionality in other tools (MS Outlook, GMail, Yahoo, etc.) so depending on your type of crowd, this sort of feature can really come in handy (think how easy it would be to grab and store dates to your mobile phone directly from WordPress).

2) ical.php

As is good practice, this sort of stand-alone functionality should be in its own file. The goal of this document will be to 1) create a function that constructs the output, and 2) registering that output as a feed. Let’s get right into it:

3) header information


function tf_events_ical() {

// - start collecting output -

// - file header -
header('Content-type: text/calendar');
header('Content-Disposition: attachment; filename="ical.ics"');

// - content header -
PRODID:-//<?php the_title(); ?>//NONSGML Events //EN
X-WR-CALNAME:<?php the_title(); _e(' - Events','themeforce'); ?>
X-ORIGINAL-URL:<?php echo the_permalink(); ?>
X-WR-CALDESC:<?php the_title(); _e(' - Events','themeforce'); ?>


As you may have seen in the previous tutorials, we’re using ob_start() to collect all outputted content after that point. We then tell the function that the actual document header should contain certain parameters that will 1) help it be identified as a calendar & 2) actually output as a downloadable .ics file when the feed is called upon. This is the right way about it as the end-user will be prompted for a download. They’ll then accept and open the file in a calendar application of their choice (which will import the events). Otherwise the user would just see a text feed that is unusable in this context.

Next up, we pump out some content header information. This stuff is essentially what meta information is to html (i.e. description, keywords, author), although iCal carries different information. I was lucky enough to stumble upon this site which had plenty of information on the subject.

4) events listing

At this point, we’re ready to list each event item in the correct iCal format. Before we create those items though, we’ll need to run a query to pull all the data in (and as you may recall, this looks just like the one in my previous tutorial):


// - grab date barrier -
$today6am = strtotime('today 6:00') + ( get_option( 'gmt_offset' ) * 3600 );
$limit = get_option('pubforce_rss_limit');

// - query -
global $wpdb;
$querystr = "
    SELECT *
    FROM $wpdb->posts wposts, $wpdb->postmeta metastart, $wpdb->postmeta metaend
    WHERE (wposts.ID = metastart.post_id AND wposts.ID = metaend.post_id)
    AND (metaend.meta_key = 'tf_events_enddate' AND metaend.meta_value > $today6am )
    AND metastart.meta_key = 'tf_events_enddate'
    AND wposts.post_type = 'tf_events'
    AND wposts.post_status = 'publish'
    ORDER BY metastart.meta_value ASC LIMIT $limit

$events = $wpdb->get_results($querystr, OBJECT);

// - loop -
if ($events):
global $post;
foreach ($events as $post):

My 6 am time limit is once again in place to only show relevant events (i.e. nothing that has already passed). The rest of the query is quite straightforward, but we’ll need to do one last thing before returning the individual events. iCal requires a special date format, especially in our case where we’ll be appending the character “Z” (that tells the importing calendar that the date will be set to the GMT+0 timezone). For example, 20100130T134500Z, is the 30th of January, 2010 – 1:45pm GMT+0. Appending the Z is good practice, especially if you only have a single person in another timezone.

// - custom variables -
$custom = get_post_custom(get_the_ID());
$sd = $custom["tf_events_startdate"][0];
$ed = $custom["tf_events_enddate"][0];

// - grab gmt for start -
$gmts = date('Y-m-d H:i:s', $sd);
$gmts = get_gmt_from_date($gmts); // this function requires Y-m-d H:i:s
$gmts = strtotime($gmts);

// - grab gmt for end -
$gmte = date('Y-m-d H:i:s', $ed);
$gmte = get_gmt_from_date($gmte); // this function requires Y-m-d H:i:s
$gmte = strtotime($gmte);

// - Set to UTC ICAL FORMAT -
$stime = date('Ymd\THis\Z', $gmts);
$etime = date('Ymd\THis\Z', $gmte);

Pfew, seems like a lot, but it should all make sense. We’re finally ready to output each event which will carry the following format:

// - item output -
DTSTART:<?php echo $stime; ?>
DTEND:<?php echo $etime; ?>
SUMMARY:<?php echo the_title(); ?>
DESCRIPTION:<?php the_excerpt_rss('', TRUE, '', 50); ?>
else :

That wasn’t that hard was it? Our loop completes here and all the content we need to generate is complete.

5) closing off

We then need to turn off output buffering and package it into a single variable that is then echoed. Finally, we register this content as a feed (‘add_feed‘) and add this action once WordPress has completed loading (‘init‘)

// - full output -
$tfeventsical = ob_get_contents();
echo $tfeventsical;

function add_tf_events_ical_feed () {
    // - add it to WP RSS feeds -
    add_feed('tf-events-ical', 'tf_events_ical');


At this point, we’re done and your feed (or rather downloadable file) is accessed through

your turn to speak

Can an iCal feed help your site or do you already use some form of it? Are there any enhancements you’d add, or code you’d write differently? Feel free to share anything you want, always great to get different opinions!