Downloadable iCalendar Files
Tags: expressionengine, eecms
Published: 2019-02-13 15:11:00

---
title: Downloadable iCalendar Files
published: true
date: 2019-02-13 15:11:00 UTC
tags: ExpressionEngine,eecms
canonical_url: https://u.expressionengine.com/article/downloadable-icalendar-files
cover_image: https://u.expressionengine.com/asset/article-images/downloadable-icalender-files.jpg
---
This tutorial will show you how to create downloadable iCalendar files. With ExpressionEngine, it’s easy-peasy because you can use your well-structured content however you need. You can use the same entries you use for your web site to create shareable `.ics` iCalendar format files. Here’s how in three easy steps.

1. Install the “Download Content” plugin.
2. Create your iCalendar format template.
3. Build a link to your iCalendar template.

## Install the free Download Content plugin

In order to make your ExpressionEngine-generated `.ics` files be treated like a downloadable file, grab and install the free Download Content plugin.

- [Download the plugin from GitHub.](https://api.github.com/repos/EllisLab/Download-Content/zipball/3.0.1)
- Unzip and upload the enclosed download_content folder to `/system/user/addons`.
- Visit the Add-on Manager in your control panel, and click Install for the “Download Content” plugin.

## Create your download template

Now we need to create an endpoint that we will link to that allows our visitors to download our `.ics` file. In this example, I’m creating an iCalendar file for a free concert the local brewpub is putting on. I have created a template named `add-to-my-calendar` inside my `concerts` template group.

It uses the entry date and expiration date of the concert, the title, and a custom field called `meta_description`. Side note: `meta_description` is part of my standard set of SEO fields that I add to every channel. Repurposing it for the description of the calendar event makes sense. I’m also going to take advantage of a few iCalendar fields to setup a default alarm two hours before the event.

```
{exp:channel:entries channel='concerts' limit='1' show_future_entries='yes' require_entry='yes'}
	{if no_results}
		{redirect='404'}
	{/if}

{exp:download_content filename='{url_title}.ics'}
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:{url_title}-{entry_date format='%Y%m%d'}@{site_name:url_slug}
DTSTART;TZID={entry_date format='%e:%Y%m%dT%H%i%s'}
DTEND;TZID={expiration_date format='%e:%Y%m%dT%H%i%s'}
SUMMARY:{title}
X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
LOCATION:The BrewPub\n62950 NE 18th St\nBend\, OR 97701
BEGIN:VALARM
X-WR-ALARMUID:{url_title}-{entry_date format='%Y%m%d'}@{site_name:url_slug}
TRIGGER:-PT120M
DESCRIPTION:{meta_description}
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
{/exp:download_content}

{/exp:channel:entries}
```

Let’s break down the tags:

```
{exp:channel:entries channel='concerts' show_future_entries='yes' limit='1' require_entry='yes'}
```

- `show_future_entries=` lets us use the Entry Date as the start time of the event. Since we want people to see events that haven’t happened yet, we tell the Channel Entries tag to show future events.
- `limit='1' require_entry='yes'` is a common design pattern for single-entry templates. This makes sure we only show one entry, and we don’t want it to display anything if the URL title is invalid. We combine it with:

```
{if no_results}
    {redirect='404'}
{/if}
```

If there are no results—an invalid URL, an entry that’s already expired, a closed status, etc.—we will have proper 404 behavior.

The next tag is the Download Content plugin we installed earlier.

    {exp:download_content filename='{url_title}.ics'}
    
Using the entry’s URL title for the filename isn’t a requirement, but it will help the visitor identify the file’s purpose after downloading. It needs an `.ics` extension so desktop and mobile calendar apps know that the file is for them. This plugin makes sure the output only contains what’s inside the plugin’s tag, and gets treated as a download instead of displaying it on the screen.

**Note:** iCalendar files are picky about whitespace. This is why the code is not indented like we would normally encourage.

The rest of the contents are made from standard iCalendar file format attributes. You can read more about that file format and its attributes here: [iCalendar file format](https://en.wikipedia.org/wiki/ICalendar). Here are some notes to help dissect our template decisions:

- `UID:` Providing a UID allows the calendar app to change the existing event if it is updated. Otherwise adding a new version will create a new event. The URL title, date, and site name should give us a unique ID. The `:url_slug` variable modifier replaces spaces with slashes and so forth so we can use the site’s name here.
- `DTSTART/DTEND:` The format parameter used here (`%e:%Y%m%dT%H%i%s`) will output: `America/Los_Angeles:20180827T105300`
- We use the `entry_date` for the start date and the expiration_date for the end date.
- `SUMMARY` uses the entry’s title for the event’s name
- Some calendar apps may not use the DESCRIPTION, but we add it for those that do. - Likewise, we add `DTSTAMP` because Outlook demands it.
- `TRIGGER:-PT120M` sets an alarm for 120 minutes (2 hours) before the event
- `X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC` sets an alarm for Apple clients when the person needs to leave their current location to make it to the event on time.

## Create a download link

Now all we need to do is link to our `.ics` file and we’re good to go! From my concert listing page, I’ll add:

    <a href="{url_title_path='concerts/add-to-my-calendar'}">Add this concert to my calendar!</a>
    
Now when I click the link, it downloads a properly formatted `.ics` file that I can add to my calendar!

<img src="https://u.expressionengine.com/asset/article-images/sample-calendar-event.png" alt="">

With this knowledge you can create events, todos, journal entries, and any other kind of iCalendar file you can imagine. Use these same principles to output your content in other file formats, too; the sky’s the limit! With ExpressionEngine, you never have to work hard to make your content work hard for you.