Read and write native calendar events and reminders
One.Calendar reads and writes events on the system calendars and keeps
reminders with due dates and completion state. Events live in the user’s
real calendars on both platforms; reminders live in the system reminders
on iOS but are private to your app on Android, which has no system
task-list provider.
On iOS both go through EKEventStore from EventKit. On Android events
go through CalendarContract, while reminders are stored by One itself
and delivered through AlarmManager and a notification channel. On web
both keep the unavailable contract: denied permission, empty lists, and
rejections for calls that return a value.
import { One } from 'one'
const status = await One.Calendar.requestPermission()if (status === 'fullAccess') { const startMs = Date.now() + 86_400_000 const identifier = await One.Calendar.create({ title: 'Team review', startMs, endMs: startMs + 3_600_000, allDay: false, }) const events = await One.Calendar.list(startMs - 1, startMs + 3_600_001, 100) console.log(events.find((event) => event.identifier === identifier)?.title)}Events need full access; write-only access cannot read, not even events the app created. Times are Unix milliseconds throughout.
Set native.app.calendar.usage for events and the independent
native.app.calendar.remindersUsage for reminders before prebuild. On iOS
they become the full-access calendars and reminders usage descriptions;
on Android the event string stamps calendar read and write permissions,
while the reminders string stamps notification and boot permissions plus
an opt-in marker. Configure only the one your app uses. EventKit work
runs on a dedicated serial queue; Android queries run on a worker thread.
list searches all visible calendars over an increasing range spanning
at most 366 days, with a whole-number limit from 1 to 500 (default 100),
returning events sorted by start time with identifier, title, times,
all-day flag, location, and each occurrence’s recurrence rule. create
saves to the default writable calendar and returns the identifier.
update and delete address one occurrence by identifier plus the exact
start time from list, since a series shares one identifier across
occurrences; they change only that occurrence, never the series. Pass the
returned start time onward for later edits. Changes carry any combination
of title, times, all-day flag, and location; omitted fields keep their
values, and an empty location clears it.
Recurrence rules carry a daily, weekly, monthly, or yearly frequency, an interval defaulting to 1, and at most one end: an occurrence count or an end date. Leave both ends out for an ongoing series. The interval runs 1 to 1,000, the count 1 to 10,000. Reminders accept the same rule when a due time is supplied. On iOS only the first incomplete reminder of a repeating set is visible, so completing it reveals the next one; list again after completion. On Android, completing a recurring reminder mints the next incomplete occurrence with a new identifier, respecting the interval and the count or date bound; reopening it does not remove the minted next one.
Android reminders are app-owned: tasks never appear in Google Tasks or
another app’s lists, and wiping app data or uninstalling deletes them.
requestRemindersPermission asks for notification permission on Android
13 and later, and fullAccess means app notifications plus the reminders
channel are on; disabling the channel rejects reminder calls. Dated
reminders schedule an inexact alarm that posts a notification even after
the process exits, with no exact-time guarantee; undated reminders have
no alarm. Completing or deleting cancels the alarm and notification.
Boot and app updates reschedule undelivered reminders, but a
force-stopped app stays silent until opened again.
| Methods: events | Type | Description |
|---|---|---|
getPermissionStatus() => CalendarPermissionStatusThe events status, without prompting. | () => CalendarPermissionStatus | The events status, without prompting. |
requestPermission() => Promise<CalendarPermissionStatus>Ask for full events access. | () => Promise<CalendarPermissionStatus> | Ask for full events access. |
list(startMs: number, endMs: number, limit: number) => Promise<CalendarEvent[]>Events in a range, by start time. Limit defaults to 100. | (startMs: number, endMs: number, limit: number) => Promise<CalendarEvent[]> | Events in a range, by start time. Limit defaults to 100. |
create(input: CalendarEventInput) => Promise<string>Save an event. Resolves with its identifier. | (input: CalendarEventInput) => Promise<string> | Save an event. Resolves with its identifier. |
update(identifier: string, originalStartMs: number, changes: CalendarEventChanges) => Promise<CalendarEvent>Edit one occurrence. Resolves with its new fields. | (identifier: string, originalStartMs: number, changes: CalendarEventChanges) => Promise<CalendarEvent> | Edit one occurrence. Resolves with its new fields. |
delete(identifier: string, startMs: number) => Promise<void>Remove the occurrence at that exact start time. | (identifier: string, startMs: number) => Promise<void> | Remove the occurrence at that exact start time. |
| Methods: reminders | Type | Description |
|---|---|---|
getRemindersPermissionStatus() => CalendarPermissionStatusThe reminders status, without prompting. | () => CalendarPermissionStatus | The reminders status, without prompting. |
requestRemindersPermission() => Promise<CalendarPermissionStatus>Ask for full reminders access. | () => Promise<CalendarPermissionStatus> | Ask for full reminders access. |
listReminders(limit: number, includeCompleted: boolean) => Promise<ReminderInfo[]>Reminders by due date, undated last. Defaults to 100, open only. | (limit: number, includeCompleted: boolean) => Promise<ReminderInfo[]> | Reminders by due date, undated last. Defaults to 100, open only. |
createReminder(input: ReminderInput) => Promise<string>Save a reminder. Resolves with its identifier. | (input: ReminderInput) => Promise<string> | Save a reminder. Resolves with its identifier. |
setReminderCompleted(identifier: string, completed: boolean) => Promise<void>Complete or reopen a reminder. | (identifier: string, completed: boolean) => Promise<void> | Complete or reopen a reminder. |
deleteReminder(identifier: string) => Promise<void>Remove a reminder. | (identifier: string) => Promise<void> | Remove a reminder. |
| Event | Type | Description |
|---|---|---|
identifierstringShared across a series; pair with startMs. | string | Shared across a series; pair with startMs. |
titlestringThe event title. | string | The event title. |
startMsnumberStart in Unix milliseconds. | number | Start in Unix milliseconds. |
endMsnumberEnd in Unix milliseconds. | number | End in Unix milliseconds. |
allDaybooleanWhether the event spans the day. | boolean | Whether the event spans the day. |
locationstringThe location, or empty. | string | The location, or empty. |
recurrenceCalendarRecurrenceThe series rule, when repeating. | CalendarRecurrence | The series rule, when repeating. |
| New event | Type | Description |
|---|---|---|
titlestringThe event title. | string | The event title. |
startMsnumberStart in Unix milliseconds. | number | Start in Unix milliseconds. |
endMsnumberEnd in Unix milliseconds. | number | End in Unix milliseconds. |
allDaybooleanWhether the event spans the day. | boolean | Whether the event spans the day. |
recurrenceCalendarRecurrenceA repeat rule. Optional. | CalendarRecurrence | A repeat rule. Optional. |
| Event changes | Type | Description |
|---|---|---|
titlestringA new title, or omit to keep it. | string | A new title, or omit to keep it. |
startMsnumberA new start, or omit to keep it. | number | A new start, or omit to keep it. |
endMsnumberA new end, or omit to keep it. | number | A new end, or omit to keep it. |
allDaybooleanA new all-day flag, or omit to keep it. | boolean | A new all-day flag, or omit to keep it. |
locationstringA new location. Empty clears it. | string | A new location. Empty clears it. |
| Recurrence | Type | Description |
|---|---|---|
frequencyCalendarRecurrenceFrequencyDaily, weekly, monthly, or yearly. | CalendarRecurrenceFrequency | Daily, weekly, monthly, or yearly. |
intervalnumberEvery nth period. Default 1. | number | Every nth period. Default 1. |
endDateMsnumberStop after this time. At most one end. | number | Stop after this time. At most one end. |
occurrenceCountnumberStop after this many. At most one end. | number | Stop after this many. At most one end. |
| New reminder | Type | Description |
|---|---|---|
titlestringThe reminder title. | string | The reminder title. |
dueMsnumberDue in Unix milliseconds. Optional. | number | Due in Unix milliseconds. Optional. |
recurrenceCalendarRecurrenceA repeat rule. Needs a due time. | CalendarRecurrence | A repeat rule. Needs a due time. |
| Reminder | Type | Description |
|---|---|---|
identifierstringThe stable id for completion and delete. | string | The stable id for completion and delete. |
titlestringThe reminder title. | string | The reminder title. |
completedbooleanWhether it is done. | boolean | Whether it is done. |
dueMsnumberDue in Unix milliseconds, when dated. | number | Due in Unix milliseconds, when dated. |
recurrenceCalendarRecurrenceThe repeat rule, when repeating. | CalendarRecurrence | The repeat rule, when repeating. |
| iOS | Android | Web | |
|---|---|---|---|
| Events | System calendars | System calendars | Empty lists |
| Reminders | System reminders | Private to the app | Empty lists |
| Reminder alert | System delivery | Inexact alarm | None |
| Access | Full or write only | Full or denied | Denied |
| Setup | Usage text each | Permissions each | Nothing |
startMs
from list to update and delete, and use the returned start time
for the next edit.E_CALENDAR_INPUT.import { useEffect, useState } from 'react'import { Text } from 'react-native'import { One } from 'one'
type WeekEvent = Awaited<ReturnType<typeof One.Calendar.list>>[number]
function ThisWeek() { const [events, setEvents] = useState<WeekEvent[]>([])
useEffect(() => { async function load() { const status = await One.Calendar.requestPermission() if (status !== 'fullAccess') return const now = Date.now() setEvents(await One.Calendar.list(now, now + 7 * 86_400_000, 100)) } load() }, [])
return ( <> {events.map((event) => ( <Text key={`${event.identifier}${event.startMs}`}> {event.title} at {new Date(event.startMs).toLocaleString()} </Text> ))} </> )}The key pairs identifier with start time because a series reuses one identifier across occurrences. The week range stays well under the 366-day cap.
import { One } from 'one'
async function moveToRoom(identifier: string, startMs: number, room: string) { const updated = await One.Calendar.update(identifier, startMs, { location: room, }) console.log('Moved', updated.title, 'to', updated.location) return updated}Only the location changes; every omitted field keeps its value. The returned event carries the fields a follow-up edit or delete needs.
import { One } from 'one'
async function reportReminder() { if ((await One.Calendar.requestRemindersPermission()) !== 'fullAccess') return const identifier = await One.Calendar.createReminder({ title: 'Send the report', dueMs: Date.now() + 86_400_000, }) const open = await One.Calendar.listReminders(100, false) console.log('Open reminders:', open.length) await One.Calendar.setReminderCompleted(identifier, true)}The default listing shows open reminders only, so the count confirms the new item landed. Completion flips the flag; on iOS a repeating reminder then reveals its next occurrence on the following list.
Edit this page on GitHub.