Give your Events Manager events an Add to Calendar link

The Events Manager plugin for WordPress lets you record and display your upcoming events, and is highly extensible through hooks and templates. Here’s how to give your single event page an Add to Calendar link that lets your visitors copy your events into Microsoft Outlook, Thunderbird, and any other iCalendar compliant calendar.

Edit: apologies to anyone who tried this code and got errors. I had copied the code from a class and forgotten to remove “public” from the function declarations; now fixed.

Edit: sometimes I need a smack upside the head; Events Manager has a placeholder for that: #_EVENTICALLINK; but at least mine handles recurring events and lets you customise the link text :) … anyway:

Add to Calendar links are basically links to very simple files in iCalendar format, generally with a .ics extension. To get something like that out of WordPress, the simplest way is to use the AJAX interface, which lets you bypass all the usual page/post stuff and get straight to your custom code. The page at that link describes how to do AJAX in WordPress, so lets focus on just the iCalendar stuff here.

To add a link to your event page, you need to either customise the event-single template or add a custom placeholder. We’ll use the custom placeholder, because it’s less work since we’re already writing PHP hooks for iCalendar. We’ll call our placeholder #_MYCALENDARLINK. Adding that to the Single Events Page format will give us our iCalendar link once we’re done.

* add some output placeholders for Events Manager
* @param string $result
* @param EM_Event $EM_Event
* @param string $placeholder
* @return string
function filterEventOutputPlaceholder($result, $EM_Event, $placeholder) {
    if ($placeholder == '#_MYCALENDARLINK') {
        // link for downloading the calendar entry into a local calendar
        // the nc parameter is to prevent caching of the result
        // so that the most up-to-date event data is always sent
        $url = admin_url('admin-ajax.php')
            . "?action=my-event-ics&id={$EM_Event->event_id}&nc=" . time();
        $result = "<p><a href='$url'>Click to add to your calendar</a></p>n";

    return $result;

add_filter('em_event_output_placeholder', 'filterEventOutputPlaceholder', 10, 3);

Now we have to make the link do something. We need to add two AJAX hooks so that the AJAX request is handled whether or not the visitor is a logged in user. We then need to build up the iCalendar data, and send it in the required format and with the required headers. To help debug things, I often add an extra parameter so that I can get the data displayed as plain text on my browser, but you can omit that if you want.

One thing of note is that this code is written for version 5.1+ of Events Manager; prior to this, the EM_Event object had different names for its members.

* generate an iCalendar .ics file for event
function ajaxEventICS() {
    $event_id = preg_replace('/D/', '', $_REQUEST['id']);
    if (!$event_id)

    $EM_Event = new EM_Event($event_id);
    if (!is_object($EM_Event))

    // if plaintext is requested (for debugging), set content type to text
    // otherwise set to calendar and tell browser to download the file
    if ($_REQUEST['plaintext'] == 1) {
        header('Content-type: text/plain; charset=utf-8');
    else {
        header('Content-type: text/calendar; charset=utf-8');
        header('Content-Disposition: inline; filename="event.ics"');

    // character conversion arrays
    $charFrom = array('', ';', ',', "n", "t");
    $charTo = array('\', ';', ',', 'n', 't');

    // build array of iCalendar lines
    $ics = array();
    $ics[] = 'BEGIN:VCALENDAR';
    $ics[] = 'VERSION:2.0';
    $ics[] = 'METHOD:PUBLISH';
    $ics[] = 'CALSCALE:GREGORIAN';
    $ics[] = 'PRODID:-//Events Manager//1.0//EN';
    $ics[] = 'BEGIN:VEVENT';

    // manufacture a unique ID using the domain name of the website from options,
    // NOT from bloginfo (which may be filtered!)
    $ics[] = "UID:event-$event_id@" . parse_url(get_option('home'), PHP_URL_HOST);

    // use the event name as the summary
    $ics[] = 'SUMMARY:' . str_replace($charFrom, $charTo, $EM_Event->event_name);

    // add link to event post
    $ics[] = "URL:{$EM_Event->output('#_EVENTURL')}";

    // if event has a location, get the location name
    if (is_object($EM_Event->location))
        $ics[] = 'LOCATION:' . str_replace($charFrom, $charTo, $EM_Event->location->location_name);

    // get the start, end and last modified dates/times
    $gmtOffset = 60 * 60 * get_option('gmt_offset');
    $ics[] = 'DTSTART:' . date('YmdTHisZ', $EM_Event->start - $gmtOffset);
    $ics[] = 'DTEND:' . date('YmdTHisZ', $EM_Event->end - $gmtOffset);
    $ics[] = 'DTSTAMP:' . date('YmdTHisZ', strtotime($EM_Event->event_modified) - $gmtOffset);

    // get categories as comma-separated list
    $categories = array();
    $cats = $EM_Event->get_categories()->categories;
    foreach ($cats as $cat) {
        $categories[] = str_replace($charFrom, $charTo, $cat->output('#_CATEGORYNAME'));
    if (count($categories) > 0)
        $ics[] = 'CATEGORIES:' . implode(',', $categories);

    // get recurring events in iCalendar format
    $recurrence = '';
    if (!$EM_Event->is_individual()) {
        $recurrence = $EM_Event->get_event_recurrence();
        if (!empty($recurrence)) {
            $days = array('SU','MO','TU','WE','TH','FR','SA');
            $until = date('YmdTHisZ', $recurrence->end - $gmtOffset);

            switch ($recurrence->freq) {
                case 'daily':
                    $ics[] = "RRULE:FREQ=DAILY;INTERVAL={$recurrence->interval};UNTIL=$until";

                case 'weekly':
                    $bydays = explode(',', $recurrence->byday);
                    $BYDAY = array();
                    foreach ($bydays as $day) {
                        $BYDAY[] = $days[$day];
                    $BYDAY = implode(',', $BYDAY);
                    $ics[] = "RRULE:FREQ=WEEKLY;BYDAY=$BYDAY;INTERVAL={$recurrence->interval};UNTIL=$until";

                case 'monthly':
                    $ics[] = "RRULE:FREQ=MONTHLY;BYDAY={$recurrence->byweekno}{$days[$recurrence->byday]};INTERVAL={$recurrence->interval};UNTIL=$until";

    // lines to close iCalendar
    $ics[] = 'END:VEVENT';
    $ics[] = 'END:VCALENDAR';

    // output lines wrapped to 75 characters per RFC-5545
    foreach ($ics as $line) {
        if (strpos($line, ' ') === FALSE) {
            // cut unspaced string
            echo wordwrap("$linen", 75, "nt", TRUE);
        else {
            // preserve space where word was wrapped
            echo wordwrap("$linen", 75, "nt ", TRUE);


add_action('wp_ajax_my-event-ics', 'ajaxEventICS');
add_action('wp_ajax_nopriv_my-event-ics', 'ajaxEventICS');

There, job is done and recorded in my Google Calendar.

  • Marcus

    At worst, great example as to how easy it is to hook into EM and do what you want :)

    • G’day Marcus, yeah… oh well. I guess it does go a little further than the placeholder, like recurring events and a custom label, but I probably could have convinced the client to accept what the placeholder does if I’d noticed it earlier.

      You might want to pinch the recurring events bit for the placeholder code though, and perhaps also the manufactured UID. Your UID changes every time, which it isn’t supposed to do. A fixed UID allows a calendar entry to be updated instead of creating a new entry. So please pinch this code :)

  • Eynat

    I am new at PHP & WP so I didn’t understand where should I place the first code and where should I place the second one. Please support!
    Thanks in advance this looks very handy!

    • I’d say the function.php within your themes theme folder.

    • G’day Eynat, Tony is correct: drop this into your functions.php or create a new plugin for it.

      If you’re new at PHP & WP, it might be easier for you to just use the placeholder that Events Manager has for this: #_EVENTICALLINK

      • Eynat

        I sa in previous posts that people are referring to I don’t have that. how can I get it?

        • There is no “file” events.ics, but if you add events.ics onto the end of your website home page URL, the Events Manager plugin will give you an ICS file listing your events. e.g.

          (NB: it should download the file and execute it with the default calendar application, but Marcus hasn’t configured that demo server correctly so it just shows as HTML in Chrome at least)

          • Eynat

            Thank you for all your help! I understood that it’s not a file but when entering the URL I get- ERROR 404 – PAGE NOT FOUND
            Thanks ( a lot!!)

  • Eynat

    Thanks for your reply but I still didn’t get it. Both filterEventOutputPlaceholder and ajaxEventICS() in functions.php? Second step is to call this functions out of event single?

    • My apologies, I had copied the code from a class and forgotten to remove “public” from the function declarations; now fixed. If you remove “public” from the function declarations in your copy, it should work.

    • And yes, both chunks of code can be pasted into the bottom of your functions.php file in your active theme.

  • @Eynat: Are you actually using the Events Manager plugin, or another similarly named one? This is the plugin that this post refers to:

    You might be using a different plugin, and if so, the discussion above simply does not apply!

    • Eynat

      This is the one I am using (by Marcus). I found several posts that people had the same problem- no resolution.
      When I am adding an ICALLINK to the mails I am sending at thye settings I don’t get any event added to my outlook calendar I am just being passed to the event page back

      • G’day Eynat,

        Sounds like you have a problem that needs support from the authors. Please create a new post on the Events Manager support forum. They’re pretty good over there.

  • I’m looking for someone to do some customizations in our event manager application…shouldnt be tough for someone that knows the program. Need it done soon and willing to pay….contact us asap!

    info at ohio mobile gaming dot com