open-horizon-labs25k downloadsAdd events from calendar ics on the web to daily notes on demand. Includes vdir support. Daily Planner, Templater and Dataview friendly.
This is a plugin for Obsidian. It adds events from calendar/ics URLs to your Daily Note on demand.
This is designed to work with the Daily Note or Periodic Notes plugins: specifically it gets the date to search for events during from the currently open daily note. You can use it through Dataview or Templater for more advanced / customized usage.
I highly recommend pairing this with the Day Planner plugin: the output format is tuned to support it and you'll get support for seeing the day and week planners.
There are many calendar integration plugins available for Obsidian, some with more features and bells and whistles. This plugin focuses on a different philosophy: clean, maintainable code that adapts to user expectations rather than forcing users to adapt to the plugin.
Key advantages of this approach include:
getEvents() method accepts date strings, moment objects, or Date objects - whatever is most natural for your use caseIf you value simplicity, performance, and the ability to customize your calendar integration exactly how you want it, this plugin might be a good fit for your workflow.
This plugin is in the community plugin browser in Obsidian. Search for ICS and you can install it from there.
ICS: Import events command. If it shows nothing, check to make sure your iCal URL is the secret URL as that commonly is the issue.
The plugin can extract custom fields from calendar events using configurable patterns. This powerful feature allows you to extract any text data and organize it into named fields.
✨ New in this version: This expands the previous video call URL extraction feature into a fully generic system. Your existing templates using
callUrlandcallTypecontinue to work unchanged, but you now have access to much more powerful extraction capabilities throughextractedFields.
By default, the plugin extracts video call URLs into the "Video Call URL" field:
GOOGLE-CONFERENCE fieldzoom.ushttps://join.skype.com/ patternYou can create patterns to extract any information:
webex.com → "Video Call URLs" fieldgotomeeting.com → "Video Call URLs" fieldmeet.jit.si → "Video Call URLs" field(https?|ftp|file)://[^\s<>"]+ → "Links" field\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4} → "Phone Numbers" field\+\d{1,3}[-.\s]?\d{1,14} → "Phone Numbers" fieldMeeting ID: (\d+) → "Meeting IDs" fieldPattern Types:
Pattern Management:
Legacy Support (Backward Compatible):
callUrl and callType continue to work for existing templatesNew Approach (Recommended):
extractedFields["Field Name"] to access any extracted dataExample:
// Legacy approach (still works but limited)
if (event.callUrl) {
// Only gets the first video call URL
}
// New approach (recommended)
const videoUrls = event.extractedFields["Video Call URLs"] || [];
const meetingIds = event.extractedFields["Meeting IDs"] || [];
const phoneNumbers = event.extractedFields["Phone Numbers"] || [];
// Handle multiple values
videoUrls.forEach(url => {
// Process each video call URL
});
💡 Tip: Consider migrating your templates to use
extractedFieldsfor more flexibility and to access all extracted data, not just the first match.
Go to a daily note, use the ICS: Import events command.
For customizations not available to the formatting, use Dataview or Templater (see below). Likewise, if you want to automatically import events when you create your daily notes, you'll want to use one of those. If you have issues using Dataview or Templater, test that your calendar imports works using the ICS: Import events command as there's more error handling available there.
You can also use a Dataview to add your events to your journal notes when they get created.
The getEvents() method accepts flexible date inputs: date strings (like "2025-03-01" or "March 1, 2025"), moment objects, or JavaScript Date objects. This makes it easy to work with whatever date format is most convenient for your use case.
For example, if you use the core Templates plugin you can add the following to add events to your daily note template:
// Simple string-based approach
var events = await app.plugins.getPlugin('ics').getEvents(dv.current().file.name);
// Or use a Date object for today's events
var events = await app.plugins.getPlugin('ics').getEvents(new Date());
// Or use moment for date manipulation
var events = await app.plugins.getPlugin('ics').getEvents(moment().add(1, 'day'));
var mdArray = [];
events.forEach((e) => {
mdArray.push(`${e.time} ${e.summary} ${e.location}: ${e.description}`.trim())
})
dv.list(dv.array(mdArray))
You can see the available fields in the Event interface.
You can use Templater with flexible date inputs. The getEvents() method now accepts moment objects directly, so you don't need to convert them to strings:
<%*
// Direct moment object usage (recommended)
var events = await app.plugins.getPlugin('ics').getEvents(moment(tp.file.title,'YYYYMMDD'));
events.sort((a,b) => a.utime - b.utime).forEach((e) => {
tR+=`- [ ] ${e.time} ${e.summary} ${e.location? e.location : ''}\n`
})
%>
If you prefer to use string dates or need to handle different date formats, you can also do:
<%*
// String format (also works)
var events = await app.plugins.getPlugin('ics').getEvents(moment(tp.file.title,'YYYYMMDD').format('YYYY-MM-DD'));
events.sort((a,b) => a.utime - b.utime).forEach((e) => {
tR+=`- [ ] ${e.time} ${e.summary} ${e.location? e.location : ''}\n`
})
%>
See advanced Templated usage example for an example that demonstrates more features.
You can see the available fields an the Event interface.
Or you can use Full Calendar to render a calendar view of your events. Here's an example of how you can use it:
const { renderCalendar } = app.plugins.getPlugin("obsidian-full-calendar");
const thisWeek = Array.from({length: 7}).map((_, weekday) => moment().set({weekday}).format("YYYY-MM-DD"))
const icsPlugin = app.plugins.getPlugin('ics')
const events = (await icsPlugin.getEvents(...thisWeek))
.map(event => {
const start = moment.unix(event.utime)
const [endHours, endMinutes] = event.endTime.split(":")
return {
start: start.toDate(),
end: start.set({hour: endHours, minute: endMinutes}).toDate(),
title: event.summary,
}
}
)
renderCalendar(this.container, {events}).render()
If you want to try out beta releases, you can use the BRAT plugin.
Settings -> Community PluginsSettings -> BRAT)Beta Plugin List sectionAdd Beta Plugincloud-atlas-ai/obsidian-icsAmazing Marvin plugin (Settings -> Community Plugins)If you want to support my work, you can buy me a coffee
If for some reason you want to install the plugin manually:
obsidian-ics-[version].zip release file from releases.obsidian-ics folder.ICS pluginnpm installnpm run dev will watch for changes and build the plugin to dist/main.js.dist/main.js (or dist/main-debug.js if you want the un-minified version) to your Obdisian vault plugin folder (cp dist/main.js <vault>/.obsidian/plugins/ics/main.js).The plugin includes a test harness for recurring events and timezone handling to prevent regressions:
# Run all tests
npm test
# Run tests in watch mode (automatically re-runs when files change)
npm run test:watch
See tests/README.md for information about how to add new tests.