# Live Events export for Google Calendar

## 1. Introduction

Advanced Views excels at displaying content. In most cases, this means presenting queried content in the browser as HTML.

However, Advanced Views is designed with **format-agnostic** Layouts and Post Selections. This means you can output your content in virtually any text format, including:

* XML
* CSV
* JSON

This flexibility allows you to provide **native data formats for external tools**, including calendar applications.

## 2. Task definition

Let’s review a practical example.

Suppose you have an **Events** CPT (Custom Post Type) with two ACF date fields:

* `Start_Date`
* `End_Date`

Both fields are assigned to the Events CPT.

Our goal is to create a **link that allows visitors to download and import events into their calendar**.

Since this content is date-related, the exported file should include **only upcoming events**, respecting the current date.

Creating such a file manually would be inefficient. It would require:

* constant copy-paste work
* manual updates whenever events change

Instead, we will generate the file **dynamically using Advanced Views**.

## 3. Implementation overview

We will define two components:

**"Event" Layout**

Responsible for rendering a **single event entry** inside the exported file.

**"Events" Post Selection**

Responsible for:

* querying the Events CPT
* assembling all event entries into a file

Unlike typical Post Selections, we won't **embed the Post Selection shortcode into a page**. Instead, we will use the [Advanced Views Ajax integration](https://wplake.gitbook.io/advanced-views/templates/custom-ajax-and-rest-api-pro).

This integration gives us a **unique URL tied to the Post Selection**, which can be placed anywhere on your site. For example, you can attach this URL to a download link.

{% hint style="info" %}
**Pro tip:**\
Visitors can add this URL in their preferred **Calendar** app using the **Import** (one time import, no event updates) or **From URL** (live event updates).
{% endhint %}

## 4. Implementation

