Plug-in

The Plug-in tab lets you write a small piece of JavaScript that fetches data from an external API and turns it into calendar items automatically -- for example, pulling public holidays, or any other data source that publishes an API, and having it show up on your calendar without manual entry.
Accessing the Plug-in tab
Go to Settings → Plug-in.
The editor
The page gives you:
- Active -- a switch that turns your plugin on or off. Toggling this off stops the plugin from running without deleting your code.
- Code editor -- a syntax-highlighted JavaScript editor where you write your plugin's source.
- Save -- stays disabled until you've made a change, then saves your code and Active state. If saving fails, an error message appears.
Writing a plugin
A plugin is a small JavaScript class with an async execute(ctx) method. The class can be named anything -- Kalendi detects it automatically. A new account starts with this placeholder:
What ctx gives you
execute() receives a context object with:
| Field | Description |
|---|---|
todayDate |
Today's date |
userAccount |
The signed-in user's account |
userAccountSettings |
The user's account settings |
projects |
The user's projects |
itemTypes |
The user's item types |
itemTaskStatuses |
The user's task statuses |
itemPriorities |
The user's priority levels |
No auth tokens, API keys, or other secrets are included in ctx -- your plugin only ever sees the data listed above.
Here's a real (anonymized) example of what ctx looks like, with itemTypes trimmed to a few
entries -- a real account typically has many more:
{
"todayDate": "2026-08-21",
"userAccountSettings":
[
{ "id": "a1b2c3d4-e5f6-4000-9000-000003000000", "key": "Date Format", "value": "YYYY-MM-DD" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000004000000", "key": "Time Zone", "value": "America/Vancouver" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000005000000", "key": "Quotes", "value": "All" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000006000000", "key": "Show Items Created by Plug-in in Month View", "value": "True" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000007000000", "key": "Language", "value": "en" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000008000000", "key": "Show Action Based Repeated Items in Kanban", "value": "True" },
{ "id": "a1b2c3d4-e5f6-4000-9000-000009000000", "key": "Can Operate Frontend Plugin", "value": "True" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000a000000", "key": "Theme", "value": "Dark" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000b000000", "key": "Show Action Based Repeated Items in Month View", "value": "True" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000c000000", "key": "Show Client", "value": "True" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000d000000", "key": "Show Repeated Items in Kanban", "value": "False" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000e000000", "key": "Show Repeated Items in Month View", "value": "False" },
{ "id": "a1b2c3d4-e5f6-4000-9000-00000f000000", "key": "Show on Launch", "value": "Week" }
],
"userAccount":
{
"avatar": "https://media.kalendi.app/media/user_account/a1b2c3d4-e5f6-4000-9000-000002000000/account/images/avatar/xxxxxxxxxxxxxxxxxxxxxxxx.webp",
"first_name": "Jamie",
"last_name": "Rivera",
"mobile_phone_number": null,
"avatar_url": null,
"is_signed_by_auth_provider": false,
"id": "a1b2c3d4-e5f6-4000-9000-000001000000",
"email": "jamie.rivera@example.com",
"has_password_login": true
},
"projects":
[
{
"order": 0,
"is_default": true,
"is_archived": false,
"title": "Personal",
"abbreviation": "PER",
"font_awesome_icon": "fa-light fa-user",
"icon": null,
"color": "#4254F5",
"client_id": null,
"id": "a1b2c3d4-e5f6-4000-9000-000012000000"
},
{
"order": 1,
"is_default": false,
"is_archived": false,
"title": "Work",
"abbreviation": "WRK",
"font_awesome_icon": "fa-light fa-briefcase",
"icon": null,
"color": "#E87B27",
"client_id": null,
"id": "a1b2c3d4-e5f6-4000-9000-000013000000"
}
],
"itemTypes":
[
{
"title": "Anniversary",
"font_awesome_icon": "fa-light fa-calendar",
"icon": null,
"color": "#C5E4E7",
"background_color": "#38838A",
"background_image": "https://media.kalendi.app/media/user_account/a1b2c3d4-e5f6-4000-9000-000002000000/product/item_types/a1b2c3d4-e5f6-4000-9000-000014000000/background_images/xxxxxxxxxxxxxxxxxxxxxxxx.webp",
"background_image_opacity": 0.3,
"id": "a1b2c3d4-e5f6-4000-9000-000014000000"
},
{
"title": "Appointment",
"font_awesome_icon": "fa-light fa-clock",
"icon": null,
"color": "#BADEC3",
"background_color": "#396543",
"background_image": "https://media.kalendi.app/media/user_account/a1b2c3d4-e5f6-4000-9000-000002000000/product/item_types/a1b2c3d4-e5f6-4000-9000-000015000000/background_images/xxxxxxxxxxxxxxxxxxxxxxxx.webp",
"background_image_opacity": 0.3,
"id": "a1b2c3d4-e5f6-4000-9000-000015000000"
}
],
"itemTaskStatuses":
[
{ "order": 0, "title": "To Do", "font_awesome_icon": "fa-light fa-circle", "icon": null, "color": "#42526E", "id": "a1b2c3d4-e5f6-4000-9000-000017000000", "status_type": "To Do" },
{ "order": 1, "title": "In Progress", "font_awesome_icon": "fa-light fa-play", "icon": null, "color": "#0052CC", "id": "a1b2c3d4-e5f6-4000-9000-000018000000", "status_type": "In Progress" },
{ "order": 2, "title": "On Hold", "font_awesome_icon": "fa-light fa-stop", "icon": null, "color": "#99031F", "id": "a1b2c3d4-e5f6-4000-9000-000019000000", "status_type": "On Hold" },
{ "order": 3, "title": "Done", "font_awesome_icon": "fa-light fa-check", "icon": null, "color": "#00875A", "id": "a1b2c3d4-e5f6-4000-9000-00001a000000", "status_type": "Done" }
],
"itemPriorities":
[
{ "order": 0, "title": "Highest", "font_awesome_icon": "fa-solid fa-chevron-double-up", "icon": null, "color": "#FF0000", "id": "a1b2c3d4-e5f6-4000-9000-00001b000000", "priority_type": "Highest" },
{ "order": 1, "title": "High", "font_awesome_icon": "fa-solid fa-chevron-up", "icon": null, "color": "#FF4500", "id": "a1b2c3d4-e5f6-4000-9000-00001c000000", "priority_type": "High" },
{ "order": 2, "title": "Medium", "font_awesome_icon": "fa-solid fa-equals", "icon": null, "color": "#FFA500", "id": "a1b2c3d4-e5f6-4000-9000-00001d000000", "priority_type": "Medium" },
{ "order": 3, "title": "Low", "font_awesome_icon": "fa-solid fa-chevron-down", "icon": null, "color": "#2684FF", "id": "a1b2c3d4-e5f6-4000-9000-00001e000000", "priority_type": "Low" },
{ "order": 4, "title": "Lowest", "font_awesome_icon": "fa-solid fa-chevron-double-down", "icon": null, "color": "#0052CC", "id": "a1b2c3d4-e5f6-4000-9000-00001f000000", "priority_type": "Lowest" }
]
}
What to return
Return an object with an item_data array. Each entry can set only these fields:
| Field | Description |
|---|---|
title |
The item's title |
starts_at |
Start date/time |
ends_at |
End date/time |
reminder |
A reminder for the item |
font_awesome_icon |
An icon for the item |
all_day |
Whether the item is an all-day item |
reminder must be exactly one of: Don't remind, At event time, 5 minutes, 10 minutes,
15 minutes, 30 minutes, 1 hour. Any other value is silently ignored and the item falls back
to the default reminder.
class KalendiFrontendPlugin
{
async execute(ctx)
{
const response = await fetch('https://api.example.com/events');
const data = await response.json();
return
{
item_data:
[
{
title : data.eventName,
starts_at : data.startTime,
ends_at : data.endTime,
reminder : '15 minutes',
font_awesome_icon : 'fa-light fa-bell',
}
]
};
}
}
KalendiFrontendPlugin;
Example: daily weather
This plugin geocodes a city name, pulls today's forecast for it, and adds a single all-day item with the high/low temperature and a weather icon:
class KalendiFrontendPlugin
{
getWeatherIcon(weatherCode)
{
if(weatherCode === 0)
{
return 'fa-light fa-sun';
}
if(weatherCode <= 3)
{
return 'fa-light fa-cloud-sun';
}
if(weatherCode <= 48)
{
return 'fa-light fa-cloud-fog';
}
if(weatherCode <= 67)
{
return 'fa-light fa-cloud-rain';
}
if(weatherCode <= 77)
{
return 'fa-light fa-snowflake';
}
if(weatherCode <= 82)
{
return 'fa-light fa-cloud-showers-heavy';
}
if(weatherCode <= 99)
{
return 'fa-light fa-cloud-bolt';
}
return 'fa-light fa-cloud';
}
async execute(ctx)
{
const city = 'Vancouver, BC';
const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
const geocodeResponse = await fetch(geocodeUrl);
const geocodeData = await geocodeResponse.json();
const location = geocodeData.results && geocodeData.results[0];
if(!location)
{
return {};
}
const forecastUrl =
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}` +
`&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=auto`;
const forecastResponse = await fetch(forecastUrl);
const forecastData = await forecastResponse.json();
const weatherCode = forecastData.daily.weathercode[0];
const highTemp = Math.round(forecastData.daily.temperature_2m_max[0]);
const lowTemp = Math.round(forecastData.daily.temperature_2m_min[0]);
const weatherItem =
{
title : `${city}: ${highTemp}°C / ${lowTemp}°C`,
starts_at : `${ctx.todayDate}T00:00:00`,
ends_at : `${ctx.todayDate}T00:00:00`,
all_day : true,
reminder : "Don't remind",
font_awesome_icon : this.getWeatherIcon(weatherCode),
};
const data =
{
item_data : [weatherItem]
};
return data;
}
}
KalendiFrontendPlugin;
How it runs
Your plugin executes in an isolated, invisible sandbox once when Kalendi loads, and again whenever
you save changes to it in this Settings tab -- it can't access your cookies, local storage, or
Kalendi's backend directly, only whatever external API you call inside execute(). Navigating the
calendar (changing the date or view) does not re-run it -- items it created stay cached from that
last run until the next page load or save.
CORS restriction
Because the sandbox runs with a null origin, it can only call external APIs that explicitly allow cross-origin requests from a null origin. Some APIs (Google's, for example) reject this and won't work from a plugin. If your plugin's requests are silently failing, this is the first thing to check.
A run that takes longer than 30 seconds is aborted automatically.
Items created by your plugin
Items your plugin creates behave differently from items you add yourself:
- They can't be edited or deleted from the calendar -- Kalendi shows a message explaining the item was created by a plug-in.
- Whether they appear in Month view is controlled separately, under Settings → User Account Settings → Show Items Created by Plug-in in Month View.
To remove items a plugin created, turn the plugin off (or change what it returns) rather than trying to delete the items directly.