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

@nauverse/expo-cloud-settings

v1.3.1

Published

iCloud key-value storage sync across devices

Readme

expo-cloud-settings

npm version CI Release License: MIT

An Expo module wrapping Apple's NSUbiquitousKeyValueStore for iCloud key-value sync across devices. Hooks-first React API with change listeners.

Android returns no-op (null values, isAvailable() === false) so you can use the same API on both platforms without crashes.

Features

  • React hooks: useCloudSetting, useCloudSettingBool, useCloudSettingNumber, useCloudSettingObject
  • Shared context provider - one event listener, one cache, no duplicate native reads
  • Change event listeners for cross-device sync
  • Config plugin - no manual Xcode entitlement setup
  • Typed helpers: setBool, setNumber, setObject<T>
  • clear() to remove all keys
  • isAvailable() runtime platform check (checks iCloud sign-in status)
  • Android no-op module (safe to call, returns null)

Installation

npx expo install @nauverse/expo-cloud-settings

Add the config plugin to your app.config.ts (or app.json):

export default {
  plugins: ['@nauverse/expo-cloud-settings'],
};

This automatically adds the following entitlements to your iOS build:

  • com.apple.developer.ubiquity-kvstore-identifier - iCloud Key-Value Storage identifier
  • com.apple.developer.icloud-container-identifiers - enables the iCloud capability
  • com.apple.developer.icloud-services - declares CloudKit service usage

These entitlements ensure that EAS Build (and Xcode) correctly enable the iCloud capability on your App ID and provisioning profile.

Custom container identifier

By default the KVS identifier is $(TeamIdentifierPrefix)$(CFBundleIdentifier) (Apple's recommended default). To share a KVS container across multiple apps:

export default {
  plugins: [
    ['@nauverse/expo-cloud-settings', { containerIdentifier: '$(TeamIdentifierPrefix)com.example.shared' }],
  ],
};

Setup

Wrap your app with CloudSettingsProvider. This sets up a single shared event listener and in-memory cache for all hooks:

import { CloudSettingsProvider } from '@nauverse/expo-cloud-settings';

export default function App() {
  return (
    <CloudSettingsProvider>
      <MyApp />
    </CloudSettingsProvider>
  );
}

All useCloudSetting* hooks must be descendants of this provider.

Usage

Hooks (recommended)

import { useCloudSetting, useCloudSettingBool, isAvailable } from '@nauverse/expo-cloud-settings';

function Settings() {
  const [username, setUsername] = useCloudSetting('username', 'Guest');
  const [darkMode, setDarkMode] = useCloudSettingBool('darkMode', false);

  return (
    <View>
      <Text>iCloud available: {String(isAvailable())}</Text>
      <Text>Hello, {username}</Text>
      <Button title="Toggle dark mode" onPress={() => setDarkMode(!darkMode)} />
      <Button title="Clear username" onPress={() => setUsername(null)} />
    </View>
  );
}

Multiple components using the same key share the cached value and stay in sync automatically. When a value changes on another device, all hooks for that key re-render with the new value.

useCloudSetting(key, defaultValue?)

Returns readonly [string | null, (value: string | null) => void].

Setting to null removes the key.

useCloudSettingBool(key, defaultValue?)

Returns readonly [boolean | null, (value: boolean | null) => void].

useCloudSettingNumber(key, defaultValue?)

Returns readonly [number | null, (value: number | null) => void].

useCloudSettingObject<T>(key, defaultValue?)

Returns readonly [T | null, (value: T | null) => void].

Values are JSON-serialized. Returns null (or defaultValue) if the stored value is not valid JSON.

interface Preferences {
  theme: string;
  fontSize: number;
}

const [prefs, setPrefs] = useCloudSettingObject<Preferences>('prefs', {
  theme: 'light',
  fontSize: 16,
});

Functional API

For use outside of React components (no provider required):

import {
  getString, setString, remove, getAllKeys, clear, isAvailable,
  getBool, setBool, getNumber, setNumber, getObject, setObject,
  addChangeListener,
} from '@nauverse/expo-cloud-settings';

// String
setString('token', 'abc123');
const token = getString('token'); // 'abc123' | null

// Boolean
setBool('notifications', true);
const enabled = getBool('notifications'); // true | null

// Number
setNumber('launchCount', 5);
const count = getNumber('launchCount'); // 5 | null

// Object
setObject('user', { name: 'Alice', age: 30 });
const user = getObject<{ name: string; age: number }>('user');

// Keys & cleanup
const keys = getAllKeys(); // string[]
remove('token');
clear(); // removes all keys

// Platform check
if (isAvailable()) {
  // iCloud KVS is available (iOS with iCloud signed in)
}

Change listener

Listen for changes pushed from other devices:

import { addChangeListener } from '@nauverse/expo-cloud-settings';

const subscription = addChangeListener((event) => {
  console.log('Changed keys:', event.changedKeys);
  console.log('Reason:', event.reason);
  // reason: 'serverChange' | 'initialSync' | 'quotaViolation' | 'accountChange'
});

// Clean up
subscription.remove();

Architecture

The CloudSettingsProvider creates a single CloudSettingsStore that:

  1. Maintains an in-memory cache of all read keys
  2. Registers one native event listener for onStoreChanged
  3. When a change event arrives, re-reads only the affected keys from native
  4. Notifies all subscribed hooks via useSyncExternalStore

This means:

  • 10 components reading the same key = 1 native read (not 10)
  • 1 event listener total (not 1 per hook)
  • No race conditions between mount and subscription

iCloud KVS limits

| Limit | Value | |-------|-------| | Total storage | 1 MB | | Maximum keys | 1024 | | Per-key size | 1 MB |

Exceeding limits may trigger a quotaViolation change event and cause data loss.

Security

iCloud KVS data is stored in the user's iCloud account and is not encrypted at the application level. Do not store sensitive data such as passwords, tokens, API keys, or personal health information. For sensitive data, use expo-secure-store or a server-side solution.

Sync behavior

  • Auto-syncs across devices signed into the same iCloud account
  • Background sync when network is available
  • The system coalesces writes and syncs periodically (not per-write)
  • First sync after app launch may take a few moments
  • Works offline - changes sync when connectivity is restored
  • Sync does not work in the iOS Simulator (device-only)

Platform support

| Platform | Status | |----------|--------| | iOS | Full support via NSUbiquitousKeyValueStore | | Android | No-op (returns null, isAvailable() returns false) - real sync support coming soon via Google Drive App Data | | Web | Not supported |

Android support coming soon. The Android module currently acts as a safe no-op so your code works on both platforms without crashes. Real cross-device sync on Android (via Google Drive App Data) is on the roadmap. Follow the repo for updates.

API reference

Types

type CloudSettingsChangeReason =
  | 'serverChange'    // Another device changed values
  | 'initialSync'     // First sync after app launch
  | 'quotaViolation'  // Storage limit exceeded
  | 'accountChange';  // iCloud account changed

type CloudSettingsChangeEvent = {
  readonly changedKeys: ReadonlyArray<string>;
  readonly reason: CloudSettingsChangeReason;
};

Acknowledgements

Big thanks to https://github.com/okwasniewski/expo-icloud-storage I was initially using that repository in my projects and it was really great. This repository started because I needed specific changes (like the hooks and Android support) and creating a merge request there would change the project a lot, but if not, I would have just done that. Again, thanks for the inspiration!

License

MIT