npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-icloud-kit

v0.1.6

Published

CloudKit and NSUbiquitousKeyValueStore for React Native. Save/query/batch CKRecords, key-value sync, and iCloud entitlements via Expo config plugin. iOS only. Built with Expo Modules API.

Readme

react-native-icloud-kit

CloudKit and NSUbiquitousKeyValueStore for React Native. iOS only, built with Expo Modules API.

  • CloudKit (iCloud): Save, query, batch save, and delete records in the user's private CloudKit database. Automatic pagination, chunked batch uploads with retry, and typed error handling.
  • Key-Value Store (iCloudKVS): Read and write small string values via NSUbiquitousKeyValueStore -- automatically synced across all of the user's devices.
  • Expo Config Plugin: Automatically configures iCloud entitlements, CloudKit services, and KVS identifiers at build time. No manual Xcode setup.

Installation

npm install react-native-icloud-kit
# or
yarn add react-native-icloud-kit
# or
bun add react-native-icloud-kit

Then install the native pods:

npx pod-install

Prerequisites

  • Expo >= 51.0.0 (uses Expo Modules API)
  • React Native >= 0.74.0
  • iOS only -- all methods return safe no-ops or throw on Android
  • An Apple Developer account with an iCloud container configured

Setup

1. Configure the Expo plugin

Add the plugin to your app.json (or app.config.js) with your iCloud container identifier:

{
  "expo": {
    "plugins": [
      [
        "react-native-icloud-kit/plugin/withICloud",
        {
          "containerIdentifier": "iCloud.com.yourcompany.yourapp"
        }
      ]
    ]
  }
}

This automatically:

  • Adds com.apple.developer.icloud-container-identifiers to your entitlements
  • Enables CloudKit in com.apple.developer.icloud-services
  • Sets up the KVS ubiquity identifier (com.apple.developer.ubiquity-kvstore-identifier)
  • Writes the container ID to Info.plist so the Swift module can read it at runtime

2. Create the iCloud container in Apple Developer Portal

  1. Go to Certificates, Identifiers & Profiles
  2. Under Identifiers, select iCloud Containers
  3. Click + and create a container matching your containerIdentifier (e.g., iCloud.com.yourcompany.yourapp)
  4. Go to your App ID and enable the iCloud capability, then associate it with the container

3. Rebuild

npx expo prebuild --clean
npx expo run:ios

Note: The container identifier does NOT need to match your bundle ID. For example, your app can be com.yourcompany.yourapp while your container is iCloud.com.yourcompany.differentname.

API Reference

The library exports two objects: iCloud (CloudKit) and iCloudKVS (Key-Value Store).

import { iCloud, iCloudKVS } from 'react-native-icloud-kit';

Types

type FieldValue = string | number | null;
type Fields = Record<string, FieldValue>;

interface CloudKitRecord {
  recordId: string;
  fields: Fields;
}

interface BatchRecord {
  fields: Fields;
  recordId?: string; // auto-generated UUID if omitted
}

iCloud.isAvailable()

Check if the user is signed into iCloud.

const available = await iCloud.isAvailable();
// true if signed in, false otherwise
// Always returns false on Android

iCloud.getUserRecordID()

Get the current user's CloudKit record ID. This is a container-scoped identifier — the same iCloud account gets a different record ID in each app's CloudKit container. Useful for diagnostics, user attribution, or generating a stable per-app identity token.

try {
  const recordID = await iCloud.getUserRecordID();
  console.log('User record:', recordID);
  // e.g., "_abc123def456..."
} catch (error) {
  // Throws if iCloud is not available
}

iCloud.save(recordType, fields, recordId?)

Save a single record to the user's private CloudKit database. Creates a new record, or overwrites an existing one if a record with the same recordId already exists.

| Parameter | Type | Description | |---|---|---| | recordType | string | The CloudKit record type (e.g., "GameSession") | | fields | Fields | Key-value pairs for the record | | recordId | string? | Optional deterministic ID. Auto-generated UUID if omitted |

Returns the saved record's ID.

const id = await iCloud.save('GameSession', {
  playerName: 'Alice',
  score: 42,
  datePlayed: Date.now(),
});
console.log('Saved record:', id);

Deterministic IDs allow idempotent saves -- if you save with the same recordId twice, the second save overwrites the first instead of creating a duplicate:

const deterministicId = `session-${session.date}-${session.score}`;
await iCloud.save('GameSession', fields, deterministicId);
// Safe to call again -- same ID means same record is updated

iCloud.query(recordType, predicate?, limit?)

Query records from the private CloudKit database. Supports filtering with NSPredicate syntax and automatic cursor-based pagination.

