Quick Start
Get up and running with the Userbubble React Native SDK in 5 minutes
Step 1: Install the SDK
Follow the installation guide to install the SDK and get your API key.
Step 2: Wrap Your App
Wrap your root component with UserbubbleProvider:
import { UserbubbleProvider } from "@userbubble/react-native";
export default function App() {
return (
<UserbubbleProvider
config={{
apiKey: process.env.EXPO_PUBLIC_USERBUBBLE_API_KEY!,
debug: __DEV__, // Enable debug logging in development
}}
>
<YourAppContent />
</UserbubbleProvider>
);
}Step 3: Identify Users
Use the useUserbubble() hook to identify users after they log in:
import { useUserbubble } from "@userbubble/react-native";
import { Button } from "react-native";
function LoginScreen() {
const { identify } = useUserbubble();
const handleLogin = async () => {
// Your login logic here
const user = await yourLoginFunction();
// Identify the user with Userbubble
await identify({
id: user.id, // Your user ID
email: user.email, // User email
name: user.name, // Optional: user name
avatar: user.avatar, // Optional: avatar URL
});
// Navigate to home screen
navigation.navigate("Home");
};
return <Button title="Login" onPress={handleLogin} />;
}Step 4: Show the Feedback Portal
The recommended approach is opening the portal in a modal / form sheet using getEmbedUrl() with a WebView. The embed view includes its own tab navigation (Feedback, Roadmap, Updates) and user info — you just render a WebView.
With Expo Router:
// app/_layout.tsx — register the modal route
<Stack.Screen
name="feedback"
options={{
presentation: "formSheet",
headerShown: false,
sheetGrabberVisible: true,
}}
/>// app/feedback.tsx — the modal screen
import { useUserbubble } from "@userbubble/react-native";
import { ActivityIndicator, View } from "react-native";
import { WebView } from "react-native-webview";
function LoadingIndicator() {
return (
<View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" />
</View>
);
}
export default function FeedbackModal() {
const { getEmbedUrl } = useUserbubble();
const url = getEmbedUrl("/feedback");
if (!url) return null;
return (
<View style={{ flex: 1 }}>
<WebView
source={{ uri: url }}
style={{ flex: 1 }}
javaScriptEnabled
domStorageEnabled
startInLoadingState
renderLoading={() => <LoadingIndicator />}
/>
</View>
);
}// Open from anywhere in your app
import { router } from "expo-router";
<Button title="Give Feedback" onPress={() => router.push("/feedback")} />getEmbedUrl() returns a fully authenticated URL. The embed view handles navigation between feedback, roadmap, and changelog — no need to build native tabs.
Alternative: Open in device browser
If you prefer opening Userbubble in an external browser instead:
const { openUserbubble } = useUserbubble();
// Opens in the device's default browser with full portal header
await openUserbubble("/feedback");Complete Example
Here's a complete working example with a form sheet modal:
import React, { useState } from "react";
import { ActivityIndicator, Alert, Modal, Pressable, SafeAreaView, Text, TextInput, View } from "react-native";
import {
UserbubbleProvider,
useUserbubble,
} from "@userbubble/react-native";
import { WebView } from "react-native-webview";
function LoadingIndicator() {
return (
<View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" />
</View>
);
}
export default function App() {
return (
<UserbubbleProvider
config={{
apiKey: process.env.EXPO_PUBLIC_USERBUBBLE_API_KEY!,
debug: __DEV__,
}}
>
<MyApp />
</UserbubbleProvider>
);
}
function MyApp() {
const { identify, getEmbedUrl, isIdentified, user, logout } = useUserbubble();
const [showFeedback, setShowFeedback] = useState(false);
const url = getEmbedUrl("/feedback");
if (!isIdentified) {
return <IdentifyForm identify={identify} />;
}
return (
<View style={{ flex: 1, justifyContent: "center", padding: 24, gap: 12 }}>
<Text style={{ fontSize: 20, fontWeight: "bold", textAlign: "center" }}>
Welcome, {user?.name || user?.email}
</Text>
<Pressable
onPress={() => setShowFeedback(true)}
style={{ backgroundColor: "#3b82f6", borderRadius: 8, padding: 14, alignItems: "center" }}
>
<Text style={{ color: "#fff", fontWeight: "600" }}>Give Feedback</Text>
</Pressable>
<Pressable onPress={logout} style={{ padding: 14, alignItems: "center" }}>
<Text style={{ color: "#888" }}>Logout</Text>
</Pressable>
{url ? (
<Modal
visible={showFeedback}
animationType="slide"
presentationStyle="formSheet"
onRequestClose={() => setShowFeedback(false)}
>
<SafeAreaView style={{ flex: 1 }}>
<WebView
source={{ uri: url }}
style={{ flex: 1, minHeight: 0 }}
javaScriptEnabled
domStorageEnabled
startInLoadingState
renderLoading={() => <LoadingIndicator />}
/>
</SafeAreaView>
</Modal>
) : null}
</View>
);
}
function IdentifyForm({
identify,
}: {
identify: (user: { id: string; email: string; name?: string }) => Promise<void>;
}) {
const [email, setEmail] = React.useState("");
const [name, setName] = React.useState("");
const handleIdentify = async () => {
try {
await identify({
id: "user_123",
email: email || "user@example.com",
name: name || "Test User",
});
} catch (error) {
Alert.alert("Error", `Failed to identify: ${error}`);
}
};
return (
<View style={{ flex: 1, justifyContent: "center", padding: 24 }}>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={{ borderWidth: 1, borderColor: "#d4d4d4", borderRadius: 8, padding: 12, marginBottom: 12 }}
/>
<TextInput
placeholder="Name"
value={name}
onChangeText={setName}
style={{ borderWidth: 1, borderColor: "#d4d4d4", borderRadius: 8, padding: 12, marginBottom: 12 }}
/>
<Pressable
onPress={handleIdentify}
style={{ backgroundColor: "#3b82f6", borderRadius: 8, padding: 14, alignItems: "center" }}
>
<Text style={{ color: "#fff", fontWeight: "600" }}>Continue</Text>
</Pressable>
</View>
);
}Configuration Options
The config object accepts these options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | Required | Your organization's API key |
baseUrl | string | "https://app.userbubble.com" | Base URL for the Userbubble service |
debug | boolean | false | Enable debug logging |
storageType | "expo" | "async-storage" | "auto" | "auto" | Storage type (auto-detect recommended) |
customStorage | StorageAdapter | undefined | Custom storage implementation |
Next Steps
- Learn about all available methods in the API Reference
- See more examples in the Examples page
Enable debug: __DEV__ during development to see helpful logs about user identification and SDK operations.