Most calendar applications accept the standard ics format, where Google Calendar [supports two import formats](https://support.google.com/calendar/answer/37118):

* `.ics`
* `.csv`

Because the automatically generated Layout and Post Selection templates are **HTML-based**, we need to customize them to output the correct formats. Below, are examples for both formats.

### 4.1) Layout

1. Create a new "**Event" Layout**
2. In the **Fields** tab, assign the fields:
   * ACF field **Start Date** (date\_picker)
   * ACF field **End Date** (date\_picker)
   * Post **Title Field**.
   * Post **Content**.
3. Click Publish to **Save the layout.**

The default template will now be generated.&#x20;

Switch to the **Template** tab.

You will see something similar to this:

```twig
<acf-view-69a3ee48f2a64 class="{{ _view.classes }}acf-view acf-view--id--{{ _view.id }} acf-view--object-id--{{ _view.object_id }}">

	{% if post_title.value %}
		<p class="acf-view__post-title">
			{{ post_title.value }}
		</p>
	{% endif %}


	{% if start_date.value %}
		<p class="acf-view__start-date">
			{{ start_date.value }}
		</p>
	{% endif %}


	{% if end_date.value %}
		<p class="acf-view__end-date">
			{{ end_date.value }}
		</p>
	{% endif %}


	{% if post_content.value %}
		<div class="acf-view__post-content">
			{{ post_content.value|raw }}
		</div>
	{% endif %}

</acf-view-69a3ee48f2a64>
```

Next, we need to modify the default template to match our export format.&#x20;

#### A. ICS format

{% hint style="warning" icon="screwdriver-wrench" %}
Use **either** the ICS format **or** the CSV format — do **not** use both simultaneously.
{% endhint %}

The ICS type expects the "*FIELD: value*" format.&#x20;

Example Twig template:

```twig
BEGIN:VEVENT
<br>
UID:event-{{ _view.object_id }}@YOUR_NAME
<br>
DTSTAMP:{{ "now"|date("Ymd\\THis\\Z") }}
<br>
DTSTART:{{ start_date.timestamp|date("Ymd\\THis\\Z") }}
<br>
DTEND:{{ end_date.timestamp|date("Ymd\\THis\\Z") }}
<br>
SUMMARY:{{ post_title.value }}
<br>
DESCRIPTION:{{ post_content.value|striptags|replace({"\n":"\\n"}) }}
<br>
END:VEVENT
```

Don't forget to replace `YOUR_NAME` with your unique name and ensure your field names match.

#### B. CSV format

For CSV, values are separated by commas. Example Twig template:

{% code overflow="wrap" %}

```twig
{{ post_title.value }},{{ start_date.timestamp|date("m/d/Y") }},{{ end_date.timestamp|date("m/d/Y") }},{{ post_content.value|striptags|replace({"\n":"\\n"}) }}
```

{% endcode %}

### 4.2) Post Selection template

Next create the Post Selection to sort and filter your event posts.

1. Create a new "**Events" Post Selection**
2. Set the '**Event'** Layout as the *Item Layout*
3. For Post Type select 'event' to filter by.
4. Click Publish to **Save** your post selectio&#x6E;**.**

Switch to the **Template** tab to see the generated default template.&#x20;

Example Twig template:

```twig
<acf-card-69a3ee57020ca class="{{ _card.classes }}acf-card acf-card--id--{{ _card.id }}">

	{% if _card.post_ids %}
		<div class="acf-card__items">
			{% for post_id in _card.post_ids %}
				[avf-layout id="{{ _card.view_id }}" object-id="{{ post_id }}"]
			{% endfor %}
		</div>
	{% else %}
		<div class="acf-card__no-posts-message">{{ _card.no_posts_found_message }}</div>
	{% endif %}

</acf-card-69a3ee57020ca>
```

Next, we need to replace this default template to match our export format.&#x20;

#### A. ICS format

**Copy** the below template into the **Custom Template** field. No extra editing required.

Twig example:

```twig
BEGIN:VCALENDAR
<br>
VERSION:2.0
<br>
PRODID:-//Advanced Views//Events Export//EN
<br>
CALSCALE:GREGORIAN
<br><br>
{% for post_id in _card.post_ids %}
[avf-layout id="{{ _card.view_id }}" object-id="{{ post_id }}"]
<br><br>
{% endfor %}
END:VCALENDAR
```

#### B. CSV format

**Copy** the below template into the **Custom Template** field. No extra editing required.

Twig example:

```twig
Subject,Start Date,End Date
<br>
{% for post_id in _card.post_ids %}
[avf-layout id="{{ _card.view_id }}" object-id="{{ post_id }}"]
<br>
{% endfor %}
```

See the Google Calendar documentation for the full list of [supported CSV fields](https://support.google.com/calendar/answer/37118).

### 4.3) Post Selection Ajax

The Post Selection is now ready.

However, we still need a **URL endpoint** that returns the generated file.

To achieve this, we will add an [Ajax handler](https://wplake.gitbook.io/advanced-views/query-content/custom-data-pro#custom-data-snippet) using the **Custom Data** field in the Advanced tab.

Use the following code as a base:

```php
<?php

declare(strict_types=1);

use Org\Wplake\Advanced_Views\Pro\Bridge\Cards\Custom_Card_Data;

return new class extends Custom_Card_Data {

    public function get_variables(): array
    {
        return [];
    }

    public function get_ajax_response(): array
    {
       $type = 'text/calendar'; // or 'text/csv'
       $filename = 'events.ics'; // or 'events.csv'
            
        header(sprintf('Content-Type: %s; charset=utf-8', $type));
        header(sprintf('Content-Disposition: attachment; filename="%s"', $filename));
    
        $content = do_shortcode('[avf-post-selection name="events.csv" id="YOUR_SELECTION_ID"]');
        $content = str_replace('<br>',"\n", $content);
        echo $content;
            
        exit; 
        return [];
    }
};
```

Make sure to:

* Copy the **Post Selection shortcode** from the current Post Selection
* Paste it into the `do_shortcode()` call

You could also change the exported filename by editing `$filename = "events.ics"`.

**Save** your Post Selection.

## 5. Verifying the result

Now we're ready to test the calendar export. Open a new browser tab and visit:

```
https://YOUR_DOMAIN.com/wp-admin/admin-ajax.php
?action=advanced_views&_post-selection-id=YOUR_POST_SELECTION_ID
```

Replace:

* `YOUR_DOMAIN`
* `YOUR_POST_SELECTION_ID`

with your actual values.

Visiting this URL will trigger the Ajax handler and **download the generated file**, which contains real-time data from the WordPress database.

Below is what my events.ics file contains.

{% code overflow="wrap" %}

```csv
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Advanced Views//Events Export//EN
CALSCALE:GREGORIAN

BEGIN:VEVENT
UID:event-4681@TESTING
DTSTAMP:20260320T084331Z
DTSTART:20260319T084331Z
DTEND:20260320T084331Z
SUMMARY:Best event
DESCRIPTION:\nThis events is about connecting and disconnecting.\n
END:VEVENT

BEGIN:VEVENT
UID:event-4683@TESTING
DTSTAMP:20260320T084331Z
DTSTART:20260513T084331Z
DTEND:20260514T084331Z
SUMMARY:Celebration
DESCRIPTION:
END:VEVENT

END:VCALENDAR
```

{% endcode %}

{% hint style="info" %}
**Note:**\
`admin-ajax.php` is the standard WordPress Ajax URL base.\
Although the URL contains `/wp-admin/`, it works for **both guests and authenticated users**.\
If necessary, you can restrict access by adding a **permission check at the top of the Ajax handler**.
{% endhint %}

{% hint style="success" icon="lightbulb-exclamation-on" %}
You could now setup a new Layout and Post selection to display your events, use the Sort by Meta value and choose Start date field, this ensures the next event is at the top. If you have Advanced Views Pro you can also add a Meta filter and hide past events [read more here](https://wplake.gitbook.io/advanced-views/query-content/meta-filters-pro/comparison#extended-support-for-post_object-and-relationship-fields). \
When you place the post selection shortcode on the page, include an icon or text 'Subscribe to events' above it, and add the download link to it so users can import or add it to their calendar see below.
{% endhint %}

<figure><img src="https://2293383029-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN2nAC4pTDjEmDu8ZkLB2%2Fuploads%2FSNzCiS71aE4EQli57RoB%2FScreenshot%202026-03-20%20110020.jpg?alt=media&#x26;token=8b738756-2533-4882-b0f9-998599564ea3" alt=""><figcaption></figcaption></figure>
