Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

Calendar Store | Getting Started

A cross-platform library for accessing device calendars and events on iOS, Mac Catalyst, macOS, Android, and Windows. Provides full CRUD operations on calendars and events, LINQ query support, Shiny’s AccessState permission model, and dependency injection integration. Built on Shiny.Core, so it runs in any Shiny host — with or without .NET MAUI.

GitHubGitHub stars for shinyorg/shiny
DownloadsNuGet downloads for Shiny.Calendar
Frameworks
.NET MAUI
.NET
  • Calendars & events CRUD — calendar management via GetAll / GetById / Create / Update / Delete (the calendar entity is implied by the interface), plus GetEvents, CreateEvent, UpdateEvent, DeleteEvent.
  • LINQ queryingQuery() returns IQueryable<CalendarEvent> where the calendar id and start/end window are pushed down to the native fetch; the rest runs in-memory.
  • Rich model — events carry attendees, reminders, availability, URL, and read-only recurrence/organizer.
  • Native backends — EventKit (iOS/Mac Catalyst/macOS), CalendarContract (Android), AppointmentStore (Windows).
  • Permissions — Shiny.Core AccessState, with CalendarAccessType for iOS 17 read/write/write-only.
  • AOT & trimmer compatible.
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
claude plugin install shiny-maui@shiny
Shell, Contact Store
claude plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
claude plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
claude plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
claude plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
claude plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
copilot plugin install shiny-maui@shiny
Shell, Contact Store
copilot plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
copilot plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
copilot plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
copilot plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
copilot plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
View Skills Repository

Install the package:

Terminal window
dotnet add package Shiny.Calendar

Register the store (the app must call .UseShiny() so platform services like permissions are wired up):

using Shiny;
builder.UseShiny();
builder.Services.AddCalendarStore();

Or generate the full set of files — packages, registration, manifests, and entitlements — with the builder below:

Shiny.CalendarNuGet package Shiny.Calendar
Shiny.Hosting.MauiNuGet package Shiny.Hosting.Maui

See Permissions for the per-platform manifest keys.

public class CalendarService(ICalendarStore store)
{
public async Task<string> AddMeeting()
{
var access = await store.RequestAccess(CalendarAccessType.ReadWrite);
if (access != AccessState.Available)
throw new InvalidOperationException("Calendar access not granted");
var evt = new CalendarEvent("Team Sync", DateTimeOffset.Now.AddHours(1), DateTimeOffset.Now.AddHours(2))
{
Location = "Room 3",
Description = "Weekly sync"
};
evt.Reminders.Add(new EventReminder(TimeSpan.FromMinutes(15)));
evt.Attendees.Add(new EventAttendee("Alice", "alice@example.com"));
return await store.CreateEvent(evt);
}
}
PropertyTypeNotes
Idstring?Platform identifier
NamestringDisplay name
Colorstring?Hex, e.g. #FF3B30
IsReadOnlyboolSubscribed/holiday calendars, etc.
Accountstring?Owning account / source
PropertyTypeNotes
Idstring?
CalendarIdstring?Target calendar
Titlestring
Descriptionstring?Notes
Locationstring?
Start / EndDateTimeOffset
IsAllDaybool
AvailabilityEventAvailabilityBusy, Free, Tentative, Unavailable
Urlstring?
IsRecurringboolRead-only
RecurrenceRulestring?Read-only
RemindersList<EventReminder>Offset = lead time before start
AttendeesList<EventAttendee>Name, Email, Role, Status, IsOrganizer
OrganizerEventAttendee?Read-only

Native calendars expose attendees as name + email + status — not contact references. Shiny.Calendar references Shiny.Contacts and ships a bridge so you can resolve an attendee back to a device contact by email:

var contact = await attendee.ResolveContact(contactStore); // matches on Email, returns Shiny.Contacts.Contact?

DeleteEvent takes a deleteSeries flag. It is ignored for non-recurring events, so it is always safe to pass — but for a recurring one, don’t guess. Check CalendarEvent.IsRecurring and let the user choose, the way the native calendar apps do:

if (evt.IsRecurring)
{
var choice = await dialogs.ActionSheet(
$"Delete \"{evt.Title}\"?", "Cancel", "Delete All Future Events", "Delete This Event");
if (choice is not ("Delete This Event" or "Delete All Future Events"))
return;
await store.DeleteEvent(evt.Id!, choice == "Delete All Future Events");
}
else
{
await store.DeleteEvent(evt.Id!);
}
PlatformdeleteSeries: falsedeleteSeries: true
iOS / Mac Catalyst / macOSEKSpan.ThisEventEKSpan.FutureEvents
AndroidInserts a cancellation exception for the occurrenceDeletes the Events row (whole series)
WindowsNo effect — AppointmentStore has no per-instance deleteDeletes the appointment
  • Recurrence is read-only everywhere.
  • Android instance granularity: events are read as series masters from the Events table, not as expanded instances, so deleteSeries: false cancels the series’ own DTSTART — the first occurrence.
  • Apple (EventKit): attendees cannot be written — attendees you set on create/update are ignored.
  • Android (CalendarContract): event CRUD works with permissions; calendar creation uses sync-adapter semantics.
  • Windows (AppointmentStore): best-effort — reads cover all calendars, but create/update/delete only work inside an app-owned calendar; writes to a system calendar throw NotSupportedException.