react-native-save-to-calendar
v0.0.4
Published
React Native module that opens the system add-event sheet on iOS and Android. Supports both the New and Legacy architectures. No calendar permission required — the OS handles its own permission prompt as part of the save flow.
Maintainers
Readme
react-native-save-to-calendar
Open the system Add Event sheet on iOS and Android from a React Native app.
The library never writes events itself — it just opens the OS-provided "create event" UI pre-filled with your data. The user reviews, picks a calendar, and saves. Because the system handles its own permission flow, the library does not request WRITE_CALENDAR (Android) or EKEntityType.event (iOS) write access on your behalf.
Features
- One JS API, two platforms:
presentEventDialog(event)→Promise<{ action: 'saved' | 'canceled' | 'deleted' }>. - Pre-fills title, start / end, location, notes, and all-day.
- iOS uses
EKEventEditViewController(EventKit + EventKitUI) and returns the user's real choice. - Android launches
Intent.ACTION_INSERTagainstCalendarContract.Events— no permissions needed. - Works on the New Architecture (TurboModule via Codegen) and the Legacy bridge — the consumer doesn't need to opt in.
- Ships with TypeScript types out of the box.
Requirements
| Platform | Minimum |
| ------------ | ---------------------------------------------------------------------------------------- |
| React Native | >= 0.71 (needs TurboModule + Codegen support). |
| iOS | Deployment target inherited from the host app (min_ios_version_supported, 13.4+). |
| Android | minSdkVersion 24, compileSdkVersion 35, Java 17, Kotlin 2.1.20 (overridable). |
The Kotlin version can be overridden by setting kotlinVersion in your root android/build.gradle. The Android module pulls compileSdkVersion / minSdkVersion / targetSdkVersion from your root ext if defined.
Expo
This library ships native code, so it does not run in Expo Go. It does work in:
- Bare React Native projects.
- Expo projects using prebuild or the Expo Dev Client (autolinking handles it on
expo prebuild).
There is no Expo config plugin — the iOS Info.plist keys below must be added through your Expo app.json ios.infoPlist block or through prebuild.
Installation
npm install react-native-save-to-calendar
# or
yarn add react-native-save-to-calendariOS only — install the pod:
cd ios && pod installAutolinking handles the rest on both platforms.
iOS setup
Add both keys to your app's Info.plist:
<key>NSCalendarsUsageDescription</key>
<string>Add this event to your calendar.</string>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>Add this event to your calendar.</string>NSCalendarsWriteOnlyAccessUsageDescriptionis the iOS 17+ key.EKEventEditViewControllerrequests write-only access on modern iOS and the system will silently fail without this string.NSCalendarsUsageDescriptionis the legacy key for iOS 16 and earlier.
App Store review requires both even though EKEventEditViewController handles its own permission prompt internally — keep both regardless of your deployment target.
Android setup
Nothing. Intent.ACTION_INSERT doesn't require WRITE_CALENDAR or any other manifest permission.
Usage
import { presentEventDialog } from 'react-native-save-to-calendar';
const start = new Date('2026-06-01T15:00:00Z');
const end = new Date('2026-06-01T16:00:00Z');
try {
const result = await presentEventDialog({
title: 'Doctor appointment',
startDate: start.toISOString(),
endDate: end.toISOString(),
location: 'Clinic, 221B Baker St',
notes: 'Bring reports',
allDay: false,
});
if (result.action === 'saved') {
// iOS: the user committed the event.
// Android: the OS reported RESULT_OK. Many calendar apps do NOT report this
// even after a successful save — see Platform behaviour below.
} else if (result.action === 'canceled') {
// User dismissed the editor / chooser, OR (Android) the calendar app
// finished without reporting success. Treat as "not confirmed", not failure.
} else if (result.action === 'deleted') {
// user tapped Delete on an existing event (iOS only)
}
} catch (err) {
// err.code will be one of E_INVALID_ARGS, E_NO_PRESENTER, E_NO_CALENDAR_APP,
// E_NO_ACTIVITY, E_IN_PROGRESS, or E_CALENDAR — see the Reject reasons table below.
console.warn('Calendar dialog failed:', err);
}API
presentEventDialog(event)
| Field | Type | Required | Notes |
| ------------ | --------- | -------- | --------------------------------------------------------------------- |
| title | string | yes | Event title shown in the editor. |
| startDate | string | yes | ISO 8601 datetime, e.g. the output of new Date(...).toISOString(). |
| endDate | string | yes | ISO 8601 datetime. |
| location | string | no | Free-form location text. |
| notes | string | no | Event description / notes. |
| allDay | boolean | no | Default false. |
Returns: Promise<{ action: 'saved' | 'canceled' | 'deleted' }>.
Platform behaviour
iOS — returns the real user choice via the
EKEventEditViewDelegatecallback.'deleted'only fires if the user editing an existing event chooses Delete.Android — the dialog is launched with
startActivityForResultand the result is mapped from the returned activity result code:RESULT_OK → 'saved', anything else →'canceled'. This means dismissing the "Complete action using" app chooser (or backing out before saving) reliably resolves'canceled'.⚠️ Android
ACTION_INSERTlimitation.ACTION_INSERTis fire-and-forget by design. Most calendar apps (Google Calendar, Samsung Calendar, Outlook, …) finish without callingsetResult(RESULT_OK)even after the user saves — so a genuine save frequently also resolves as'canceled'. Therefore on Android:'saved'is a strong signal (the app explicitly reported success) but is not emitted by every calendar app.'canceled'means "not confirmed" — the user may have cancelled, or saved in an app that doesn't report the result. It does not reliably mean the event was not created.
Reliably detecting a real save would require
READ_CALENDARpermission to query the provider after the editor closes, which this library deliberately avoids. Design your UX so that a non-'saved'result is treated as "outcome unknown" rather than a hard failure — the calendar app shows its own save confirmation regardless.
Reject reasons
| Code | Platform | Cause |
| --------------------- | -------- | ------------------------------------------------------------ |
| E_INVALID_ARGS | iOS | Missing or unparsable title, startDate, or endDate. |
| E_NO_PRESENTER | iOS | No view controller available to present the editor. |
| E_ALREADY_OPEN | iOS | A calendar dialog is already open. |
| E_NO_CALENDAR_APP | Android | No installed activity handles ACTION_INSERT for events. |
| E_NO_ACTIVITY | Android | No foreground activity available to launch the dialog. |
| E_IN_PROGRESS | Android | A calendar dialog is already open. |
| E_CALENDAR | Android | Generic failure (bad ISO date, intent launch exception). |
Architecture support
- New Architecture (Fabric / TurboModules): the library declares a Codegen spec (
src/NativeSaveToCalendar.ts) and ships a TurboModule on both platforms. - Legacy: the library registers a bridge module (
RCT_EXTERN_MODULEon iOS,ReactContextBaseJavaModuleon Android) selected via dualsrc/newarch/src/oldarchsource sets on Android.
No consumer-side opt-in is needed for either mode.
License
MIT © Aditya Thummar