| Parameter | Type | Description | |---|---|---| | recordType | string | The CloudKit record type to query | | predicate | string? | Optional NSPredicate format string. Defaults to all records | | limit | number? | Max records to return. Defaults to all (paginated in batches of 200) |

Returns an array of CloudKitRecord objects.

// Fetch all records of a type
const all = await iCloud.query('GameSession');

// Filter with NSPredicate
const hard = await iCloud.query('GameSession', 'nValue >= 3');

// Limit results
const recent = await iCloud.query('GameSession', undefined, 10);

Important: For queries to work, the fields you filter on must be marked as QUERYABLE in CloudKit Dashboard. At minimum, mark recordName as QUERYABLE for each record type.


iCloud.batchSave(recordType, records)

Save multiple records in a single operation. Automatically handles CloudKit's per-request limits by chunking into batches of 400 and retrying with smaller batches if the server returns limitExceeded.

| Parameter | Type | Description | |---|---|---| | recordType | string | The CloudKit record type | | records | BatchRecord[] | Array of records with fields and optional recordId |

Returns the count of successfully saved records.

const records = sessions.map(s => ({
  fields: { score: s.score, date: s.date },
  recordId: `session-${s.id}`,  // optional deterministic ID
}));

const savedCount = await iCloud.batchSave('GameSession', records);
console.log(`Saved ${savedCount} of ${records.length} records`);

Retry behavior:

  • Starts with chunks of 400 records
  • If CloudKit returns limitExceeded, halves the chunk size and retries
  • Individual records that fail are retried up to 2 times before being skipped
  • Returns the total count of successfully saved records

iCloud.delete(recordType, recordId)

Delete a single record by its ID.

| Parameter | Type | Description | |---|---|---| | recordType | string | The CloudKit record type | | recordId | string | The record ID to delete |

Returns true on success.

await iCloud.delete('GameSession', 'session-123');

iCloud.deleteAll()

Delete ALL records from the CloudKit private database by deleting the custom record zone. This is the most efficient way to wipe all data -- a single server round-trip regardless of record count. The zone is automatically recreated on the next save, query, or batchSave call.

Returns true on success.

await iCloud.deleteAll();
// All records in the private database are now gone.
// The zone will be recreated automatically on next use.

Warning: This is irreversible. All records of all types within the zone are permanently deleted.


iCloudKVS.set(key, value)

Write a string value to NSUbiquitousKeyValueStore. The value is automatically synced across all of the user's devices via iCloud.

| Parameter | Type | Description | |---|---|---| | key | string | The key to store under | | value | string | The string value to store. Use JSON.stringify() for objects |

// Simple string
await iCloudKVS.set('username', 'Alice');

// Complex object as JSON
const config = { theme: 'dark', level: 5 };
await iCloudKVS.set('app_config', JSON.stringify(config));

Limits: NSUbiquitousKeyValueStore allows up to 1 MB total storage and 1024 keys. Individual values should be kept small.


iCloudKVS.get(key)

Read a string value from NSUbiquitousKeyValueStore.

| Parameter | Type | Description | |---|---|---| | key | string | The key to read |

Returns the stored string, or null if the key doesn't exist. Returns null on Android.

const value = await iCloudKVS.get('app_config');
if (value) {
  const config = JSON.parse(value);
  console.log('Theme:', config.theme);
}

iCloudKVS.remove(key)

Remove a key from NSUbiquitousKeyValueStore. The removal is synced across all of the user's devices.

| Parameter | Type | Description | |---|---|---| | key | string | The key to remove |

await iCloudKVS.remove('app_config');

Error Handling

CloudKit errors are mapped to typed exceptions:

| Error | Cause | |---|---| | ICloudNotAvailableException | User is not signed into iCloud | | ICloudQuotaExceededException | iCloud storage is full | | ICloudNetworkException | Network unavailable or connection failed | | ICloudRecordNotFoundException | Record ID does not exist | | ICloudRateLimitedException | Too many requests; includes retry-after interval | | ICloudException | Any other CloudKit error |

try {
  await iCloud.save('MyRecord', { key: 'value' });
} catch (error) {
  // error.message contains the specific reason
  console.error('CloudKit error:', error.message);
}

Architecture Notes

  • All CloudKit operations use the private database with a custom record zone (RNICloudKitZone). The zone is created automatically on the first operation and cached via UserDefaults to avoid redundant network calls.
  • Operations run with .userInitiated QoS (Apple's default for CloudKit is low priority).
  • The save function uses savePolicy: .allKeys, which means all fields are written on every save. This makes deterministic IDs safe for overwrites -- the entire record is replaced, not merged.
  • Queries paginate in batches of 200 (CloudKit's recommended page size) using cursor-based pagination.
  • Android: iCloud.isAvailable() returns false, iCloudKVS.get() returns null. All other methods throw with "iCloud is only available on iOS".

License

MIT