React Native SDK
Use Koolbase in your React Native app — auth (email, OAuth, phone), database, semantic search, storage with custom metadata, realtime, feature flags, functions, an offline write queue with conflict resolution, analytics and cloud messaging in one TypeScript package. The same core powers @koolbase/js in the browser.
Using Flutter? This page covers the React Native SDK. For Flutter (https://pub.dev/packages/koolbase_flutter), see the Flutter SDK guide.
Installation
npm install @koolbase/react-nativeyarn add @koolbase/react-nativepnpm add @koolbase/react-nativebun add @koolbase/react-nativeOptional peer dependency for persistent sessions
react-native-keychain (≥ 8.0.0) to enable secure session persistence (iOS Keychain + Android Keystore). Without it, the SDK runs without persistence rather than crashing — sessions are kept in memory only.Setup
Initialize the SDK once at app startup, then restore any persisted session before rendering screens that depend on auth state.
import { useEffect, useState } from 'react';
import {
Koolbase,
RestoreResult,
} from '@koolbase/react-native';
export default function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
await Koolbase.initialize({
publicKey: 'pk_live_your_key_here',
baseUrl: 'https://api.koolbase.com',
analyticsEnabled: true, // opt-in: off unless true (12.4.0+)
appVersion: '1.0.0',
authTimeout: 10000, // optional, default 10s
});
// Restore any session persisted from a previous launch.
// Optimistic state is populated before any network call, so
// authenticated UI renders immediately if a session exists.
const result = await Koolbase.auth.restoreSession();
switch (result) {
case RestoreResult.Restored:
case RestoreResult.Offline: // optimistic restore, will revalidate
case RestoreResult.NoSession:
case RestoreResult.Expired:
break;
}
setReady(true);
})();
}, []);
if (!ready) return null;
return <Navigation />;
}Get your public key
Authentication
Email/password, social sign-in (Apple & Google), and phone + OTP all return a KoolbaseSession. Sessions persist across launches when react-native-keychain is installed.
Email + password
import { Koolbase } from '@koolbase/react-native';
// Register — password must be at least 8 characters (client-side check).
// Two outcomes: signed in, or account created and awaiting verification.
const result = await Koolbase.auth.register({
email: '[email protected]',
password: 'securepassword',
});
if (result.status === 'verification_required') {
// The account exists. No session — they verify their email first.
showCheckYourEmail(result.user.email);
} else {
showApp();
}
// Login
const session = await Koolbase.auth.login({
email: '[email protected]',
password: 'securepassword',
});
// Attach user to analytics after login
Koolbase.analytics.identify(session.user.id);
// Current user (synchronous read of in-memory state)
const me = Koolbase.auth.currentUser;
// Logout — returns true if the server call succeeded; the local session
// is always cleared regardless.
await Koolbase.auth.logout();
Koolbase.analytics.reset();Auth state listener
Subscribe to auth changes Firebase/Supabase style — fires immediately with the current state, then on every login, logout, or token refresh.
import { useEffect, useState } from 'react';
import { Koolbase, KoolbaseUser } from '@koolbase/react-native';
export function useUser() {
const [user, setUser] = useState<KoolbaseUser | null>(null);
useEffect(() => {
const unsubscribe = Koolbase.auth.onAuthStateChange((state) => {
setUser(state.user);
});
return () => unsubscribe();
}, []);
return user;
}Social sign-in
Koolbase is library-agnostic: get the identity token from any native sign-in package, and the server verifies it against the provider's JWKS.
import { Koolbase } from '@koolbase/react-native';
// Apple — identityToken comes from a native Apple Sign-In package
// (e.g. @invertase/react-native-apple-authentication).
const appleSession = await Koolbase.auth.signInWithApple({
identityToken,
nonce,
// fullName is only available on the user's FIRST sign-in
fullName: { givenName: 'Ada', familyName: 'Lovelace' },
});
// Google — idToken comes from a native Google Sign-In package
// (e.g. @react-native-google-signin/google-signin).
const googleSession = await Koolbase.auth.signInWithGoogle({ idToken });Auto-link policy
OAuthEmailConflictError if linkage is ambiguous.Phone + OTP
import { Koolbase } from '@koolbase/react-native';
// 1. Send a one-time code over SMS (E.164 format required)
const { expiresAt } = await Koolbase.auth.sendOtp({
phoneNumber: '+233200000000',
});
// 2. Verify the code to sign in (creates the user if new)
const { session, isNewUser } = await Koolbase.auth.verifyOtp({
phoneNumber: '+233200000000',
code: '123456',
});
// Or link a phone number to the currently signed-in user
await Koolbase.auth.linkPhone({
phoneNumber: '+233200000000',
code: '123456',
});Password reset
import { Koolbase } from '@koolbase/react-native';
// 1. Trigger the reset email
await Koolbase.auth.forgotPassword({ email: '[email protected]' });
// 2. Consume the token from the email link to set a new password
await Koolbase.auth.resetPassword({
token: 'token-from-email-link',
password: 'newSecurePassword',
});Account unlock
After too many failed login attempts, an account is locked and the user receives an unlock email with a one-shot token.
await Koolbase.auth.unlock('token-from-unlock-email');See Authentication for the full auth reference, including session lifecycle and typed errors.
Database
CRUD against typed collections. insert and update and delete are online-first with offline fallback — the authoritative server record on success, queued locally on network failure. A server rejection is never queued: it surfaces immediately, since it would be refused again on every retry.
import { Koolbase } from '@koolbase/react-native';
// Insert — returns the authoritative server record on success
const post = await Koolbase.db.insert('posts', {
title: 'Hello Koolbase',
published: true,
});
// Query
const { records, total } = await Koolbase.db.query('posts', {
filters: { published: true },
limit: 10,
orderBy: 'created_at',
orderDesc: true,
});
// Populate related records (one or many)
const { records: postsWithAuthor } = await Koolbase.db.query('posts', {
populate: ['author_id:users', 'category_id:categories'],
});
// Get by id
const single = await Koolbase.db.get('record-id');
// Update
await Koolbase.db.update('record-id', { title: 'Updated' });
// Delete
await Koolbase.db.delete('record-id');Upsert & bulk delete
// Upsert — insert or update by a match filter; online-only.
// Throws KoolbaseConflictError on unique-constraint violation.
const { record, created } = await Koolbase.db.upsert(
'user_settings',
{ user_id: userId }, // match
{ user_id: userId, theme: 'dark' } // data
);
// Bulk delete by filter — returns the number of records deleted.
const removed = await Koolbase.db.deleteWhere('notifications', {
user_id: userId,
read: true,
});Batch transactions
Run multiple writes as a single atomic transaction — all operations commit together or none are applied. Online-only by design; throws on network failure rather than queuing.
import { Koolbase, BatchOp } from '@koolbase/react-native';
const results = await Koolbase.db.batch([
BatchOp.insert('orders', { user_id: userId, total: 4999 }),
BatchOp.update('cart', cartId, { checked_out: true }),
BatchOp.upsert('inventory', { sku: 'A1' }, { sku: 'A1', count: -1 }),
BatchOp.delete('cart_items', itemId),
]);
// One BatchResult per operation, in order.
results.forEach(r => console.log(r));Typed data errors
KoolbaseDataError selected from the server's stable error code: KoolbaseNotFoundError, KoolbaseValidationError, KoolbasePermissionError, KoolbaseRateLimitError, and KoolbaseConflictError (carries the offending field). See Database for the full reference.Offline-first database
Queries return cached data instantly and refresh from the network in the background. insert, update, and delete queue on network failure and replay on reconnect — an update or delete only if the record has been read on this device, so the change has something to be applied against. A write the server refuses is held for a decision, with the server's message, and nothing is ever dropped.
const { records, isFromCache } = await Koolbase.db.query('posts', {
filters: { published: true },
limit: 20,
});
if (isFromCache) console.log('Served from local cache');
// Manual sync — usually unnecessary, NetInfo triggers it on reconnect
await Koolbase.db.syncPendingWrites();The full model — baselines, conflict reasons, the four resolutions, and what happens to each kind of failure — is documented once at Offline Support, with React Native examples throughout.
Semantic Search
Search records by meaning, not by field equality. Configure a vector field on a collection with an embedding provider (Gemini or OpenAI) and a source field — records are auto-embedded on every insert and update. At query time, pass the user's question as plain text; the server embeds it through the same provider before running HNSW similarity search. Zero embedding code on the client.
Search with text
Most common usage. The server embeds the query inline using the vector field's configured provider — no client-side embedding model required.
import { Koolbase } from '@koolbase/react-native';
const result = await Koolbase.db.searchSemantic({
collection: 'faqs',
field: 'question_embedding',
queryText: 'how do I cancel my subscription?',
limit: 5,
// Optional: equality filter applied alongside vector search
where: { is_published: true },
});
// result.hits is SemanticHit[] — each carries record + cosine distance.
// Lowest distance = closest match. For display: similarity = 1 - distance.
result.hits.forEach((hit) => {
console.log(hit.record, 'distance:', hit.distance);
});
console.log(`${result.total} matches`);Search with a pre-computed vector
Use when you've already embedded the query on the client (advanced; most apps should use queryText).
const result = await Koolbase.db.searchSemantic({
collection: 'faqs',
field: 'question_embedding',
queryVector: precomputedVector,
limit: 5,
});Manual vector operations
When you need finer control — your own embedding model, hybrid storage strategies, or testing. Manual writes override any auto-embedded vector on the same record.
// Write a vector for a record. The field must already be declared on
// the collection; the vector's length must match its dimension.
await Koolbase.db.setVector(recordId, 'question_embedding', vector);
// Read it back as KoolbaseVector { recordId, fieldName, vector, createdAt, updatedAt }
const stored = await Koolbase.db.getVector(recordId, 'question_embedding');
console.log(`${stored.vector.length}-dim, last updated ${stored.updatedAt}`);
// Remove a record's vector slot (the field declaration stays intact)
await Koolbase.db.deleteVector(recordId, 'question_embedding');For backfilling embeddings on records that pre-date the auto-embed hook, use Koolbase.db.embedText({recordId, field, text?}). See Vectors for the full signature.
Vector fields are declared from the dashboard or CLI
koolbase vector-fields create <collection> <name> --dimensions 1536 --provider gemini --model gemini-embedding-001 --source-field <field>. Supported dimensions: 384, 768, 1024, 1536.Online-only
KoolbaseVectorDimensionMismatchError is thrown when a setVector call's length doesn't match the declared dimension.See Vectors for the full reference, or walk through the RAG Knowledge Base tutorial for an end-to-end FAQ assistant.
Storage
Upload files to R2-backed buckets, attach custom key/value metadata, and retrieve them via short-lived signed download URLs. The SDK runs a presigned 3-step flow under the hood (presign → R2 PUT → confirm) so file bytes go directly to R2 without round-tripping through Koolbase.
import { Koolbase } from '@koolbase/react-native';
// Upload — returns { object, downloadUrl }
const { object, downloadUrl } = await Koolbase.storage.upload({
bucket: 'avatars',
path: `user-${userId}.jpg`,
file: {
uri: imageResult.assets[0].uri,
name: 'avatar.jpg',
type: 'image/jpeg',
},
// Optional: attach custom metadata at upload time
metadata: {
user_id: userId,
orig_filename: 'IMG_0942.HEIC',
content_version: '1',
},
// Safe-by-default: rejects existing paths unless overwrite is true
overwrite: false,
});
console.log(object.id, object.size, object.metadata);
// Signed download URL on demand
const url = await Koolbase.storage.getDownloadUrl(
'avatars',
`user-${userId}.jpg`,
);
// Delete
await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);Update metadata
Edit metadata independently of the file bytes. Uses merge semantics — string values set or update keys, null deletes them, omitted keys are left as-is.
await Koolbase.storage.updateMetadata('avatars', `user-${userId}.jpg`, {
alt_text: 'Profile photo',
orig_filename: null, // delete this key
// any other existing key is left untouched
});Typed storage errors
KoolbaseStorageError subclasses with one branch per failure mode: KoolbaseStorageConflictError (path collision when overwrite is false), KoolbaseStorageQuotaError (bucket size cap), KoolbaseStorageFileTooLargeError (per-file size cap), KoolbaseStorageMimeTypeError (content-type allowlist), and KoolbaseStorageMetadataInvalidError (metadata validation — exposes a detail field naming the failing key and rule). See Custom Metadata for the validation budget.Realtime
Stream created, updated, and deleted events from a collection. Auto-reconnects; authenticated as the signed-in user.
import { Koolbase } from '@koolbase/react-native';
useEffect(() => {
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
if (event.type === 'created') {
setMessages(prev => [event.record!, ...prev]);
}
if (event.type === 'updated') {
setMessages(prev =>
prev.map(m => (m.id === event.record!.id ? event.record! : m)),
);
}
if (event.type === 'deleted') {
// deleted events don't carry a record — use recordId
setMessages(prev => prev.filter(m => m.id !== event.recordId));
}
});
return () => unsubscribe();
}, []);Functions
Invoke deployed Functions. The signed-in user's access token is forwarded automatically — Functions receive caller identity via ctx.auth.
import { Koolbase } from '@koolbase/react-native';
const result = await Koolbase.functions.invoke('process-payment', {
amount: 4999,
currency: 'USD',
userId: user.id,
});
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}See Invoke from Flutter / RN for the auth model, retries, and error shapes.
Feature flags & remote config
import { Koolbase } from '@koolbase/react-native';
// Feature flag
if (Koolbase.isEnabled('new_checkout')) {
// show new checkout UI
}
// Remote config — typed accessors with required defaults
const timeout = Koolbase.configNumber('timeout_seconds', 30);
const apiUrl = Koolbase.configString('api_url', 'https://api.myapp.com');
const dark = Koolbase.configBool('force_dark_mode', false);
// Version enforcement
const result = Koolbase.checkVersion('1.2.3');
if (result.status === 'force_update') {
// block the app and show update screen
}Analytics
Track screen views, custom events, and user behaviour. Events are batched and flushed automatically — no manual setup required beyond enabling analytics in your config.
import { Koolbase } from '@koolbase/react-native';
// Custom event
Koolbase.analytics.track('purchase', {
value: 1200,
currency: 'GHS',
product_id: 'prod_123',
});
// Screen view
Koolbase.analytics.screenView('checkout');
// User identity — call after login
Koolbase.analytics.identify(user.id);
Koolbase.analytics.setUserProperty('plan', 'pro');
Koolbase.analytics.setUserProperties({ plan: 'pro', country: 'GH' });
// On logout
Koolbase.analytics.reset();
// Manual flush
await Koolbase.analytics.flush();Auto events
app_open. Call screenView() on each navigation. session_end fires when dispose() is called.Batch flush triggers
dispose() is called. Failed flushes are re-queued and retried.Cloud Messaging
Register a device's push token with Koolbase so you can deliver notifications. The token comes from your native push library (for example @react-native-firebase/messaging) — Koolbase stores it against the device and, optionally, the signed-in user.
import { Platform } from 'react-native';
import { Koolbase } from '@koolbase/react-native';
export const metadata = {
title: 'React Native — SDK',
description: 'Use Koolbase in your React Native app — auth (email, OAuth, phone), database, semantic search, storage with custom metadata, realtime, feature flags, functio...',
}
// Register the device's push token (obtained from your native FCM library)
await Koolbase.messaging.registerToken({
token: fcmToken,
platform: Platform.OS === 'ios' ? 'ios' : 'android',
userId: user.id, // optional — associates the token with a user
});// Send a notification to a specific device token
await Koolbase.messaging.send({
to: fcmToken,
title: 'New message',
body: 'You have a new message',
data: { screen: 'inbox' },
});Delivery
registerToken and send return a boolean indicating whether the request succeeded.Error handling
Every domain throws typed errors that extend a domain base class — so you can branch on the specific failure mode, or fall back to the base class for catch-all handling. No string matching required.
import {
KoolbaseAuthError,
InvalidCredentialsError,
AccountLockedError,
RateLimitError,
KoolbaseDataError,
KoolbaseConflictError,
KoolbaseStorageError,
KoolbaseStorageConflictError,
KoolbaseStorageMetadataInvalidError,
} from '@koolbase/react-native';
try {
await Koolbase.auth.login({ email, password });
} catch (err) {
if (err instanceof InvalidCredentialsError) {
// wrong email/password
} else if (err instanceof AccountLockedError) {
// err.lockedUntil — show "try again at X" message
} else if (err instanceof RateLimitError) {
// too many attempts
} else if (err instanceof KoolbaseAuthError) {
// any other auth failure
}
}
try {
await Koolbase.storage.upload({ bucket, path, file, overwrite: false });
} catch (err) {
if (err instanceof KoolbaseStorageConflictError) {
// path already exists — prompt to overwrite
} else if (err instanceof KoolbaseStorageMetadataInvalidError) {
console.error(err.detail); // names the failing key + rule
} else if (err instanceof KoolbaseStorageError) {
// any other storage failure
}
}Error hierarchy
KoolbaseAuthError ← InvalidCredentialsError, EmailAlreadyInUseError, UserDisabledError, WeakPasswordError, SessionExpiredError, TokenRevokedError, AccountLockedError, UnlockTokenInvalidError, RateLimitError, NetworkError, OAuth + OTP errors.Database:
KoolbaseDataError ← KoolbaseNotFoundError, KoolbaseValidationError, KoolbasePermissionError, KoolbaseRateLimitError, KoolbaseConflictError.Storage:
KoolbaseStorageError ← KoolbaseStorageNotFoundError, KoolbaseStorageValidationError, KoolbaseStoragePermissionError, KoolbaseStorageConflictError, KoolbaseStorageQuotaError, KoolbaseStorageFileTooLargeError, KoolbaseStorageMimeTypeError, KoolbaseStorageMetadataInvalidError.See the Errors reference for wire codes and HTTP statuses.
Source & contributing
The React Native SDK is open source at github.com/kennedyowusu/koolbase-react-native. Issues and PRs welcome.