Loading…

Embed API reference

The full contract, in one place. You don't need the builder above to write a correct embed — hand this page, or /embed.md, to a coding assistant and it has everything it needs.

Quick start

<div data-ics-url="https://example.com/calendar.ics"></div>
<script src="https://icsviewer.com/widget.js" async></script>

Swap in a public iCal feed URL. That's a complete embed — every other attribute is optional. widget.js mounts every element on the page carrying data-ics-url, so include the script once no matter how many calendars you place.

Options

Every option is an attribute on the container <div>. Booleans are "1" for on; omit the attribute for off.

Required

Attribute Values Default Description
data-ics-url url required Public iCal feed URL (an .ics endpoint). Must be reachable without authentication — a private webcal:// or password-protected feed will not load. Pass several comma-separated URLs to merge them into one calendar; each feed gets its own colour and a legend appears above the grid. This is the only required attribute.

Layout

Attribute Values Default Description
data-view month | week | day | year | agenda month View shown when the calendar first loads. Must be one of the views listed in data-views.
data-views any of month, week, day, year, agenda month,week,agenda Comma-separated list of views the visitor can switch between. Order controls the order of the switcher buttons. Pass a single value to lock the calendar to one view.
data-height length 600px Height of the embed. A bare number is treated as pixels; any CSS length (600px, 80vh, 100%) is accepted. Use "auto" to size the embed to its content — the frame measures itself and reports its height to the page, so the calendar never scrolls internally and leaves no empty space below it. Ignored when the container already carries an explicit height from your own CSS.
data-radius int 8 Corner radius of the frame in pixels. 0 for square corners.
data-compact bool 0 Tighter spacing and smaller type. Use for sidebars and narrow columns under ~400px wide.

Appearance

Attribute Values Default Description
data-theme light | dark | auto light Colour scheme. "auto" follows the visitor's system dark-mode setting and repaints if they change it mid-session. Setting data-bg overrides this — an explicit background always wins.
data-color color 7c3aed Accent colour for event chips, the today marker and active controls. Hex, with or without the leading #.
data-bg color ffffff Background colour of the calendar surface. Dark values automatically flip text and grid lines to a light-on-dark palette. Overrides data-theme, so set one or the other.
data-hide-header bool 0 Hide the calendar's title bar and view switcher. Combine with a single value in data-views to drop a bare calendar into a page that supplies its own heading.
data-title string — Replace the feed's own calendar name with custom header text. Ignored when data-hide-header is set.

Content

Attribute Values Default Description
data-upcoming bool 0 Show only events from today forward. Past events are omitted entirely.
data-filter string — Show only events whose title or category contains this keyword. Case-insensitive substring match, single keyword.

Localization

Attribute Values Default Description
data-timezone string — IANA timezone name used to render event times. Defaults to the visitor's own browser timezone, which is usually what you want for a public calendar. Set it explicitly to pin the calendar to a venue's local time.
data-locale string — BCP 47 locale for month names, weekday labels and date formatting. Defaults to the visitor's browser locale.
data-weekstart 0 | 1 0 First day of the week in month and week views. 0 = Sunday, 1 = Monday.
data-timeformat 12h | 24h 12h Clock format for event times. Note the query-param form differs: the direct-iframe equivalent is h24=1 for 24-hour.

Common configurations

Upcoming events, sidebar width

Agenda-only, past events dropped, tight spacing.

<div
  data-ics-url="https://example.com/calendar.ics"
  data-view="agenda"
  data-views="agenda"
  data-height="480"
  data-compact="1"
  data-upcoming="1"
></div>
<script src="https://icsviewer.com/widget.js" async></script>

Dark theme

A dark background hex flips the whole palette; nothing else needed.

<div
  data-ics-url="https://example.com/calendar.ics"
  data-color="22d3ee"
  data-bg="0b0b0f"
  data-weekstart="1"
  data-timeformat="24h"
></div>
<script src="https://icsviewer.com/widget.js" async></script>

Filtered, titled, pinned timezone

One keyword, your own header text, a fixed venue timezone.

<div
  data-ics-url="https://example.com/calendar.ics"
  data-color="059669"
  data-title="Workshops"
  data-filter="workshop"
  data-timezone="America/New_York"
></div>
<script src="https://icsviewer.com/widget.js" async></script>

Notes and constraints

The feed must be publicly reachable

widget.js loads the feed through icsviewer.com, not from the visitor's browser session. A Google Calendar 'secret address' works; a calendar shared only with signed-in users does not. If the calendar renders empty, open the feed URL in a private window first — if you get a login page, the embed will get one too.

Height comes from the container

The frame fills its container. Use data-height, or give the container an explicit height in your own CSS. Without either, the frame collapses to the browser's default iframe height of 150px.

data-height="auto" sizes month, agenda and year to content

Day and week are a fixed 24-hour grid, so sizing them to content would mean a 1536px embed. Those two keep an internal scroll at roughly 620px in auto mode; the other three grow and shrink to fit, reporting their height to the page as the visitor navigates.

Merging feeds

Comma-separate URLs in data-ics-url to show several calendars at once — each gets a colour from a ten-slot palette in the order listed, and a legend row names them. Feeds are fetched in parallel and one that fails is skipped rather than breaking the whole embed, so a merged calendar still renders if a single host is down.

One script tag, any number of calendars

widget.js mounts every element carrying data-ics-url on the page. Repeat the div for multiple calendars and include the script once.

The script must run after the div exists

widget.js mounts on DOMContentLoaded, so placement is flexible in plain HTML. In a client-rendered framework the div may appear after the script has already run — see the framework snippets for the mounting pattern that avoids this.

Embedded calendars are cached for 15 minutes

An embed re-renders whenever its page loads — on a dashboard or a signage screen that is hundreds of times a day for one unchanged feed. So icsviewer.com serves embedded frames from a shared cache and fetches your feed at most once every 15 minutes, keyed on the feed URL alone: every screen showing the same calendar shares one upstream request, and your calendar host is not hit once per viewer. An edit to the calendar therefore takes up to 15 minutes to appear in an embed. Nothing else is cached this way — opening a feed directly on icsviewer.com always fetches it fresh, so you can check a change immediately there. Cache entries are keyed by the full feed URL, which is itself the calendar's read credential, so an entry is only ever reachable by a request already holding that URL.

We record the page the calendar is embedded on

widget.js adds the URL of the page it is running on to the frame URL as hosturl=, and we record it. This is the address of your page, not anything about the people viewing it — no visitor identifiers, no cookies, and the fragment (#...) is never included. It tells us where the widget is in use, which is what lets us keep the embed free: we can see when one page is generating heavy load and fix it, rather than rate-limiting everyone. Your browser already sends us your site in the Referer header on every embed request, so this changes the precision of what we see, not the kind. If your embed sits on a URL whose query string you would rather not share, put the calendar on a path without it — or use the direct-iframe form, which skips widget.js and sends no hosturl at all.

Attribution

widget.js renders a small 'Calendar by ICS Viewer' link directly beneath the calendar, in your page's DOM. It is part of the free embed. Removing it in CSS is not supported.

Building this with an AI assistant?

Point it at one of these instead of copying from the builder. /embed.md and /embed.json are generated from the same spec as this page and are served with permissive CORS, so an agent can fetch them directly.