feat: whyrating - initial project from turbostarter boilerplate

This commit is contained in:
Alejandro Gutiérrez
2026-02-04 01:54:52 +01:00
commit 5cdc07cd39
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
---
title: AI
description: Learn how to use AI integration in your mobile app.
url: /docs/mobile/ai
---
# AI
As AI integration for [web](/docs/web/ai/overview), [extension](/docs/extension/ai), and mobile is based on the same battle-tested [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction), the implementation is very similar across platforms.
In this section, we'll focus on how to consume AI responses in the mobile app. For server-side implementation details, please refer to the [web documentation](/docs/web/ai/overview).
## Features
The most common AI integration features are also supported in the mobile app:
* **Chat**: Build chat interfaces inside native mobile apps.
* **Streaming**: Receive AI responses as soon as the model starts generating them, without waiting for the full response to be completed.
* **Image generation**: Generate images based on a given prompt.
You can easily compose your application using these building blocks or extend them to suit your specific needs.
## Usage
The usage of AI integration in the mobile app is the same as for [web app](/docs/web/ai/configuration#client-side) and [browser extension](/docs/extension/ai#server--client). We use the exact same [API endpoint](/docs/web/ai/configuration#api-endpoint), and since TurboStarter ships with built-in support for streaming on mobile, we can leverage it to display answers incrementally to the user as they're generated.
```tsx title="ai.tsx"
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
const AI = () => {
const { messages } = useChat({
transport: new DefaultChatTransport({
api: "/api/ai/chat",
}),
});
return (
<View>
{messages.map((message) => (
<Text key={message.id}>
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <Text key={`${message.id}-${i}`}>{part.text}</Text>;
}
})}
</Text>
))}
</View>
);
};
export default AI;
```
By leveraging this integration, we can easily manage the state of the AI request and update the UI as soon as the response is ready.
TurboStarter ships with a ready-to-use implementation of AI chat, allowing you to see this solution in action. Feel free to reuse or modify it according to your needs.

View File

@@ -0,0 +1,161 @@
---
title: Configuration
description: Learn how to configure mobile analytics in TurboStarter.
url: /docs/mobile/analytics/configuration
---
# Configuration
The `@turbostarter/analytics-mobile` package offers a streamlined and flexible approach to tracking events in your TurboStarter mobile app using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking.
In this section, we'll guide you through the configuration process for each supported provider.
Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured.
## Permissions
First and foremost, to start tracking any metrics from your app (and to do so legally), you need to ask your users for permission. It's [required](https://support.apple.com/en-us/102420), and you're not allowed to collect any data without it.
To make this process as simple as possible, TurboStarter comes with a `useTrackingPermissions` hook that you can use to access the user's consent status. It will handle asking for permission automatically as well as process updates made through the general phone settings.
```tsx
import { useTrackingPermissions } from "@turbostarter/analytics-mobile";
export const MyComponent = () => {
const granted = useTrackingPermissions();
if (granted) {
// Start tracking
} else {
// Disable tracking
}
};
```
Also, for Apple, you must declare the tracking justification via [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency). It comes pre-configured in TurboStarter via the [Expo Config Plugin](https://docs.expo.dev/versions/latest/config/app/#plugins), where you can provide a custom message to the user:
```ts title="app.config.ts"
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
plugins: [
[
"expo-tracking-transparency",
{
/* 🍎 Describe why you need access to the user's data */
userTrackingPermission:
"This identifier will be used to deliver personalized ads to you.",
},
],
],
});
```
This way, we ensure that the user is aware of the data we collect and can make an informed decision. If you don't provide this information, your app is likely to be rejected by Apple and/or Google during the [review process](/docs/mobile/publishing/checklist#send-to-review).
## Providers
TurboStarter supports multiple analytics providers, each with its own unique configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section.
<Accordions>
<Accordion title="Google Analytics" id="google-analytics">
To use Google Analytics as your analytics provider, you need to [configure and link a Firebase project to your app](/docs/mobile/installation/firebase).
After that, you can proceed with the installation of the analytics package:
```bash
pnpm add --filter @turbostarter/analytics-mobile @react-native-firebase/analytics
```
Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in:
```ts title="index.ts"
// [!code word:google-analytics]
export * from "./google-analytics";
export * from "./google-analytics/env";
```
To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/google-analytics` directory.
For more information, please refer to the [React Native Firebase documentation](https://rnfirebase.io/analytics/usage).
![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg)
</Accordion>
<Accordion title="PostHog" id="posthog">
<Callout type="info" title="You can also use it for monitoring!">
PostHog is also one of pre-configured providers for [monitoring](/docs/mobile/monitoring/overview) in TurboStarter mobile apps. You can learn more about it [here](/docs/mobile/monitoring/posthog).
</Callout>
To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it.
Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your `eas.json` file:
```dotenv
EXPO_PUBLIC_POSTHOG_KEY="your-posthog-api-key"
EXPO_PUBLIC_POSTHOG_HOST="your-posthog-instance-host"
```
Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in:
```ts title="index.ts"
// [!code word:posthog]
export * from "./posthog";
export * from "./posthog/env";
```
To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/posthog` directory.
For more information, please refer to the [PostHog documentation](https://posthog.com/docs).
![PostHog dashboard](/images/docs/web/analytics/posthog.png)
</Accordion>
<Accordion title="Mixpanel" id="mixpanel">
To use Mixpanel as your analytics provider, you need to [create an account](https://mixpanel.com/) and [obtain your project token](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token).
Then, set it as an environment variable in your `.env.local` file in the `apps/mobile` directory and your `eas.json` file:
```dotenv
EXPO_PUBLIC_MIXPANEL_TOKEN="your-project-token"
```
Also, make sure to activate the Mixpanel provider as your analytics provider by updating the exports in:
```ts title="index.ts"
// [!code word:mixpanel]
export * from "./mixpanel";
export * from "./mixpanel/env";
```
To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/mixpanel` directory.
For more information, please refer to the [Mixpanel documentation](https://docs.mixpanel.com/).
</Accordion>
</Accordions>
## Context
To enable tracking events, capturing screen views and other analytics features, you need to wrap your app with the `Provider` component that's implemented by every provider and available through the `@turbostarter/analytics-mobile` package:
```tsx title="providers.tsx"
// [!code word:AnalyticsProvider]
import { memo } from "react";
import { Provider as AnalyticsProvider } from "@turbostarter/analytics-mobile";
interface ProvidersProps {
readonly children: React.ReactNode;
}
export const Providers = memo<ProvidersProps>(({ children }) => {
return (
<OtherProviders>
<AnalyticsProvider>{children}</AnalyticsProvider>
</OtherProviders>
);
});
Providers.displayName = "Providers";
```
By implementing this setup, you ensure that all analytics events are properly tracked from your mobile app code. This configuration allows you to safely utilize the [Analytics API](/docs/mobile/analytics/tracking) within your components, enabling comprehensive event tracking and data collection.

View File

@@ -0,0 +1,50 @@
---
title: Overview
description: Get started with mobile analytics in TurboStarter.
url: /docs/mobile/analytics/overview
---
# Overview
When it comes to mobile app analytics, we can distinguish between two types:
* **Store listing analytics**: Used to track the performance of your mobile app's store listing (e.g., how many people have viewed your app in the store or how many have installed it).
* **In-app analytics**: Tracks user actions within your mobile app (e.g., how many users entered a specific screen, how many users clicked on a specific button, etc.).
The `@turbostarter/analytics-mobile` package provides a set of tools to easily implement both types of analytics in your mobile app.
## Store listing analytics
Interpreting your mobile app's store listing metrics can help you evaluate how changes to your app and store listing affect conversion rates. For example, you can identify keywords that users are searching for to optimize your app's store listing.
While each store implements a different set of metrics, there are some common ones you should be aware of:
* **Downloads**: The total number of times your app was downloaded, including both first-time downloads and re-downloads.
* **Sales**: The total number of pre-orders, first-time app downloads, in-app purchases, and their associated sales.
* **Usage**: A variety of user engagement metrics, such as installations, sessions, crashes, and active devices.
To learn more about these or other metrics (e.g., how to create custom reports or KPIs), please refer to the official documentation of the store you're publishing to:
<Cards>
<Card title="Overview of reporting tools" description="developer.apple.com" href="https://developer.apple.com/help/app-store-connect/measure-app-performance/overview-of-reporting-tools" />
<Card title="View app statistics" description="support.google.com" href="https://support.google.com/googleplay/android-developer/answer/139628?hl=en&co=GENIE.Platform%3DDesktop&oco=1" />
</Cards>
## In-app analytics
TurboStarter comes with built-in analytics support for multiple providers as well as a unified API for tracking events. This API enables you to easily and consistently track user behavior and app usage across your mobile application.
To learn more about each provider and how to configure them, see their respective sections:
<Cards>
<Card title="Google Analytics" href="/docs/mobile/analytics/configuration#google-analytics" />
<Card title="PostHog" href="/docs/mobile/analytics/configuration#posthog" />
<Card title="Mixpanel" href="/docs/mobile/analytics/configuration#mixpanel" />
</Cards>
All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic.
In the following sections, we'll cover how to set up each provider and how to track events in your application.

View File

@@ -0,0 +1,84 @@
---
title: Tracking events
description: Learn how to track events in your TurboStarter mobile app.
url: /docs/mobile/analytics/tracking
---
# Tracking events
The strategy for tracking events that every provider has to implement is extremely simple:
```ts
export type AllowedPropertyValues = string | number | boolean;
type TrackFunction = (
event: string,
data?: Record<string, AllowedPropertyValues>,
) => void;
export interface AnalyticsProviderStrategy {
Provider: ({ children }: { children: React.ReactNode }) => React.ReactNode;
track: TrackFunction;
}
```
<Callout>
You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider.
</Callout>
As shown above, each provider must supply two key elements:
1. `Provider` - a component that [wraps your app](/docs/mobile/analytics/configuration#context).
2. `track` - a function responsible for sending event data to the provider.
To track an event, you simply need to invoke the `track` method, passing the event name and an optional data object:
```tsx
import { track } from "@turbostarter/analytics-mobile";
export const MyComponent = () => {
return (
<Pressable onPress={() => track("button.click", { country: "US" })}>
Track event
</Pressable>
);
};
```
In most mobile apps, you'll only ever need to use the `track` method to track events. You can use it anywhere in your app code—such as in response to user interactions, navigation events, or custom actions - by simply calling `track` with an event name and optional properties.
## Identifying users
Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms.
For identification purposes, we're extending the strategy with the `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID.
```ts
type IdentifyFunction = (
userId: string,
traits?: Record<string, AllowedPropertyValues>,
) => void;
export interface AnalyticsProviderClientStrategy {
identify: IdentifyFunction;
reset: () => void;
}
```
To identify users, call the `identify` method, passing the user's ID and an optional traits object:
```tsx
import { identify } from "@turbostarter/analytics-mobile";
identify("user-123", { name: "John Doe" });
```
This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns.
<Callout title="Configured by default!">
The `identify` method is configured out-of-the-box to react on changes to the user's authentication state.
When the user is authenticated, the `identify` method will be called with the user's ID and the user's traits. When the user is logged out, the `reset` method will be called to clear the existing user identification.
</Callout>
Congratulations! You've now mastered event tracking in your TurboStarter mobile app. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your application's usage patterns. Happy analyzing!

View File

@@ -0,0 +1,213 @@
---
title: Using API client
description: How to use API client to interact with the API.
url: /docs/mobile/api/client
---
# Using API client
In mobile app code, you can only access the API client from the **client-side.**
When you create a new component or screen and want to fetch some data, you can use the API client to do so.
## Creating a client
We're creating a client-side API client in `apps/mobile/src/lib/api/index.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API.
It also requires wrapping your app in a `QueryClientProvider` component to provide the API client to the rest of the app:
```tsx title="_layout.tsx"
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<QueryClientProvider>
<SafeAreaProvider>
<Stack>
...
<Stack.Screen name="index" />
...
</Stack>
<StatusBar barStyle="light-content" />
</SafeAreaProvider>
</QueryClientProvider>
);
}
```
<Callout type="warn" title="Ensure correct API url">
Inside the `apps/mobile/src/lib/api/utils.ts` file we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your web api endpoint is corresponding with the name there.
```tsx title="utils.ts"
const getBaseUrl = () => {
/**
* Gets the IP address of your host-machine. If it cannot automatically find it,
* you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
* you don't have anything else running on it, or you'd have to change it.
*
* **NOTE**: This is only for development. In production, you'll want to set the
* baseUrl to your production API URL.
*/
const debuggerHost = Constants.expoConfig?.hostUri;
const localhost = debuggerHost?.split(":")[0];
if (!localhost) {
console.warn("Failed to get localhost. Pointing to production server...");
return env.EXPO_PUBLIC_SITE_URL;
}
return `http://${localhost}:3000`;
};
```
As you can see we're relying on your machine IP address for local development (in case you want to open the app from another device) or on the [environment variables](/docs/mobile/configuration/environment-variables) in production to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉
</Callout>
## Queries
Of course, everything comes already configured for you, so you just need to start using `api` in your components/screens.
For example, to fetch the list of posts you can use the `useQuery` hook:
```tsx title="app/(tabs)/tab-one.tsx"
import { api } from "~/lib/api";
export default function TabOneScreen() {
const { data: posts, isLoading } = useQuery({
queryKey: ["posts"],
queryFn: async () => {
const response = await api.posts.$get();
if (!response.ok) {
throw new Error("Failed to fetch posts!");
}
return response.json();
},
});
if (isLoading) {
return <Text>Loading...</Text>;
}
/* do something with the data... */
return (
<View>
<Text>{JSON.stringify(posts)}</Text>
</View>
);
}
```
It's using the `@tanstack/react-query` [useQuery API](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery), so you shouldn't have any troubles with it.
<Cards>
<Card title="Hono RPC" description="hono.dev" href="https://hono.dev/docs/guides/rpc" />
<Card title="useQuery hook | Tanstack Query" description="tanstack.com" href="https://tanstack.com/query/latest/docs/framework/react/reference/useQuery" />
</Cards>
## Mutations
If you want to perform a mutation in your mobile code, you can use the `useMutation` hook that comes straight from the integration with [Tanstack Query](https://tanstack.com/query):
```tsx title="form.tsx"
import { api } from "~/lib/api";
export function CreatePost() {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: async (post: PostInput) => {
const response = await api.posts.$post(post);
if (!response.ok) {
throw new Error("Failed to create post!");
},
},
onSuccess: () => {
toast.success("Post created successfully!");
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
});
return (
<Form>
<Button onPress={onSubmit(mutate)}>Submit</Button>
</Form>
);
}
```
Here, we're also invalidating the query after the mutation is successful. This is a very important step to make sure that the data is updated in the UI.
<Cards>
<Card title="useMutation hook" description="tanstack.com" href="https://tanstack.com/query/latest/docs/framework/react/reference/useMutation" />
<Card title="Query invalidation" description="tanstack.com" href="https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation" />
</Cards>
## Handling responses
As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase.
That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client:
<Tabs items={["Queries", "Mutations"]}>
<Tab value="Queries">
```tsx
// [!code word:handle]
import { handle } from "@turbostarter/api/utils";
import { api } from "~/lib/api";
export default function TabOneScreen() {
const { data: posts, isLoading } = useQuery({
queryKey: ["posts"],
queryFn: handle(api.posts.$get),
});
if (isLoading) {
return <Text>Loading...</Text>;
}
/* do something with the data... */
return (
<View>
<Text>{JSON.stringify(posts)}</Text>
</View>
);
}
```
</Tab>
<Tab value="Mutations">
```tsx
// [!code word:handle]
import { handle } from "@turbostarter/api/utils";
import { api } from "~/lib/api/client";
export default function CreatePost() {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: handle(api.posts.$post),
onSuccess: () => {
toast.success("Post created successfully!");
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
});
return (
<Form>
<Button onPress={onSubmit(mutate)}>Submit</Button>
</Form>
);
}
```
</Tab>
</Tabs>
With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable.
The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms.

View File

@@ -0,0 +1,55 @@
---
title: Overview
description: Get started with the API.
url: /docs/mobile/api/overview
---
# Overview
<Callout type="error" title="API deployment required">
To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first.
<Cards>
<Card title="API" description="Learn more about the API." href="/docs/web/api/overview" />
<Card title="Web deployment" description="Deploy your web application to production." href="/docs/web/deployment/checklist" />
</Cards>
</Callout>
TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library.
<Callout title="Why Hono?">
Hono is a small, simple, and ultrafast web framework that gives you a way to
define your API endpoints with full type safety. It provides built-in
middleware for common needs like validation, caching, and CORS.
It also
includes an [RPC client](https://hono.dev/docs/guides/rpc) for making
type-safe function calls from the frontend. Being edge-first, it's optimized
for serverless environments and offers excellent performance.
</Callout>
All API endpoints and their resolvers are defined in the `packages/api/` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers.
For each module, we create a separate Hono route in the `packages/api/index.ts` file and aggregate all sub-routers into one main router.
The API is then exposed as a route handler that will be provided as a Next.js API route:
```ts title="apps/web/src/app/api/[...route]/route.ts"
import { handle } from "hono/vercel";
import { appRouter } from "@turbostarter/api";
const handler = handle(appRouter);
export {
handler as GET,
handler as POST,
handler as OPTIONS,
handler as PUT,
handler as PATCH,
handler as DELETE,
handler as HEAD,
};
```
Learn more about how to use the API in your mobile app in the following sections:

View File

@@ -0,0 +1,131 @@
---
title: Two-Factor Authentication (2FA)
description: Add an extra layer of security with two-factor authentication in your mobile app.
url: /docs/mobile/auth/2fa
---
# Two-Factor Authentication (2FA)
TurboStarter uses [Better Auth's 2FA plugin](https://www.better-auth.com/docs/plugins/2fa) to provide multi-factor authentication (MFA) capabilities in your mobile app. Two-factor authentication adds an extra layer of security by requiring users to provide a second form of verification alongside their password.
## Available methods
TurboStarter supports multiple 2FA verification methods through Better Auth:
* **TOTP (Time-based One-Time Password)** - codes generated by authenticator apps
* **OTP (One-Time Password)** - codes sent via email or SMS
* **Backup codes** - single-use recovery codes for account recovery
You can use any TOTP-compatible authenticator app, such as:
* [Google Authenticator](https://support.google.com/accounts/answer/1066447)
* [Authy](https://authy.com/)
* [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app)
* [1Password](https://1password.com/features/authenticator/)
* [Bitwarden](https://bitwarden.com/help/authenticator-keys/)
## Enabling 2FA
<Steps>
<Step>
### Enable in settings
Users enable two-factor authentication in their account security settings within the mobile app.
![Enable 2FA](/images/docs/mobile/auth/two-factor/enable.png)
</Step>
<Step>
### Setup authenticator
A QR code is displayed in the mobile app for users to scan with their authenticator app. Users can also manually enter the setup key if needed.
![Setup authenticator](/images/docs/mobile/auth/two-factor/authenticator-app.png)
</Step>
<Step>
### Verify setup
Users enter a verification code from their authenticator to confirm setup directly in the mobile app.
</Step>
<Step>
### Backup codes
Users receive single-use backup codes for account recovery, which can be saved or shared from the mobile app.
![Backup codes](/images/docs/mobile/auth/two-factor/backup-codes.png)
</Step>
</Steps>
<Callout type="info">
Recovery codes are essential for account recovery if users lose access to
their authenticator device. Make sure to educate users about safely storing
their backup codes, and consider providing options to save them to the device
or share them securely.
</Callout>
## Using 2FA
<Steps>
<Step>
### Sign in normally
Users enter their email and password or use other authentication methods (biometric, social login) as usual in the mobile app.
</Step>
<Step>
### 2FA prompt
After successful password verification, users are prompted for their 2FA code in a native mobile interface.
![2FA prompt](/images/docs/mobile/auth/two-factor/sign-in-prompt.png)
</Step>
<Step>
### Enter verification code
Users input the 6-digit code from their authenticator app using the mobile keyboard.
</Step>
<Step>
### Access granted
Upon successful verification, users gain access to their account and are navigated to the main app screen.
</Step>
</Steps>
### Trusted devices
Users can mark their mobile device as trusted during 2FA verification. Trusted devices won't require 2FA verification for 60 days, providing a balance between security and convenience. This is particularly useful for personal mobile devices.
## Mobile-specific considerations
### Biometric integration
On mobile devices, 2FA can be enhanced with biometric authentication (fingerprint, face recognition) for added security and convenience.
### App switching
The mobile app should handle switching between your app and authenticator apps seamlessly, maintaining the authentication state when users return.
### Offline support
Consider implementing offline backup code verification for scenarios where users may have limited connectivity.
### Push notifications
For OTP delivery via SMS or email, ensure your app handles incoming notifications gracefully during the authentication flow.
## Configuration
2FA is configured through Better Auth's plugin system. The plugin handles:
* Secure secret generation and storage
* QR code generation for authenticator setup
* TOTP code validation
* Backup code generation and management
* Trusted device management
* Mobile-specific session handling
For detailed implementation instructions, refer to the [Better Auth 2FA documentation](https://www.better-auth.com/docs/plugins/2fa).

View File

@@ -0,0 +1,122 @@
---
title: Configuration
description: Configure authentication for your application.
url: /docs/mobile/auth/configuration
---
# Configuration
TurboStarter supports multiple different authentication methods:
* **Password** - the traditional email/password method
* **Magic Link** - passwordless email link authentication
* **Anonymous** - guest mode for users who want to proceed anonymously
* **OAuth** - OAuth providers, [Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) are set up by default
All authentication methods are enabled by default, but you can easily customize them to your needs. You can enable or disable any method, and configure them according to your requirements.
<Callout>
Remember that you can mix and match these methods or add new ones - for
example, you can have both password and magic link authentication enabled at
the same time, giving your users more flexibility in how they authenticate.
</Callout>
Authentication configuration can be customized through a simple configuration file. The following sections explain the available options and how to configure each authentication method based on your requirements.
## API
To enable new authentication method or add some plugin, you'd need to update the API configuration. Please refer to [web authentication configuration](/docs/web/auth/configuration) for more information as it's not strictly related with mobile app configuration.
<Callout title="Remember to add your app scheme as trusted origin">
For mobile apps, we need to define an [authentication trusted origin](https://www.better-auth.com/docs/reference/security#trusted-origins) using a mobile app scheme instead.
App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication.
To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration:
```ts title="server.ts"
export const auth = betterAuth({
...
trustedOrigins: ["turbostarter://**"],
...
});
```
Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through.
[Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security)
</Callout>
## UI
We have separate configuration that determines what is displayed to your users in the **UI**. It's set at `apps/mobile/config/auth.ts`.
The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior.
```ts title="apps/mobile/config/auth.ts"
import env from "env.config";
import { Platform } from "react-native";
import { SocialProvider, authConfigSchema } from "@turbostarter/auth";
import type { AuthConfig } from "@turbostarter/auth";
export const authConfig = authConfigSchema.parse({
providers: {
password: env.EXPO_PUBLIC_AUTH_PASSWORD,
magicLink: env.EXPO_PUBLIC_AUTH_MAGIC_LINK,
anonymous: env.EXPO_PUBLIC_AUTH_ANONYMOUS,
oAuth: [
Platform.select({
android: SocialProvider.GOOGLE,
ios: SocialProvider.APPLE,
}),
SocialProvider.GITHUB,
],
},
}) satisfies AuthConfig;
```
The configuration is also validated using the Zod schema, so if something is off, you'll see the errors.
For example, if you want to switch from password to magic link, you'd change the following environment variables:
```dotenv title=".env.local"
EXPO_PUBLIC_AUTH_PASSWORD=false
EXPO_PUBLIC_AUTH_MAGIC_LINK=true
```
To display third-party providers in the UI, you need to set the `oAuth` array to include the provider you want to display. The default is Google and Github.
```tsx title="apps/web/config/auth.ts"
providers: {
...
oAuth: [
Platform.select({
android: SocialProvider.GOOGLE,
ios: SocialProvider.APPLE,
}),
SocialProvider.GITHUB,
],
...
},
```
You can even display specific providers for specific platforms - for example, you can display Google authentication for Android and Apple authentication for iOS.
## Third party providers
To enable third-party authentication providers, you'll need to:
1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use)
2. Configure the corresponding environment variables in your TurboStarter **API (web) application**
Each OAuth provider requires its own set of credentials and environment variables. Please refer to the [Better Auth documentation](https://better-auth.com/docs/concepts/oauth) for detailed setup instructions for each supported provider.
<Callout title="Multiple environments">
Make sure to set both development and production environment variables
appropriately. Your OAuth provider may require different callback URLs for
each environment.
</Callout>

View File

@@ -0,0 +1,51 @@
---
title: User flow
description: Discover the authentication flow in Turbostarter.
url: /docs/mobile/auth/flow
---
# User flow
TurboStarter ships with a fully functional authentication system. Most of the screens and components are preconfigured and easily customizable to your needs.
Here you will find a quick walkthrough of the authentication flow.
## Sign up
The sign-up screen is where users can create an account. They need to provide their email address and password.
![Sign up](/images/docs/mobile/auth/sign-up.png)
Once successful, users are asked to confirm their email address. This is enabled by default - and due to security reasons, it's not possible to disable it.
<Callout type="warn" title="Sending authentication emails">
Make sure to configure the [email provider](/docs/web/emails/configuration) together with the [auth hooks](/docs/web/emails/sending#authentication-emails) to be able to send emails from your app.
</Callout>
![Confirm email](/images/docs/mobile/auth/confirm-email.png)
## Sign in
The sign-in screen is where users can log in to their account. They need to provide their email address and password, use magic link (if enabled) or third-party providers.
![Sign in](/images/docs/mobile/auth/sign-in.png)
## Sign out
The sign out button is located in the user account settings.
![Settings](/images/docs/mobile/auth/settings.png)
## Forgot password
The forgot password screen is where users can reset their password. They need to provide their email address and follow the instructions in the email.
It comes together with the reset password screen, where users land from a forgot email. There they can reset their password by providing new password and confirming it.
![Forgot password](/images/docs/mobile/auth/forgot-password.png)
## Two-factor authentication
Two-factor authentication is a security feature that requires users to provide a code sent to their email or phone number in addition to their password when logging in.
![Two-factor authentication](/images/docs/mobile/auth/two-factor/sign-in-prompt.png)

View File

@@ -0,0 +1,72 @@
---
title: OAuth
description: Get started with social authentication.
url: /docs/mobile/auth/oauth
---
# OAuth
Better Auth supports almost **30** (!) different [OAuth providers](https://www.better-auth.com/docs/concepts/oauth). They can be easily configured and enabled in the kit without any additional configuration needed.
<Callout title="Everything configured!">
TurboStarter provides you with all the configuration required to handle OAuth providers responses from your app:
* redirects
* middleware
* confirmation API routes
You just need to configure one of the below providers on their side and set correct credentials as environment variables in your TurboStarter app.
</Callout>
![OAuth providers](/images/docs/web/auth/social-providers.png)
Third Party providers need to be configured, managed and enabled fully on the provider's side. TurboStarter just needs the correct credentials to be set as environment variables in your app and passed to the [authentication API configuration](/docs/web/auth/configuration#api).
To enable OAuth providers in your TurboStarter app, you need to:
1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use)
2. Configure the provider's credentials as environment variables in your app. For example, for Google OAuth:
```dotenv title="apps/web/.env.local"
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
```
Then, pass it to the authentication configuration in `packages/auth/src/server.ts`:
```ts title="server.ts"
export const auth = betterAuth({
...
socialProviders: {
[SocialProvider.GOOGLE]: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
},
},
...
});
```
<Callout title="Remember to add your app scheme as trusted origin">
For mobile apps, we need to define a trusted origin using an app scheme instead of a classic URL. App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication.
To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration:
```ts title="server.ts"
export const auth = betterAuth({
...
trustedOrigins: ["turbostarter://**"],
...
});
```
Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through.
[Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security)
</Callout>
Also, we included some native integrations (["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS and ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android) to make the sign-in process smoother and faster for the user.

View File

@@ -0,0 +1,74 @@
---
title: Apple
description: Configure "Sign in with Apple" for your mobile application.
url: /docs/mobile/auth/oauth/apple
---
# Apple
**"Sign in with Apple"** provides a native, privacy-preserving SSO experience on iOS. Use the system Apple button and the Apple Authentication APIs to sign users in, then verify the identity token on your backend and create a session with your auth server.
<Callout title="Apple ID authentication is available on iOS only">
Native Apple ID authentication is available on iOS only. You are advised to
present the official system button (or our custom component - also compliant!)
and follow [Apple's Human Interface
Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple)
for best practices.
</Callout>
![Sign in with Apple](/images/docs/mobile/auth/sign-in-with-apple.png)
## Why use native Apple ID authentication?
<Cards>
<Card title="First-class native UX">
System sheet + official button, aligned with [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple) for trust and conversion.
</Card>
<Card title="Privacy-forward">
Private relay email and limited data by design, ensuring your users' privacy is protected and compliant with App Store guidelines.
</Card>
<Card title="Fewer passwords">
Fast, low-friction sign-in on iOS enabling your users to sign in without the need to remember or create additional passwords.
</Card>
<Card title="Secure by default">
JWT verification on the server with [Better Auth](https://www.better-auth.com/docs/authentication/apple), keeping your users' credentials secure.
</Card>
<Card title="Seamless sessions">
We exchange Apple credentials for an app session and persist it in the app.
</Card>
</Cards>
## Requirements
* Enable the "Sign in with Apple" capability for your bundle identifier in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list)
* Add the entitlement and build with [EAS](/docs/mobile/publishing/checklist) (or configure natively)
* Ensure your app's deep link scheme is added to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration)
Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/apple) for more details on how to configure all the required keys and certificates.
## High-level flow
1. Check availability with `AppleAuthentication.isAvailableAsync()`.
2. Render the system `AppleAuthenticationButton` or custom TurboStarter component.
3. Call `AppleAuthentication.signInAsync()` requesting `FULL_NAME` and/or `EMAIL` as needed.
4. Send the returned `idTokeb` identifier to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/apple) to verify and establish a session.
5. Optionally track credential state with `AppleAuthentication.getCredentialStateAsync(user)`.
<Callout type="warn" title="Verify on the server">
Always verify the JWT signature from `idToken` on your backend using Apple's
public keys before creating a session.
</Callout>
For a more in-depth overview of Apple ID authentication—including implementation details, platform caveats, and advanced configuration—see the following resources:
<Cards>
<Card title="Expo AppleAuthentication" href="https://docs.expo.dev/versions/latest/sdk/apple-authentication/" description="docs.expo.dev" />
<Card title="Login with Apple" href="https://www.better-auth.com/docs/authentication/apple" description="better-auth.com" />
<Card title="Sign in with Apple" href="https://developer.apple.com/documentation/sign_in_with_apple" description="developer.apple.com" />
</Cards>

View File

@@ -0,0 +1,71 @@
---
title: Google
description: Configure "Sign in with Google" for your mobile application.
url: /docs/mobile/auth/oauth/google
---
# Google
**"Sign in with Google"** enables a fast account-chooser experience on mobile (especially on Android). Configure your platform credentials, prompt the native account picker, then exchange the returned token on your backend to create a session with your auth server.
<Callout title="Platform support">
On Android, Google SignIn uses [Google Identity
Services](https://developers.google.com/identity?hl=pl) and integrates with
the system account chooser. On iOS, the recommended Expo flow uses
[expo-auth-session](https://docs.expo.dev/versions/latest/sdk/auth-session/)
with Google for a native, web-based sign-in experience.
</Callout>
![Sign in with Google](/images/docs/mobile/auth/sign-in-with-google.png)
## Why use Google authentication?
<Cards>
<Card title="First-class native UX">
Account picker and token storage integrated with the OS for speed and familiarity.
</Card>
<Card title="Seamless across platforms">
Android native chooser; iOS polished experience via Expo.
</Card>
<Card title="Secure by default">
Tokens are verified server-side with [Better Auth](https://www.better-auth.com/docs/authentication/google) before a session is issued.
</Card>
<Card title="Faster onboarding">
Reduce friction with one-tap sign-in and fewer passwords to remember.
</Card>
<Card title="Scalable">
Built on [Google Identity Services](https://developers.google.com/identity?hl=pl) and best-practice OAuth flows.
</Card>
</Cards>
## Requirements
* Configure [Google Cloud OAuth Client IDs](https://react-native-google-signin.github.io/docs/setting-up/get-config-file) (Android package + SHA-1, iOS bundle ID) in the [Google Cloud Console](https://console.cloud.google.com/)
* Build with [EAS](/docs/mobile/publishing/checklist) to ensure native credentials are embedded correctly
* Add your app deep link scheme to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration)
Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/google) and [`@react-native-google-signin/google-signin` documentation](https://react-native-google-signin.github.io) for steps to configure your server verification, client IDs and more.
## High-level flow
1. Configure Google OAuth Client IDs for Android and iOS in [Google Cloud Console](https://console.cloud.google.com/).
2. Initialize the Google auth request in your app and render a "Sign in with Google" button.
3. Prompt the account chooser; on success you receive an `idToken` and/or `accessToken`.
4. Send the tokens to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/google) to verify and establish a session.
5. Persist the session and proceed to the app.
For a more in-depth overview of Google authentication, including implementation details, platform caveats, and advanced configuration, see the following resources:
<Cards>
<Card title="Use Google Authentication" href="https://docs.expo.dev/guides/google-authentication/" description="docs.expo.dev" />
<Card title="Login with Google" href="https://www.better-auth.com/docs/authentication/google" description="better-auth.com" />
<Card title="React Native Google Sign In" href="https://react-native-google-signin.github.io/" description="react-native-google-signin.github.io" />
<Card title="Authenticate users with Sign in with Google" href="https://developer.android.com/identity/sign-in/credential-manager-siwg" description="developer.android.com" />
</Cards>

View File

@@ -0,0 +1,40 @@
---
title: Overview
description: Get started with authentication.
url: /docs/mobile/auth/overview
---
# Overview
TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box.
<Callout title="Why Better Auth?">
One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**.
Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter.
Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible.
</Callout>
![Better Auth](/images/docs/better-auth.png)
You can read more about Better Auth in the [official documentation](https://better-auth.com/docs).
TurboStarter supports multiple authentication methods:
* **Password** - the traditional email/password method
* **Magic Link** - magic links with [deep linking](https://docs.expo.dev/linking/overview)
* **Anonymous** - allowing users to proceed anonymously
* **OAuth** - OAuth social providers ([Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) preconfigured)
* **Native Apple authentication** - ["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS
* **Native Google authentication** - ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android
As well as common applications flows, with ready-to-use views and components:
* **Sign in** - sign in with email/password or OAuth providers
* **Sign up** - sign up with email/password or OAuth providers
* **Sign out** - sign out
* **Password recovery** - forgot and reset password
* **Email verification** - verify email
You can construct your auth flow like LEGO bricks - plug in needed parts and customize them to your needs.

View File

@@ -0,0 +1,72 @@
---
title: Billing
description: Get started with billing in TurboStarter.
url: /docs/mobile/billing
---
# Billing
<Callout title="Fully-featured billing on mobile is coming soon">
For now, billing has a limited functionalities on mobile, we're mostly relying on the [web app](/docs/web/billing/overview) to handle billing.
We are working on a fully-featured mobile billing to help you monetize your mobile app easier. Stay tuned for updates.
[See roadmap](https://github.com/orgs/turbostarter/projects/1)
</Callout>
## Fetching customer data
When your user purchased a plan from your landing page or web app, you can easily fetch their data using the [API](/docs/mobile/api/client).
To do so, just call the `/api/billing/customer` endpoint:
```tsx title="customer-screen.tsx"
import { api } from "~/lib/api";
export default function CustomerScreen() {
const { data: customer, isLoading } = useQuery({
queryKey: ["customer"],
queryFn: handle(api.billing.customer.$get),
});
if (isLoading) return <Text>Loading...</Text>;
return <Text>{customer?.plan}</Text>;
}
```
You may also want to ensure that user is logged in before fetching their billing data to avoid unnecessary API calls.
```tsx title="customer-screen.tsx"
import { api } from "~/lib/api";
import { authClient } from "~/lib/auth";
export default function CustomerScreen() {
const {
data: { user },
} = authClient.useSession();
const { data: customer } = useQuery({
queryKey: ["customer"],
queryFn: handle(api.billing.customer.$get),
enabled: !!user, // [!code highlight]
});
if (!user || !customer) {
return null;
}
return (
<View>
<Text>{user.email}</Text>
<Text>{customer.plan}</Text>
</View>
);
}
```
<Callout title="Be cautious!" type="warn">
Be mindful when implementing payment-related features in your mobile app. Apple has strict guidelines regarding external payment systems and **may reject your app** if you aggressively redirect users to web-based payment flows. Make sure to review the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#payments) carefully and consider implementing native in-app purchases for iOS users to ensure compliance.
We are currently working on a fully native payments system that will make it easier to comply with Apple's guidelines - stay tuned for updates!
</Callout>

View File

@@ -0,0 +1,92 @@
---
title: CLI
description: Start your new project with a single command.
url: /docs/mobile/cli
---
# CLI
<CliDemo />
To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds.
The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently.
Currently, the following action is available:
* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.)
**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available:
* **Translations** - Translate your project, verify translations, and manage them effectively
* **Installing plugins** - Easily install plugins for your project
* **Dynamic code generation** - Generate dynamic code based on your project structure
## Installation
You can run commands using `npx`:
```bash
npx turbostarter <command>
npx @turbostarter/cli@latest <command>
```
<Callout>
If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`.
This also allows you to always run the latest version of the CLI without having to update it.
</Callout>
## Usage
Running the CLI without any arguments will display the general information about the CLI:
```bash
Usage: turbostarter [options] [command]
Your Turbo Assistant for starting new projects, adding plugins and more.
Options:
-v, --version display the version number
-h, --help display help for command
Commands:
new create a new TurboStarter project
help [command] display help for command
```
You can also display help for it or check the actual version.
### Starting a new project
Use the `new` command to initialize configuration and dependencies for a new project.
```bash
npx turbostarter new
```
You will be asked a few questions to configure your project:
```bash
✔ All prerequisites are satisfied, let's start! 🚀
? What do you want to ship?
◉ Web app
◉ Mobile app
◯ Browser extension
? Enter your project name.
? How do you want to use database?
Local (powered by Docker)
Cloud
? What do you want to use for billing?
Stripe
Lemon Squeezy
...
🎉 You can now get started. Open the project and just ship it! 🎉
Problems? https://www.turbostarter.dev/docs
```
It will create a new project, configure providers, install dependencies and start required services in development mode.

View File

@@ -0,0 +1,152 @@
---
title: App configuration
description: Learn how to setup the overall settings of your app.
url: /docs/mobile/configuration/app
---
# App configuration
When configuring your app, you'll need to define settings in different places depending on which provider will use them (e.g., Expo, EAS).
## App configuration
Let's start with the core settings for your app. These settings are **crucial** as they're used by Expo and EAS to build your app, determine its store presence, prepare updates, and more.
This configuration includes essential details like the official name, description, scheme, store IDs, splash screen configuration, and more.
You'll define these settings in `apps/mobile/app.config.ts`. Make sure to follow the [Expo config schema](https://docs.expo.dev/versions/latest/config/app/) when setting this up.
Here is an example of what the config file looks like:
```ts title="apps/mobile/app.config.ts"
import { ExpoConfig } from "expo/config";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: "TurboStarter",
slug: "turbostarter",
scheme: "turbostarter",
version: "0.1.0",
orientation: "portrait",
icon: "./assets/images/icon.png",
userInterfaceStyle: "automatic",
assetBundlePatterns: ["**/*"],
sdkVersion: "51.0.0",
platforms: ["ios", "android"],
updates: {
fallbackToCacheTimeout: 0,
},
newArchEnabled: true,
ios: {
bundleIdentifier: "your.bundle.identifier",
supportsTablet: false,
},
android: {
package: "your.bundle.identifier",
adaptiveIcon: {
monochromeImage: "./public/images/icon/android/monochrome.png",
foregroundImage: "./public/images/icon/android/adaptive.png",
backgroundColor: "#0D121C",
},
},
extra: {
eas: {
projectId: "your-project-id",
},
},
experiments: {
tsconfigPaths: true,
typedRoutes: true,
},
plugins: ["expo-router", ["expo-splash-screen", SPLASH]],
});
```
Make sure to replace the values with your own and take your time to set everything correctly.
<Card title="Configure with app config" description="docs.expo.dev" href="https://docs.expo.dev/workflow/configuration/" />
### Internal configuration
The same as for the [web app](/docs/web/configuration/app), and [extension](/docs/extension/configuration/app), we're defining the internal app config, which stores some overall variables for your application (that can't be read from Expo config).
The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time.
```ts title="apps/mobile/src/config/app.ts"
import env from "env.config";
export const appConfig = {
locale: env.EXPO_PUBLIC_DEFAULT_LOCALE,
url: env.EXPO_PUBLIC_SITE_URL,
theme: {
mode: env.EXPO_PUBLIC_THEME_MODE,
color: env.EXPO_PUBLIC_THEME_COLOR,
},
} as const;
```
For example, to set the mobile app default theme color, you'd update the following variable:
```dotenv title=".env.local"
EXPO_PUBLIC_THEME_COLOR="yellow"
```
<Callout type="warn" title="Do NOT use process.env!">
Do NOT use `process.env` to get the values of the variables. Variables
accessed this way are not validated at build time, and thus the wrong variable
can be used in production.
</Callout>
## EAS configuration
To properly build and publish your app, you need to define settings for the EAS build service.
This is done in `apps/mobile/eas.json` and it must follow the [EAS config scheme](https://docs.expo.dev/eas/json/).
Here is an example of what the config file looks like:
```json title="apps/mobile/eas.json"
{
"cli": {
"version": ">= 4.1.2"
},
"build": {
"base": {
"node": "20.15.0",
"pnpm": "9.6.0",
"ios": {
"resourceClass": "m-medium"
},
"env": {
"EXPO_PUBLIC_DEFAULT_LOCALE": "en",
"EXPO_PUBLIC_AUTH_PASSWORD": "true",
"EXPO_PUBLIC_AUTH_MAGIC_LINK": "false",
"EXPO_PUBLIC_THEME_MODE": "system",
"EXPO_PUBLIC_THEME_COLOR": "orange"
}
},
...
"preview": {
"extends": "base",
"distribution": "internal",
"android": {
"buildType": "apk"
},
"env": {
"APP_ENV": "test",
}
},
"production": {
"extends": "base",
"env": {
"APP_ENV": "production",
}
}
...
},
}
```
Make sure to also fill all the [environment variables](/docs/mobile/configuration/environment-variables) with the correct values for your project and correct environment, otherwise your app won't build and you won't be able to publish it.
<Card title="Configure EAS Build with eas.json" description="docs.expo.dev" href="https://docs.expo.dev/build/eas-json/" />

View File

@@ -0,0 +1,92 @@
---
title: Environment variables
description: Learn how to configure environment variables.
url: /docs/mobile/configuration/environment-variables
---
# Environment variables
Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/mobile` package.
* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, mobile).
* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments.
* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/web`). These are specific to the app and are not shared between apps.
* **Build environment variables**: Not stored in the `.env` file. Instead, they are stored in `eas.json` file used to build app on [Expo Application Services](https://expo.dev/eas).
* **Secret keys**: They're not stored on mobile side, instead [they're defined on the web side.](/docs/web/configuration/environment-variables#secret-keys)
## Shared variables
Here you can add all the environment variables that are shared across all the apps.
To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`).
```dotenv title=".env.local"
# Shared environment variables
# The database URL is used to connect to your database.
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
# The name of the product. This is used in various places across the apps.
PRODUCT_NAME="TurboStarter"
# The url of the web app. Used mostly to link between apps.
URL="http://localhost:3000"
...
```
## App-specific variables
Here you can add all the environment variables that are specific to the app (e.g. `apps/mobile`).
You can also override the shared variables defined in the root `.env` file.
```dotenv title="apps/mobile/.env.local"
# App-specific environment variables
# Env variables extracted from shared to be exposed to the client in Expo app
EXPO_PUBLIC_SITE_URL="${URL}"
EXPO_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}"
# Theme mode and color
EXPO_PUBLIC_THEME_MODE="system"
EXPO_PUBLIC_THEME_COLOR="orange"
# Use this variable to enable or disable password-based authentication. If you set this to true, users will be able to sign up and sign in using their email and password. If you set this to false, the form won't be shown.
EXPO_PUBLIC_AUTH_PASSWORD="true"
...
```
<Callout title="EXPO_PUBLIC_ prefix">
To make environment variables available in the Expo app code, you need to prefix them with `EXPO_PUBLIC_`. They will be injected to the code during the build process.
Only environment variables prefixed with `EXPO_PUBLIC_` will be injected.
[Read more about Expo environment variables.](https://docs.expo.dev/guides/environment-variables/)
</Callout>
## Build environment variables
To allow your app to build properly on [EAS](https://expo.dev/eas) you need to define your environment variables either in your `eas.json` file under corresponding profile (e.g. `preview` or `production`) or directly in the [EAS platform](https://docs.expo.dev/eas/environment-variables/):
![EAS environment variables](/images/docs/mobile/eas-environment-variables.png)
Then, when you trigger build, correct environment variables will be injected to your mobile app code ensuring that everything is working correctly.
[Check EAS documentation for more details.](https://docs.expo.dev/eas/environment-variables/)
## Secret keys
Secret keys and sensitive information are to be **never** stored on the mobile app code.
<Callout title="What does this mean?">
It means that you will need to add the secret keys to the **web app, where the API is deployed.**
The mobile app should only communicate with the backend API, which is typically part of the web app. The web app is responsible for handling sensitive operations and storing secret keys securely.
[See web documentation for more details.](/docs/web/configuration/environment-variables#secret-keys)
This is not a TurboStarter-specific requirement, but a best practice for security for any
application. Ultimately, it's your choice.
</Callout>

View File

@@ -0,0 +1,47 @@
---
title: Paths configuration
description: Learn how to configure the paths of your app.
url: /docs/mobile/configuration/paths
---
# Paths configuration
The paths configuration is set at `apps/mobile/config/paths.ts`. This configuration stores all the paths that you'll be using in your application. It is a convenient way to store them in a central place rather than scatter them in the codebase using magic strings.
It is **unlikely you'll need to change** this unless you're heavily editing the codebase.
```ts title="apps/mobile/config/paths.ts"
const pathsConfig = {
index: "/",
setup: {
welcome: "/welcome",
auth: {
login: `${AUTH_PREFIX}/login`,
register: `${AUTH_PREFIX}/register`,
forgotPassword: `${AUTH_PREFIX}/password/forgot`,
updatePassword: `${AUTH_PREFIX}/password/update`,
error: `${AUTH_PREFIX}/error`,
join: `${AUTH_PREFIX}/join`,
},
steps: {
start: `${STEPS_PREFIX}/start`,
required: `${STEPS_PREFIX}/required`,
skip: `${STEPS_PREFIX}/skip`,
final: `${STEPS_PREFIX}/final`,
},
},
dashboard: {
user: {
index: DASHBOARD_PREFIX,
ai: `${DASHBOARD_PREFIX}/ai`,
...
}
...
}
} as const;
```
<Callout title="Fully type-safe">
By declaring the paths as constants, we can use them safely throughout the
codebase. There is no risk of misspelling or using magic strings.
</Callout>

View File

@@ -0,0 +1,83 @@
---
title: Adding apps
description: Learn how to add apps to your Turborepo workspace.
url: /docs/mobile/customization/add-app
---
# Adding apps
<Callout title="Advanced topic" type="warn">
This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository.
</Callout>
In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions.
To pull updates into a separate application outside of `mobile` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree).
Basically, we will create a subtree at `apps/mobile` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/mobile` folder.
To add a new app to your TurboStarter project, you need to follow these steps:
<Steps>
<Step>
## Create a subtree
First, we need to create a subtree for the `apps/mobile` folder. We will create a branch named `mobile-branch` and create a subtree for the `apps/mobile` folder.
```bash
git subtree split --prefix=apps/mobile --branch mobile-branch
```
</Step>
<Step>
## Create a new app
Now, we can create a new application in the `apps` folder.
Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/mobile` folder (which acts as the template for all new apps).
```bash
git subtree add --prefix=apps/ai-chat origin mobile-branch --squash
```
You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/mobile` folder.
</Step>
<Step>
## Update the app
When you want to update the new application, follow these steps:
### Pull the latest updates from the TurboStarter repository
The command below will update all the changes from the TurboStarter repository:
```bash
git pull upstream main
```
### Push the `mobile-branch` updates
After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the mobile-branch:
```bash
git subtree split --prefix=apps/mobile --branch mobile-branch
```
Now, you can push the updates to the `mobile-branch`:
```bash
git push origin mobile-branch
```
### Pull the updates to the new application
Now, you can pull the updates to the new application:
```bash
git subtree pull --prefix=apps/ai-chat origin mobile-branch --squash
```
</Step>
</Steps>
That's it! You now have a new application in the monorepo 🎉

View File

@@ -0,0 +1,100 @@
---
title: Adding packages
description: Learn how to add packages to your Turborepo workspace.
url: /docs/mobile/customization/add-package
---
# Adding packages
<Callout title="Advanced topic" type="warn">
This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/mobile` or modify existing packages under `packages`. You don't need to do this to add a new screen or component to your application.
</Callout>
To add a new package to your TurboStarter application, you need to follow these steps:
<Steps>
<Step>
## Generate a new package
First, enter the command below to create a new package in your TurboStarter application:
```bash
turbo gen package
```
Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter.
If you don't want to add dependencies to your package, you can skip this step by pressing enter.
The command will have generated a new package under packages named `@turbostarter/<package-name>`. If you named it `example`, the package will be named `@turbostarter/example`.
</Step>
<Step>
## Export a module from your package
By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file.
### From `index.ts` file
The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file.
```ts title="packages/example/src/module.ts"
export function example() {
return "example";
}
```
Then, export the module from the `index.ts` file.
```ts title="packages/example/src/index.ts"
export * from "./module";
```
### From `exports` field in `package.json`
**This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file.
```json title="packages/example/package.json"
{
"exports": {
".": "./src/index.ts",
"./module": "./src/module.ts"
}
}
```
**When to do this?**
1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules.
2. for better organization of your package
For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file.
```json title="packages/example/package.json"
{
"exports": {
".": "./src/index.ts",
"./client": "./src/client.ts",
"./server": "./src/server.ts"
}
}
```
1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'`
2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'`
</Step>
<Step>
## Use the package in your application
You can now use the package in your application by importing it using the package name:
```ts title="apps/mobile/src/app/index.tsx"
import { example } from "@turbostarter/example";
console.log(example());
```
</Step>
</Steps>
Et voilà! You have successfully added a new package to your TurboStarter application. 🎉

View File

@@ -0,0 +1,119 @@
---
title: Components
description: Manage and customize your app components.
url: /docs/mobile/customization/components
---
# Components
For the components part, we're using [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) for atomic, accessible and highly customizable components.
> It's like shadcn/ui, but for mobile apps.
<Callout type="info" title="Why react-native-reusables?">
react-native-reusables is a powerful tool that allows you to generate
pre-designed components with a single command. It's built with Uniwind (like
Tailwind CSS for mobile) and accessibility in mind, it's also highly
customizable.
</Callout>
TurboStarter defines two packages that are responsible for the UI part of your app:
* `@turbostarter/ui` - shared styles, [themes](/docs/mobile/customization/styling#themes) and assets (e.g. icons)
* `@turbostarter/ui-mobile` - pre-built UI mobile components, ready to use in your app
## Adding a new component
There are basically two ways to add a new component:
<Tabs items={["Using the CLI", "Copy-pasting"]}>
<Tab value="Using the CLI">
TurboStarter is fully compatible with [react-native-reusables CLI](https://www.npmjs.com/package/@react-native-reusables/cli), so you can generate new components with single command.
Run the following command from the **root** of your project:
```bash
pnpm --filter @turbostarter/ui-mobile ui:add
```
This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add.
```bash
Which components would you like to add? > Space to select. A to toggle all.
Enter to submit.
◯ accordion
◯ alert
◯ alert-dialog
◯ aspect-ratio
◯ avatar
◯ badge
◯ button
◯ calendar
◯ card
◯ checkbox
```
Newly created components will appear in the `packages/ui/mobile/src` directory.
</Tab>
<Tab value="Copy-pasting">
You can always copy-paste a component from the [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) website and modify it to your needs.
This is possible, because the components are headless and don't need (in most cases) any additional dependencies.
Copy code from the website, create a new file in the `packages/ui/mobile/src` directory and paste the code into the file.
</Tab>
</Tabs>
<Callout title="Keep it atomic" type="warn">
Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition.
E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory.
</Callout>
## Using components
Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible.
To import a component from the UI package, use the following syntax:
```tsx title="apps/mobile/src/modules/common/my-component.tsx"
// [!code word:card]
import {
Card,
CardContent,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
} from "@turbostarter/ui-mobile/card";
```
Then you can use it to build a component specific to your app:
```tsx title="apps/mobile/src/modules/common/my-component.tsx"
export function MyComponent() {
return (
<Card>
<CardHeader>
<CardTitle>My Component</CardTitle>
</CardHeader>
<CardContent>
<Text>My Component Content</Text>
</CardContent>
<CardFooter>
<Button>Click me</Button>
</CardFooter>
</Card>
);
}
```
<Callout title="Think of it the same as for the web">
Most of the components are the same as for the [web app](/docs/web/customization/components).
It means that you can basically migrate existing web components to the mobile app with just an import change!
</Callout>
<Card href="https://reactnativereusables.com//getting-started/introduction/" title="react-native-reusables" description="reactnativereusables.com" />

View File

@@ -0,0 +1,146 @@
---
title: Styling
description: Get started with styling your app.
url: /docs/mobile/customization/styling
---
# Styling
To build the mobile user interface, TurboStarter comes with [Uniwind](https://uniwind.dev/) pre-configured.
<Callout title="Why Uniwind?" type="info">
Uniwind brings Tailwind CSS utilities to React Native. It lets you style with familiar classes while keeping native performance and platform-appropriate primitives.
</Callout>
## Tailwind configuration
In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind configuration. To change global styles, edit the files in this folder.
Here is an example of a shared CSS file that includes the Tailwind CSS configuration:
```css title="packages/ui/shared/src/styles/globals.css"
@import "tailwindcss";
@import "./themes.css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.65rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
...
}
```
For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript.
Also, each app has its own `globals.css` file, which extends the shared config and allows you to override global styles.
Here is an example of an app's `globals.css` file:
```css title="apps/mobile/src/assets/styles/globals.css"
@import "@turbostarter/ui/globals.css";
@import "uniwind";
@theme inline {
--font-sans: "Geist_400Regular";
--font-sans-medium: "Geist_500Medium";
--font-sans-semibold: "Geist_600SemiBold";
--font-sans-bold: "Geist_700Bold";
--font-mono: "GeistMono_400Regular";
}
```
This keeps a clear separation of concerns and a consistent structure for the Tailwind CSS configuration across apps.
## Themes
TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app.
They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden:
```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts"
export const orange = {
light: {
background: [1, 0, 0],
foreground: [0.141, 0.005, 285.823],
card: [1, 0, 0],
"card-foreground": [0.141, 0.005, 285.823],
...
}
} satisfies ThemeColors;
```
Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails).
These variables are consumed across platforms. On mobile, the theme provider injects the shared variables into the app, so Uniwind utility classes like `bg-background` and `text-foreground` resolve correctly.
Feel free to add your own themes or override the existing ones to match your brand's identity.
To apply a custom theme to your app, use a `useTheme` hook to modify the config:
```tsx title="apps/mobile/src/lib/providers/theme.tsx"
import { ThemeColor, ThemeMode } from "@turbostarter/ui";
import { useTheme } from "~/modules/common/hooks/use-theme";
export const ThemeSwitcher = () => {
const { setConfig } = useTheme();
return (
<Pressable
onPress={() =>
setConfig({ mode: ThemeMode.DARK, color: ThemeColor.BLUE })
}
>
<Text>Change the theme to dark blue</Text>
</Pressable>
);
};
```
Under the hood, the `useTheme` hook uses [Uniwind.setTheme](https://docs.uniwind.dev/theming/basics#switch-to-a-specific-theme) and [updateCSSVariables](https://docs.uniwind.dev/theming/update-css-variables) utilities to apply the correct theme to the app together with its variables.
## Dark mode
TurboStarter comes with built-in dark mode support.
Each theme has a corresponding set of dark mode variables, which are used to switch the theme to its dark mode counterpart.
```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts"
export const orange = {
light: {},
dark: {
background: [0.141, 0.005, 285.823],
foreground: [0.985, 0, 0],
card: [0.21, 0.006, 285.885],
"card-foreground": [0.985, 0, 0],
...
}
} satisfies ThemeColors;
```
Our custom implementation reads the system color scheme via `useColorScheme` and applies `dark:` variants automatically. With the provider injecting shared variables, dark mode works out of the box.
You can also define the default theme mode and color in the [app configuration](/docs/mobile/configuration/app).
<Cards>
<Card title="Uniwind" description="uniwind.dev" href="https://uniwind.dev/" />
<Card title="Theming Basics | Uniwind" description="docs.uniwind.dev" href="https://docs.uniwind.dev/theming/basics" />
<Card title="Custom Themes | Uniwind" description="docs.uniwind.dev" href="https://docs.uniwind.dev/theming/custom-themes" />
</Cards>

View File

@@ -0,0 +1,39 @@
---
title: Database
description: Get started with the database.
url: /docs/mobile/database
---
# Database
<Callout type="error" title="API deployment required">
To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first.
<Cards>
<Card title="API" description="Learn more about the API." href="/docs/web/api/overview" />
<Card title="Web deployment" description="Deploy your web application to production." href="/docs/web/deployment/checklist" />
</Cards>
</Callout>
As a mobile app uses only client-side code, **there's no way to interact with the database directly**.
Also, you should avoid any workarounds to interact with the database directly, because it can lead to leaking your database credentials and other security issues.
## Recommended approach
You can safely use the [API](/docs/mobile/api/overview) and call the endpoints which will run queries on the database.
To do this you need to set up the database on the [web, server side](/docs/web/database/overview) and then use the [API client](/docs/mobile/api/client) to interact with it.
Learn more about its configuration in the web part of the docs, especially in the following sections:
<Cards>
<Card title="Overview" description="Get started with the database" href="/docs/web/database/overview" />
<Card title="Schema" description="Learn about the database schema." href="/docs/web/database/schema" />
<Card title="Migrations" description="Migrate your changes to the database." href="/docs/web/database/migrations" />
<Card title="Database client" description="Use database client to interact with the database." href="/docs/web/database/client" />
</Cards>

View File

@@ -0,0 +1,66 @@
---
title: Extras
description: See what you get together with the code.
url: /docs/mobile/extras
---
# Extras
## Tips and Tricks
In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls.
```tsx title="Hero.tsx"
return (
<header>
{/* 💡 Use something that user can visualize e.g.
"Ship your startup while on the toilet" */}
<h1>Best startup on the world</h1>
</header>
);
```
### Submission tips
When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly.
```json title="app.json"
{
"ios": {
"infoPlist": {
/* 🍎 add descriptive justification of using this permission on iOS */
"NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets."
}
}
}
```
As well as providing you with the info on how to make your store listings better:
```json title="package.json"
{
"manifest": {
/* 💡 Use localized messages to get more visibility in web stores */
"name": "__MSG_extensionName__",
"default_locale": "en"
}
}
```
## Discord community
We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord).
<DiscordCta source="extras" />
![Discord](/images/docs/discord.png)
## 25+ SaaS Ideas
Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥
Grouped by category, these ideas are a great way to get inspired and start building your next project.
Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product.
![SaaS Ideas](/images/docs/saas-ideas.png)

View File

@@ -0,0 +1,92 @@
---
title: FAQ
description: Find answers to common technical questions.
url: /docs/mobile/faq
---
# FAQ
## Why isn't everything hidden and configured with one BIG config file?
TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase.
While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements.
## I don't know some technology! Should I buy TurboStarter?
You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn.
Even without knowing some technologies, you can still use the rest of the features.
## I don't need mobile app or browser extension, what should I do?
You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project.
The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack.
## I want to use a different provider for X
Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase.
## Will you add more packages in the future?
Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version.
## Can I use this kit for a non-SaaS project?
This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases.
## Can I use personal accounts only?
Yes! You can disable team accounts and have personal accounts only by setting a feature flag.
## Does it set up the production instance for me?
No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project.
## Does the starter include Solito?
No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc.
Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference.
## Does this pattern leak backend code to my client applications?
No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe.
If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides.
## How do I get support if I encounter issues?
For support, you can:
1. Visit our [Discord](https://discord.gg/KjpK2uk3JP)
2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev))
## Are there any example projects or demos?
Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase).
## How do I deploy my application?
Please check the [production checklist](/docs/web/deployment/checklist) for more information.
## How do I update my project when a new version of the boilerplate is released?
Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update).
## Can I use the React package X with this kit?
Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them.
## Can I integrate TurboStarter into an existing project?
TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first.
## Where can I deploy my application?
TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live.
## Can I easily swap providers (billing, email, etc.)?
Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects.

View File

@@ -0,0 +1,336 @@
---
title: Introduction
description: Get started with TurboStarter mobile kit.
url: /docs/mobile
---
# Introduction
Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development.
<ThemedImage light="/images/docs/demo/light.webp" dark="/images/docs/demo/dark.webp" alt="TurboStarter demo" width={2311} height={1562} zoomable priority />
## What is TurboStarter?
TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes.
Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds.
## Demo apps
TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below.
<DemoBadges
urls={{
android:
"https://play.google.com/store/apps/details?id=com.turbostarter.core",
ios: "https://apps.apple.com/app/id6754278899",
chrome:
"https://chromewebstore.google.com/detail/bcjmonmlfbnngpkllpnpmnjajaciaboo",
firefox: "https://addons.mozilla.org/addon/turbostarter_",
edge: "https://microsoftedge.microsoft.com/addons/detail/turbostarter/ianbflanmmoeleokihabnmmcahhfijig",
web: "https://demo.turbostarter.dev",
}}
/>
## Principles
TurboStarter is built with the following principles:
* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things.
* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project.
* **As performant as possible** - It should be fast and light without any unnecessary overhead.
## Features
Before diving into the technical details, let's overview the features TurboStarter provides.
### Multi-platform development
* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS.
* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo.
* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT.
For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications.
<Callout title="Available. Everywhere.">
Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps.
</Callout>
### Authentication
<Cards>
<Card title="Ready-to-use components and views" description="Pre-built authentication components and pages that match your brand's unique style." className="shadow-none" />
<Card title="Email/password authentication" description="Traditional email and password auth implementing validation and security best practices." className="shadow-none" />
<Card title="Magic links" description="Passwordless authentication through secure email-based magic links, including rate limiting." className="shadow-none" />
<Card title="Password recovery" description="Complete password reset flow including email verification and secure token handling." className="shadow-none" />
<Card title="Multi-factor authentication (MFA)" description="Increase account security with support for 2FA (authenticator apps, TOTP), ready to use and customizable in your app." className="shadow-none" />
<Card title="Passkeys (passwordless)" description="Passwordless authentication using Passkeys (FIDO2/WebAuthn) for seamless, phishing-resistant sign-ins." className="shadow-none" />
<Card title="Anonymous" description="Allow users to proceed anonymously without requiring authentication." className="shadow-none" />
<Card title="OAuth providers" description="Pre-configured social authentication for Google and GitHub, ready to use." className="shadow-none" />
</Cards>
### Organizations/teams
<Cards>
<Card title="Multi-tenancy" description="Multi-tenant organization model with ownership and membership." className="shadow-none" />
<Card title="Teams and members" description="Create teams, invite members, assign roles and manage seats." className="shadow-none" />
<Card title="Invitations" description="Email-based invites with role presets and expiry." className="shadow-none" />
<Card title="Roles per organization" description="Role-based permissions scoped to each organization." className="shadow-none" />
</Cards>
### Billing
<Cards>
<Card title="Subscriptions" description="Recurring billing system supporting multiple plans, pricing tiers and usage metrics." className="shadow-none" />
<Card title="One-time payments" description="Simple payment processing featuring secure checkout and payment confirmation." className="shadow-none" />
<Card title="Webhooks" description="Real-time billing event handling and payment provider data synchronization." className="shadow-none" />
<Card title="Custom plans" description="Create and manage custom pricing plans offering flexible billing options." className="shadow-none" />
<Card title="Billing components" description="Ready-made components for pricing, checkout and billing management." className="shadow-none" />
<Card title="Multiple providers" description="Seamless integration of Stripe, LemonSqueezy and Polar payment systems." className="shadow-none" />
</Cards>
### Database
<Cards>
<Card title="Advanced querying" description="Type-safe SQL queries, relational joins, filters, ordering, pagination and more." className="shadow-none" />
<Card title="Schema migrations" description="Automated schema migrations with version control, rollback, and auto-generation." className="shadow-none" />
<Card title="Connection pooling" description="Standalone or serverless database connections with optimal pooling strategies." className="shadow-none" />
<Card title="Data validation" description="End-to-end data validation using shared types and schema definitions." className="shadow-none" />
</Cards>
### API
<Cards>
<Card title="Serverless architecture" description="Modern serverless infrastructure offering auto-scaling and high availability." className="shadow-none" />
<Card title="Single source of truth" description="Unified data management across all apps through shared types and validation." className="shadow-none" />
<Card title="Protected routes" description="Secure API endpoints implementing role-based access control and rate limiting." className="shadow-none" />
<Card title="Feature-based access" description="Access control based on features and subscription plans." className="shadow-none" />
<Card title="Typesafe client" description="Fully typesafe frontend client featuring automatic type generation." className="shadow-none" />
</Cards>
### Admin
<Cards>
<Card title="Super admin UI" description="Centralized admin workspace with overview metrics and quick actions." className="shadow-none" />
<Card title="User management" description="Search, filter and manage users, status, auth methods and MFA." className="shadow-none" />
<Card title="Roles and permissions" description="Granular access control for admins, moderators and support staff." className="shadow-none" />
<Card title="Impersonation" description="Securely impersonate users to reproduce issues and provide support." className="shadow-none" />
</Cards>
### AI
<Cards>
<Card title="Multiple providers" className="shadow-none">
Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs).
</Card>
<Card title="Ready-to-use components" description="Pre-built chatbot and assistant components supporting real-time streaming." className="shadow-none" />
<Card title="Streaming responses" description="Real-time AI response delivery including progress indicators." className="shadow-none" />
<Card title="Custom rules" description="Includes custom rules and prompts for AI editors and models to make you ship faster." className="shadow-none" />
</Cards>
### Internationalization
<Cards>
<Card title="Locale routing" description="Smart routing based on user locale and automatic language detection." className="shadow-none" />
<Card title="Multiple languages" description="Comprehensive multi-language support and translation management." className="shadow-none" />
<Card title="Language switching" description="One-click language changes and persistent preferences." className="shadow-none" />
<Card title="Mail templates" description="Multi-language email templates including fallback options." className="shadow-none" />
</Cards>
### Emails
<Cards>
<Card title="Transactional emails" description="Automated email delivery including tracking and analytics capabilities." className="shadow-none" />
<Card title="Marketing emails" description="Create and send marketing campaigns using beautiful templates." className="shadow-none" />
<Card title="Email templates" description="Responsive email templates supporting dark mode customization." className="shadow-none" />
<Card title="Multiple providers" description="Easy integration of SendGrid, Resend, and Nodemailer services." className="shadow-none" />
</Cards>
### Landing page
<Cards>
<Card title="Hero section" description="Dynamic hero with subtle animations, platform links, and primary/secondary CTAs." className="shadow-none" />
<Card title="Feature highlights" description="Grid and list layouts to present key features and differentiators." className="shadow-none" />
<Card title="Pricing plans" description="Billing-integrated pricing table with intervals, discounts and footer notes." className="shadow-none" />
<Card title="Testimonials" description="Social proof section with avatars, star ratings and counts." className="shadow-none" />
<Card title="FAQ" description="Structured FAQ with SEO schema for rich results." className="shadow-none" />
<Card title="Re-usable CTAs" description="Buttons, badges and links to docs, pricing and community." className="shadow-none" />
</Cards>
### Marketing
<Cards>
<Card title="SEO" description="Complete SEO toolkit including automatic sitemap generation." className="shadow-none" />
<Card title="Meta tags" description="Flexible meta tag system supporting social media previews." className="shadow-none" />
<Card title="Tips and tricks" description="Comprehensive tips and tricks for optimizing marketing of your app." className="shadow-none" />
<Card title="Mobile onboarding" description="Onboarding flow for mobile apps including custom steps, authentication, and more." className="shadow-none" />
<Card title="Blog" description="Full-featured blog system including categories and RSS feed." className="shadow-none" />
<Card title="Legal pages" description="Pre-built legal templates including version control." className="shadow-none" />
<Card title="Contact form" description="Smart contact form featuring spam protection and auto-responses." className="shadow-none" />
</Cards>
### Storage
<Cards>
<Card title="File uploads" description="Complete file upload system including progress tracking and validation." className="shadow-none" />
<Card title="S3 storage" description="S3-compatible storage offering automatic file optimization." className="shadow-none" />
</Cards>
### CMS
<Cards>
<Card title="Blog pages" description="Complete blog management system including categories and tags." className="shadow-none" />
<Card title="MDX content collections" description="Organized content structure using MDX-based collections and custom frontmatter." className="shadow-none" />
</Cards>
### Theming
<Cards>
<Card title="Built-in themes" description="10+ pre-built themes offering customizable color schemes." className="shadow-none" />
<Card title="Dark mode" description="Built-in dark mode supporting system preference detection." className="shadow-none" />
<Card title="Components CLI" description="Component generation tools following best practices and TypeScript standards." className="shadow-none" />
<Card title="Design system" description="Complete atomic design system including accessibility features." className="shadow-none" />
</Cards>
### Analytics
<Cards>
<Card title="Event tracking" description="Custom event tracking plus automatic session management." className="shadow-none" />
<Card title="Page views" description="Automatic page view capture including bounce rate metrics." className="shadow-none" />
<Card title="User identification" description="Cross-device user tracking and session management." className="shadow-none" />
<Card title="Multiple providers" description="Seamless integration with multiple platforms (e.g. Google Analytics, PostHog, Plausible, Umami, Open Panel, Vemetric)." className="shadow-none" />
</Cards>
### Monitoring
<Cards>
<Card title="Auto-capture exceptions" description="Automatically capture exceptions and errors in your application." className="shadow-none" />
<Card title="Track performance metrics" description="Track performance metrics such as page views, user sessions, and more." className="shadow-none" />
<Card title="Source maps" description="Automatically generate source maps for your application to improve error reporting." className="shadow-none" />
<Card title="Multiple providers" description="Seamless integration with multiple platforms (e.g. Sentry, PostHog)." className="shadow-none" />
</Cards>
### Deployment
<Cards>
<Card title="One-click deploy" description="Simple deployment to your preferred cloud provider." className="shadow-none" />
<Card title="Submission guide" description="Comprehensive guide for app store submissions and requirements." className="shadow-none" />
<Card title="CI/CD workflows" description="Pre-configured deployment pipelines including automated testing." className="shadow-none" />
<Card title="Over-the-air updates" description="Instantly push code or config updates to users without resubmitting to the app store." className="shadow-none" />
</Cards>
### Testing
<Cards>
<Card title="Unit tests" description="Write and run fast unit tests for individual functions and components with instant feedback." className="shadow-none" />
<Card title="Code coverage" description="See precise coverage metrics that show what code is and isn't tested, giving you valuable insight to improve your test suite." className="shadow-none" />
<Card title="Integration tests" description="Test the interaction between different modules or services to ensure everything works together as intended." className="shadow-none" />
<Card title="E2E tests" description="Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples." className="shadow-none" />
</Cards>
## Use like LEGO blocks
The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it.
If you don't need a specific feature, feel free to remove it without affecting the rest of the stack.
This approach allows for:
* **Easy feature integration** - plug new features into the kit with minimal changes.
* **Simplified maintenance** - keep the codebase clean and maintainable.
* **Core feature separation** - distinguish between core features and custom features.
* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more.
## Scope of this documentation
While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation.
This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary.
## `llms.txt`
You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation.
### Example usage
For instance, to prompt an LLM with questions about the TurboStarter:
1. Copy the documentation contents from [/llms.txt](/llms.txt)
2. Use the following prompt format:
```
Documentation:
{paste documentation here}
---
Based on the above documentation, answer the following:
{your question}
```
## Enjoy!
This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev).
Explore new features, build amazing apps, and have fun! 🚀

View File

@@ -0,0 +1,65 @@
---
title: Cloning repository
description: Get the code to your local machine and start developing your app.
url: /docs/mobile/installation/clone
---
# Cloning repository
<Callout type="info" title="Prerequisite: Git installed">
Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com).
</Callout>
## Git clone
Clone the repository using the following command:
```bash
git clone git@github.com:turbostarter/core
```
By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up.
Alternatively, you can use HTTPS to clone the repository:
```bash
git clone https://github.com/turbostarter/core
```
Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations.
<Card title="Git clone" description="git-scm.com" href="https://git-scm.com/docs/git-clone" />
## Git remote
After cloning the repository, remove the original origin remote:
```bash
git remote rm origin
```
Add the upstream remote pointing to the original repository to pull updates:
```bash
git remote add upstream git@github.com:turbostarter/core
```
Once you have your own repository set up, add your repository as the origin:
```bash
git remote add origin <your-repository-url>
```
<Card title="Git remote" description="git-scm.com" href="https://git-scm.com/docs/git-remote" />
## Staying up to date
To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕):
```bash
git pull upstream main
```
This ensures your repository stays up to date with the latest changes.
Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase.

View File

@@ -0,0 +1,353 @@
---
title: Common commands
description: Learn about common commands you need to know to work with the mobile project.
url: /docs/mobile/installation/commands
---
# Common commands
<Callout>
For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them.
</Callout>
<Callout title="Want shorter commands?">
You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`:
```bash title="~/.bashrc"
alias p='pnpm'
```
Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`:
```bash title="~/.zshrc"
alias p='pnpm'
```
Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes.
You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`.
</Callout>
<Callout title="Injecting environment variables">
To inject environment variables into the command you run, prefix it with `with-env`:
```bash
pnpm with-env <command>
```
For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected.
Some commands, like `pnpm dev`, automatically inject the environment variables for you.
</Callout>
## Installing dependencies
To install the dependencies, run:
```bash
pnpm install
```
## Starting development server
Start development server by running:
```bash
pnpm dev
```
## Building project
To build the project (including all apps and packages), run:
```bash
pnpm build
```
## Building specific app/package
To build a specific app/package, run:
```bash
pnpm turbo build --filter=<package-name>
```
## Cleaning project
To clean the project, run:
```bash
pnpm clean
```
Then, reinstall the dependencies:
```bash
pnpm install
```
## Formatting code
To check for formatting errors using Prettier, run:
```bash
pnpm format
```
To fix formatting errors using Prettier, run:
```bash
pnpm format:fix
```
## Linting code
To check for linting errors using ESLint, run:
```bash
pnpm lint
```
To fix linting errors using ESLint, run:
```bash
pnpm lint:fix
```
## Typechecking
To typecheck the code using TypeScript for any type errors, run:
```bash
pnpm typecheck
```
## Adding UI components
<Tabs items={["Web", "Mobile"]}>
<Tab value="Web">
To add a new web component, run:
```bash
pnpm --filter @turbostarter/ui-web ui:add
```
This command will add and export a new component to `@turbostarter/ui-web` package.
</Tab>
<Tab value="Mobile">
To add a new mobile component, run:
```bash
pnpm --filter @turbostarter/ui-mobile ui:add
```
This command will add and export a new component to `@turbostarter/ui-mobile` package.
</Tab>
</Tabs>
## Services commands
<Callout title="Prerequisite: Docker installed">
To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine.
You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects.
</Callout>
We have a few commands to help you manage the services containers (for local development).
### Starting containers
To start the services containers, run:
```bash
pnpm services:start
```
It will run all the services containers. You can check their configs in `docker-compose.yml`.
### Setting up services
To setup all the services, run:
```bash
pnpm services:setup
```
It will start all the services containers and run necessary setup steps.
### Stopping containers
To stop the services containers, run:
```bash
pnpm services:stop
```
### Displaying status
To check the status and logs of the services containers, run:
```bash
pnpm services:status
```
### Displaying logs
To display the logs of the services containers, run:
```bash
pnpm services:logs
```
### Database commands
We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands).
#### Generating migrations
To generate a new migration, run:
```bash
pnpm with-env turbo db:generate
```
It will create a new migration `.sql` file in the `packages/db/migrations` folder.
#### Running migrations
To run the migrations against the db, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:migrate
```
It will apply all the pending migrations.
#### Pushing changes directly
<Callout type="warn" title="Don't mess up with your schema!">
Make sure you know what you're doing before pushing changes directly to the db.
</Callout>
To push changes directly to the db, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:push
```
It lets you push your schema changes directly to the database and omit managing SQL migration files.
#### Checking database status
To check the status of the database, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:status
```
It will display the status of the applied migrations and the pending ones.
```bash
Applied migrations:
- 0000_cooing_vargas
- 0001_curious_wallflower
- 0002_good_vertigo
- 0003_peaceful_devos
- 0004_fat_mad_thinker
- 0005_yummy_bucky
- 0006_glorious_vargas
Pending migrations:
- 0007_nebulous_havok
```
#### Resetting database
To reset the database, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:reset
```
It will reset the database to the initial state.
#### Seeding database
To seed the database with some example data (for development purposes), run:
```bash
pnpm with-env turbo db:seed
```
It will populate your database with some example data.
#### Checking database
To check the database schema consistency, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:check
```
#### Studying database
To study the database schema in the browser, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:studio
```
This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio).
## Tests commands
### Running tests
To run the tests, run:
```bash
pnpm test
```
This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline.
### Running tests projects
To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run:
```bash
pnpm test:projects
```
This will run all the tests in the project using Vitest.
### Watching tests
To watch the tests, run:
```bash
pnpm test:projects:watch
```
This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes.
### Generating code coverage
To generate code coverage report, run:
```bash
pnpm turbo test:coverage
```
This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package.
### Viewing code coverage
To preview the code coverage report in the browser, run:
```bash
pnpm turbo test:coverage:view
```
This will launch the report's `.html` file in your default browser.

View File

@@ -0,0 +1,86 @@
---
title: Conventions
description: Some standard conventions used across TurboStarter codebase.
url: /docs/mobile/installation/conventions
---
# Conventions
You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand.
## Turborepo Packages
In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications.
* **Apps** are used to define the main application, including routing, layout, and global styles.
* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application.
<Callout title="Should I create a new package?">
**Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code.
If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping.
**Experienced developers:** If you have the experience, feel free to create packages as needed.
</Callout>
## Imports and Paths
When importing modules from packages or apps, use the following conventions:
* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.).
* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.).
## Enforcing conventions
* [Prettier](https://prettier.io/) is used to enforce code formatting.
* [ESLint](https://eslint.org/) is used to enforce code quality and best practices.
* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety.
<Cards className="grid-cols-2 sm:grid-cols-3">
<Card title="Prettier" href="https://prettier.io/" description="prettier.io" />
<Card title="ESLint" href="https://eslint.org/" description="eslint.org" />
<Card title="TypeScript" href="https://www.typescriptlang.org/" description="typescriptlang.org" />
</Cards>
## Code health
TurboStarter provides a set of tools to ensure code health and quality in your project.
### Github Actions
By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory.
The workflow has multiple stages:
* `format` - runs Prettier to format the code.
* `lint` - runs ESLint to check for linting errors.
* `typecheck` - runs TypeScript to check for type errors.
### Git hooks
Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history.
Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/).
#### Setting up the Pre-Commit Hook
To do so, create a `pre-commit` file in the `./..husky` directory with the following content:
```bash
#!/bin/sh
pnpm typecheck
pnpm lint
```
Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected.
#### Make the Pre-Commit Hook Executable
```bash
chmod +x ./.husky/pre-commit
```
To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console.

View File

@@ -0,0 +1,73 @@
---
title: Managing dependencies
description: Learn how to manage dependencies in your project.
url: /docs/mobile/installation/dependencies
---
# Managing dependencies
As the package manager we chose [pnpm](https://pnpm.io/).
<Callout title="Why pnpm?">
It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort.
</Callout>
## Install dependency
To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace.
To install a package globally, run:
```bash
pnpm add -w <package-name>
```
To install a package to a specific workspace, run:
```bash
pnpm add --filter <workspace-name> <package-name>
```
For example:
```bash
pnpm add --filter @turbostarter/ui motion
```
It will install `motion` to the `@turbostarter/ui` workspace.
## Remove dependency
Removing a package is the same as installing but with the `remove` command.
To remove a package globally, run:
```bash
pnpm remove -w <package-name>
```
To remove a package from a specific workspace, run:
```bash
pnpm remove --filter <workspace-name> <package-name>
```
## Update a package
Updating is a bit easier since there is a nice way to update a package in all workspaces at once:
```bash
pnpm update -r <package-name>
```
<Callout title="Semantic versioning">
When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag.
</Callout>
## Renovate bot
By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/).
When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.**
<Card href="https://docs.renovatebot.com" title="Renovate" description="renovatebot.com" />

View File

@@ -0,0 +1,110 @@
---
title: Development
description: Get started with the code and develop your mobile SaaS.
url: /docs/mobile/installation/development
---
# Development
## Prerequisites
To get started with TurboStarter, ensure you have the following installed and set up:
* [Node.js](https://nodejs.org/en) (22.x or higher)
* [Docker](https://www.docker.com) (only if you want to use local services e.g. database)
* [pnpm](https://pnpm.io)
* [Firebase](https://firebase.google.com) project (optional for some features - check [Firebase project](/docs/mobile/installation/firebase) section for more details)
## Project development
<Steps>
<Step>
### Set up environment
We won't copy the official docs, as there is quite a bit of setup you need to make to get started with iOS and Android development and it also depends what approach you want to take.
[Check this official setup guide to get started](https://docs.expo.dev/get-started/set-up-your-environment/). After you're done with the setup, go back to this guide and continue with the next step.
You can pick if you want to develop the app for iOS or Android by using the real device or the simulator.
<Callout title="Recommendation">
We recommend using the simulators and [development builds](https://docs.expo.dev/develop/development-builds/create-a-build/) for development, as it is more real and reliable approach. It also won't limit you in terms of native dependencies (required for e.g. [analytics](/docs/mobile/analytics/overview)).
Of course, you can start with the simplest approach (using [Expo Go](https://expo.dev/go)) and when you iterate further, switch to different approach.
</Callout>
</Step>
<Step>
### Install dependencies
Install the project dependencies by running the following command:
```bash
pnpm i
```
<Callout title="Why pnpm?">
It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort.
</Callout>
</Step>
<Step>
### Setup environment variables
Create a `.env.local` files from `.env.example` files and fill in the required environment variables.
You can use the following command to recursively copy the `.env.example` files to the `.env.local` files:
<Tabs items={["Unix (MacOS/Linux)", "Windows"]}>
<Tab value="Unix (MacOS/Linux)">
```bash
find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \;
```
</Tab>
<Tab value="Windows">
```bash
Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object {
Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local')
}
```
</Tab>
</Tabs>
Check [Environment variables](/docs/web/configuration/environment-variables) for more details on setting up environment variables.
</Step>
<Step>
### Setup services
If you want to use local services like database etc. (**recommended for development purposes**), ensure Docker is running, then setup them with:
```bash
pnpm services:setup
```
This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use.
</Step>
<Step>
### Start development server
To start the application development server, run:
```bash
pnpm dev
```
Your development server should now be running at `http://localhost:8081`.
![Metro server](/images/docs/mobile/metro-server.png)
Scan the QR code with your mobile device to start the app or press the appropriate key on your keyboard to run it on simulator. In case of any issues check the [Troubleshooting](https://docs.expo.dev/troubleshooting/overview/) section.
</Step>
<Step>
### Publish to stores
When you're ready to publish the project to the stores, follow [guidelines](/docs/mobile/marketing) and [checklist](/docs/mobile/publishing/checklist) to ensure everything is set up correctly.
</Step>
</Steps>

View File

@@ -0,0 +1,69 @@
---
title: Editor setup
description: Learn how to set up your editor for the fastest development experience.
url: /docs/mobile/installation/editor-setup
---
# Editor setup
Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions.
## Settings
We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs.
## LLM rules
We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt).
The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions.
### AGENTS.md
We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards.
You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others.
```md title="AGENTS.md"
### Code Style and Structure
- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
### Naming Conventions
....
```
To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/).
## Extensions
Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later.
These are the extensions we recommend:
### ESLint
Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code.
<Card title="Download ESLint" href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" description="marketplace.visualstudio.com" />
### Prettier
Global extension for code formatting. It will help you to keep your code clean and consistent.
<Card title="Download Prettier" href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" description="marketplace.visualstudio.com" />
### Pretty TypeScript Errors
Improves TypeScript error messages shown in the editor.
<Card title="Download Pretty TypeScript Errors" href="https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors" description="marketplace.visualstudio.com" />
### Tailwind CSS IntelliSense
Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting.
<Card title="Download Tailwind CSS IntelliSense" href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss" description="marketplace.visualstudio.com" />

View File

@@ -0,0 +1,102 @@
---
title: Firebase project
description: Learn how to set up a Firebase project for your TurboStarter mobile app.
url: /docs/mobile/installation/firebase
---
# Firebase project
For some features of your mobile app, you will need to set up a Firebase project. It's a requirement enforced by how these features are implemented under the hood and we cannot change it.
You would need a Firebase project to use the following features:
* [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics) provider
Here, we'll go through the steps to set up a Firebase project and link it to your mobile app.
<Callout title="Development build required" type="warn">
In development environment, the integration with Firebase is possible only when using a [development build](https://docs.expo.dev/workflow/overview/#development-builds). It means that **it won't work in the [Expo Go](https://expo.dev/go) app**.
</Callout>
<Steps>
<Step>
## Create a Firebase project
First things first, you need to create a Firebase project. You can do this by going to the [Firebase console](https://console.firebase.google.com/) and clicking on "Add Project":
![Create a Firebase project](/images/docs/mobile/installation/firebase/create-project.png)
Name it as you want, and proceed to the dashboard.
</Step>
<Step>
## Install Firebase SDK
To install React Native Firebase's base app module, run the following command in your mobile app directory:
```bash
npx expo install @react-native-firebase/app
```
</Step>
<Step>
## Configure Firebase modules
The recommended approach to configure React Native Firebase is to use [Expo Config Plugins](https://docs.expo.dev/config-plugins/introduction/).
To enable Firebase on the native Android and iOS platforms, create and download Service Account files for each platform from your Firebase project.
You can find them in the dashboard under the Firebase project settings:
![Download Service Account files](/images/docs/mobile/installation/firebase/config-files.png)
For Android, it will be a `google-services.json` file, and for iOS it will be a `GoogleService-Info.plist` file.
Then provide paths to the downloaded files in the following `app.config.ts` fields: [`android.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile-1) and [`ios.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile). This is how an example configuration looks like:
```ts title="app.config.ts"
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
ios: {
googleServicesFile: "./GoogleService-Info.plist",
},
android: {
googleServicesFile: "./google-services.json",
},
plugins: [
"@react-native-firebase/app",
[
"expo-build-properties",
{
ios: {
useFrameworks: "static",
},
},
],
],
});
```
<Callout>
For iOS only, since `firebase-ios-sdk` requires `use_frameworks` you need to configure `expo-build-properties` by adding `"useFrameworks": "static"`.
</Callout>
Listing a module in the Config Plugins (the `plugins` array in the config above) is only required for React Native Firebase modules that involve native installation steps - e.g. modifying the Xcode project, `Podfile`, `build.gradle`, `AndroidManifest.xml` etc. React Native Firebase modules without native steps will work out of the box.
</Step>
<Step>
## Generate native code
If you are compiling your app locally, you'll need to regenerate the native code for the platforms to pick up the changes:
```bash
npx expo prebuild --clean
```
Then, you could follow the same steps as in the [development environment setup](/docs/mobile/installation/development) guide to run the app locally or [build a production version](/docs/mobile/publishing/checklist#build-your-app) of your app.
</Step>
</Steps>
Et voilà! You've set up and linked your Firebase project to your mobile app 🎉
You can learn more about the Firebase integration and it's possibilities in the [official documentation](https://rnfirebase.io/).

View File

@@ -0,0 +1,120 @@
---
title: Project structure
description: Learn about the project structure and how to navigate it.
url: /docs/mobile/installation/structure
---
# Project structure
The main directories in the project are:
* `apps` - the location of the main apps
* `packages` - the location of the shared code and the API
### `apps` Directory
This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory.
### `packages` Directory
This is where the shared code and the API for packages live. It includes the following:
* shared libraries (database, mailers, cms, billing, etc.)
* shared features (auth, mails, billing, ai etc.)
* UI components (buttons, forms, modals, etc.)
All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code.
## Repository structure
By default the monorepo contains the following apps and packages:
<Files>
<Folder name="apps" defaultOpen>
<Folder name="web - Web app (Next.js)" />
<Folder name="mobile - Mobile app (React Native - Expo)" />
<Folder name="extension - Browser extension (WXT - Vite + React)" />
</Folder>
<Folder name="packages" defaultOpen>
<Folder name="analytics - Analytics setup" />
<Folder name="api - API server (including all features logic)" />
<Folder name="auth - Authentication setup" />
<Folder name="billing - Billing config and providers" />
<Folder name="cms - CMS setup and providers" />
<Folder name="db - Database setup" />
<Folder name="email - Mail templates and providers" />
<Folder name="i18n - Internationalization setup" />
<Folder name="shared - Shared utilities and helpers" />
<Folder name="storage - Storage setup" />
<Folder name="ui - Atomic UI components">
<Folder name="shared" />
<Folder name="web" />
<Folder name="mobile" />
</Folder>
</Folder>
<Folder name="tooling" defaultOpen>
<Folder name="eslint - ESLint config" />
<Folder name="github - Github actions" />
<Folder name="prettier - Prettier config" />
<Folder name="typescript - TypeScript config" />
</Folder>
</Files>
## Mobile application structure
The mobile application is located in the `apps/mobile` folder. It contains the following folders:
<Files>
<Folder name="public - Static assets" />
<Folder name="src" defaultOpen>
<Folder name="app - Main application" />
<Folder name="assets - Optimized static assets" />
<Folder name="config - App config" />
<Folder name="lib - Communication with packages" />
<Folder name="modules - Application modules" />
<Folder name="utils - Shared utilities" />
</Folder>
<File name=".env.local" />
<File name="app.config.ts" />
<File name="eas.json" />
<File name="package.json" />
<File name="env.config.ts" />
<File name="eslint.config.js" />
<File name="metro.config.js" />
<File name="tsconfig.json" />
<File name="turbo.json" />
</Files>

View File

@@ -0,0 +1,97 @@
---
title: Updating codebase
description: Learn how to update your codebase to the latest version.
url: /docs/mobile/installation/update
---
# Updating codebase
If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository.
Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps!
<Steps>
<Step>
## Stash changes
<Callout title="Don't have changes?">
If you don't have any changes to stash, you can skip this step and proceed with the update process.
Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes.
</Callout>
If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process.
```bash
git stash
```
This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory.
```bash
git stash apply
```
</Step>
<Step>
## Pull changes
Pull the latest changes from the `upstream` remote.
```bash
git pull upstream main
```
When prompted the first time, please opt for merging instead of rebasing.
Don't forget to run `pnpm i` in case there are any updates in the dependencies.
</Step>
<Step>
## Resolve conflicts
If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes.
<Callout title="Conflicts in pnpm-lock.yaml?">
If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run:
```bash
pnpm i
```
Your lock file will now reflect both your changes and the updates from the upstream repository.
</Callout>
</Step>
<Step>
## Run a health check
After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly.
For a quick health check, you can run:
```bash
pnpm lint
pnpm typecheck
```
If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository.
</Step>
<Step>
## Commit and push
Once everything is working fine, don't forget to commit your changes using:
```bash
git commit -m "<your-commit-message>"
```
and push them to your remote repository with:
```bash
git push origin <your-branch-name>
```
</Step>
</Steps>

View File

@@ -0,0 +1,127 @@
---
title: Internationalization
description: Learn how to internationalize your mobile app.
url: /docs/mobile/internationalization
---
# Internationalization
TurboStarter mobile uses [i18next](https://www.i18next.com/) and [expo-localization](https://docs.expo.dev/versions/latest/sdk/localization/) for internationalization. This powerful combination allows you to leverage both i18next's mature translation framework and Expo's native device locale detection.
<Callout title="Why this combination?">
While i18next handles the translation management, expo-localization provides
seamless integration with the device's locale settings. This means your app
can automatically detect and adapt to the user's preferred language, while
still maintaining the flexibility to override it when needed.
</Callout>
The mobile app's internationalization is configured to work out of the box with:
* Automatic device language detection
* Right-to-left (RTL) layout support
* Locale-aware date and number formatting
* Fallback language handling
You can read more about the underlying technologies in their documentation:
* [i18next documentation](https://www.i18next.com/overview/getting-started)
* [expo-localization documentation](https://docs.expo.dev/versions/latest/sdk/localization/)
![i18next logo](/images/docs/i18next.jpg)
## Configuration
The global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can read more about it in the [web configuration](/docs/web/internationalization/configuration) documentation.
By default, the locale is automatically detected based on the user's device settings. You can override it and set the default locale of your mobile app in the [app configuration](/docs/mobile/configuration/app) file.
## Translating app
To translate individual components and screens, you can use the `useTranslation` hook.
```tsx
import { useTranslation } from "@turbostarter/i18n";
export default function MyComponent() {
const { t } = useTranslation();
return <Text>{t("hello")}</Text>;
}
```
It's a recommended way to translate your app.
### Store presence
If you plan on shipping your app to different countries or regions or want it to support various languages, you can provide localized strings for things like the display name and system dialogs.
To do so, check the [official Expo documentation](https://docs.expo.dev/guides/localization/) as it requires modifying your app configuration (`app.config.ts`).
You can find the resources below helpful in this process:
<Cards>
<Card title="Expo Localization" href="https://docs.expo.dev/guides/localization/" description="docs.expo.dev" />
<Card title="Apple App Store Localization" href="https://developer.apple.com/localization/" description="developer.apple.com" />
<Card title="Google Play Localization" href="https://support.google.com/googleplay/android-developer/answer/9844778?hl=en" description="support.google.com" />
</Cards>
## Language switcher
TurboStarter ships with a language customizer component that allows you to switch between languages. You can import and use the `LocaleCustomizer` component and drop it anywhere in your application to allow users to change the language seamlessly.
```tsx
import { LocaleCustomizer } from "@turbostarter/ui-mobile/i18n";
export default function MyComponent() {
return <LocaleCustomizer />;
}
```
The component automatically displays all languages configured in your i18n settings. When a user switches languages, it will be reflected in the app and saved into persistent storage to keep the language across app restarts.
## Best practices
Here are key best practices for managing translations in your mobile app:
* Use clear, hierarchical translation keys for easy maintenance
```ts
// ✅ Good
"screen.home.welcome";
"component.button.submit";
// ❌ Bad
"welcomeText";
```
* Organize translations by app screens and features
```
translations/
├── en/
│ ├── layout.json
│ └── common.json
└── es/
├── layout.json
└── common.json
```
* Consider device language settings and regional formats
* Cache translations locally for offline access
* Handle dynamic content for mobile contexts:
```ts
// Device-specific messages
t("errors.noConnection"); // "Check your internet connection"
// Dynamic values
t("storage.space", { gb: 2.5 }); // "2.5 GB available"
```
* Keep translations concise - mobile screens have limited space
* Test translations with different screen sizes and orientations

View File

@@ -0,0 +1,113 @@
---
title: Marketing
description: Learn how to market your mobile application.
url: /docs/mobile/marketing
---
# Marketing
As you saw in the [Extras](/docs/mobile/extras) section, TurboStarter comes with a lot of tips and tricks to make your product better and help you launch your app faster with higher traffic.
The same applies to [submission tips](/docs/mobile/extras#submission-tips) to help you get your app approved by Apple and Google faster.
We'll talk more about the whole process of deploying and publishing your app in the [Publishing](/docs/mobile/publishing/checklist) section, here we'll go through some guidelines that you need to follow to make your store's visibility higher.
## Before you submit
To help your app approval go as smoothly as possible, review the common missteps listed below that can slow down the review process or trigger a rejection. This doesn't replace the official guidelines or guarantee approval, but making sure you can check every item on the list is a good start.
Make sure you:
* Test your app for crashes and bugs
* Ensure that all app information and metadata is complete and accurate
* Update your contact information in case App Review needs to reach you
* Provide App Review with full access to your app. If your app includes account-based features, provide either an active demo account or fully-featured demo mode, plus any other hardware or resources that might be needed to review your app (e.g. login credentials or a sample QR code)
* Enable backend services so that they're live and accessible during review
* Include detailed explanations of non-obvious features and in-app purchases in the App Review notes, including supporting documentation where appropriate
Following these basic steps during development and before submission will help you get your app approved faster.
## App Store (iOS)
Apple reviews are much stricter than Google reviews, so you need to make sure your app is ready for the App Store.
### Guidelines
Apple has a set of [guidelines](https://developer.apple.com/app-store/review/guidelines/) that you need to follow to make sure your app can be accepted in the App Store.
These include:
* **Safety**: Your app must not contain content or behavior that is harmful, abusive, or threatening.
* **Performance**: Your app must be performant and stable, with a smooth user experience.
* **Business**: Your app must not engage in unethical or deceptive practices.
* **Design**: Your app must have a clean and intuitive design.
* **Legal**: Your app must comply with all relevant laws and regulations.
You can read more about each guideline in the [official App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/).
### Search optimization
App store optimization is the process of increasing an app or game's visibility in an app store, with the objective of increasing organic app downloads. Apps are more visible when they rank high on a wide variety of search terms, maintain a high position in the top charts, or get featured on the store.
There are a few actions that you can take to improve your app's visibility in the App Store:
* **Choose accurate keywords**: Use relevant keywords in your app's store listing.
* **Create a compelling app name, subtitle, and description**: Your app's title should be catchy and descriptive, the same applies to the subtitle and description.
* **Assign the right categories**: Make sure your app is categorized in the right category, this will help you reach the right audience.
* **Foster positive ratings**: Ratings and reviews appear on your product page and influence how your app ranks in search results. They can encourage people to engage with your app, so focus on providing a great app experience that motivates users to leave positive reviews.
* **Publish in-app events**: You can publish in-app events to promote your app and encourage users to engage with your app. (e.g. game competitions)
* **Promote in-app purchases**: Your promoted in-app purchases appear in search results on the App Store. Tapping an in-app purchase leads to your product page, which displays your app's description, screenshots, app previews, and in-app events — and lets people initiate an in-app purchase.
Read more about App Store Optimization in the [official documentation](https://developer.apple.com/app-store/search/).
<Cards>
<Card title="App Review Guidelines" href="https://developer.apple.com/app-store/review/guidelines/" description="Official Apple App Store Review Guidelines" />
<Card title="Search Optimization" href="https://developer.apple.com/app-store/search/" description="Apple's guide on App Store Search Optimization" />
</Cards>
## Google Play (Android)
Google reviews are less stringent than Apple reviews and usually take less time to review, but you still need to make sure your app is ready for the Play Store.
### Guidelines
Google has its own guidelines that apps must adhere to. Some important aspects to consider include:
* **Spam, functionality, and user experience**: Your app must not be spammy, must work as expected and must provide a good user experience.
* **Restricted content**: Before submitting an app to Google Play, ensure it complies with these content policies and with local laws.
* **Privacy**: Apps that are deceptive, malicious, or intended to abuse or misuse any network, device, or personal data are strictly prohibited
* **Monetization**: Your app must not engage in unethical or deceptive practices.
For more detailed information and an interactive checklist, check the [Google requirements page](https://developers.google.com/workspace/marketplace/about-app-review).
### Search optimization
Ensuring that your app and store listing is thorough and optimized is an important factor in getting discovered by users on Google Play.
Follow these steps to optimize your app's visibility on Google Play:
* **Build a comprehensive store listing**: This includes providing accurate **title**, **description** and **promo text**.
* **Use high-quality graphics and images**: App icons, images, and screenshots help make your app stand out in search results, categories, and featured app lists.
* **Diversify your audience**: Google provides automated machine translations of store listings that you don't explicitly define for your app. However, using a professional translation service for your *Description* can lead to better search results and discoverability for worldwide users.
* **Create a great user experience**: Google Play search factors in the overall experience of your app based on user behavior and feedback. Apps are ranked based on a combination of ratings, reviews, downloads, and other factors.
<Cards>
<Card title="Prepare your app for review" href="https://support.google.com/googleplay/android-developer/answer/9859455" description="Google Play's guide on preparing your app for review" />
<Card title="Get discovered on Google Play search" href="https://support.google.com/googleplay/android-developer/answer/4448378" description="Google's guide on improving app discoverability" />
</Cards>
## Common mistakes
There are a few common mistakes that you should avoid to make sure your app can be accepted in the stores. Apple reports that, on average, over **40%** of unresolved issues relate to [guideline 2.1: App Completeness](https://developer.apple.com/app-store/review/guidelines/#2.1), so make sure to avoid these:
* **Crashes and bugs**
* **Broken links**
* **Placeholder content**
* **Incomplete information**
* **Privacy policy issues**
* **Inaccurate screenshots**
* **Repeated submission of similar apps**
Don't worry if your first submission is rejected, improve it, fix all the mentioned issues and try again.

View File

@@ -0,0 +1,113 @@
---
title: Overview
description: Get started with mobile monitoring in TurboStarter.
url: /docs/mobile/monitoring/overview
---
# Overview
TurboStarter ships with powerful, provider-agnostic monitoring helpers for the mobile app so you can answer the questions that matter in production: **what broke**, **on which screen**, and **which users were impacted**. It's designed for simplicity and extensibility, and works with multiple providers behind a single API.
## Capturing exceptions
On mobile, you'll usually want to report errors from a few key places:
* **UI/runtime crashes**: unexpected JS errors that would otherwise blank the screen or break navigation.
* **Async work**: background tasks, effects, and data fetching where failures are easy to miss.
* **Manual reporting**: wrap critical flows (auth, purchases, sync, deep-links) with `try/catch` so you can attach context when things go wrong.
```tsx
import { Pressable, Text } from "react-native";
import { captureException } from "@turbostarter/monitoring-mobile";
export default function ExampleComponent() {
const handleClick = () => {
try {
/* some risky operation */
} catch (error) {
captureException(error);
}
};
return (
<Pressable onPress={handleClick}>
<Text>Trigger Exception</Text>
</Pressable>
);
}
```
<Callout type="warn" title="JS exceptions vs native crashes">
`try/catch` (and most JS error handlers) can only see JavaScript exceptions. Native crashes (for example, a hard crash in a native module) typically require provider-specific native setup to capture crash reports. Use the provider pages below for platform details.
</Callout>
## Identifying users
Error reports become much more actionable once they're tied to a signed-in user. TurboStarter supports identifying the current user after the auth session resolves, so your monitoring provider can associate errors with a stable user profile (without you plumbing this through every capture call).
If you want richer filtering, pass non-sensitive traits (plan, role, locale) depending on what your provider supports.
```tsx title="monitoring.tsx"
import { useEffect } from "react";
import { identify } from "@turbostarter/monitoring-mobile";
import { authClient } from "~/lib/auth";
export const MonitoringProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const session = authClient.useSession();
useEffect(() => {
if (session.isPending) {
return;
}
identify(session.data?.user ?? null);
}, [session]);
return children;
};
```
<Callout title="Keep user data minimal" type="error">
Identify users with **stable IDs** and only the traits you need for debugging. Avoid sending PII or secrets (tokens, raw emails, payment details) unless you've explicitly decided it's acceptable for your monitoring provider and compliance requirements.
</Callout>
## Providers
TurboStarter can report through different monitoring providers while keeping your app code consistent. Choose a provider (or swap later) by updating the exports/config in the monitoring package.
<Cards>
<Card title="Sentry" href="/docs/mobile/monitoring/sentry" />
<Card title="PostHog" href="/docs/mobile/monitoring/posthog" />
</Cards>
## Recommended practices
<Cards>
<Card title="Report what you'd actually act on" className="shadow-none">
Prioritize crashes, failed network calls that break a flow, and unexpected
states. Skip noisy “expected” errors (validation, user cancellations).
</Card>
<Card title="Attach useful context" className="shadow-none">
Include the screen/route, the action the user took, and relevant IDs
(request id, order id). Mobile issues are often device- or version-specific,
so make sure app version/build info is included by your provider.
</Card>
<Card title="Guard against loops" className="shadow-none">
If an effect or retry path can fire repeatedly, debounce or dedupe your
capture calls so you don't spam reports (or exceed quotas).
</Card>
<Card title="Separate dev/staging/prod" className="shadow-none">
Keep environments isolated so test devices don't pollute production signal.
Tag builds/releases so you can correlate spikes with deployments.
</Card>
</Cards>
With solid capture + identification in place, mobile monitoring becomes a feedback loop: you can spot regressions quickly, understand who they affect, and validate fixes by release.

View File

@@ -0,0 +1,155 @@
---
title: PostHog
description: Learn how to setup PostHog as your mobile monitoring provider.
url: /docs/mobile/monitoring/posthog
---
# PostHog
[PostHog](https://posthog.com/) is a product analytics platform that can also help with monitoring via error tracking and session replay. On mobile, it's especially useful when you want to connect **what went wrong** with **what the user did** right before it happened.
TurboStarter keeps monitoring provider selection behind a unified API, so you can route captures to PostHog without changing your app code.
<Callout type="warn" title="Prerequisite: PostHog account">
You'll need a PostHog account ([cloud](https://app.posthog.com/signup) or [self-hosted](https://posthog.com/docs/self-host)) to use it as your monitoring provider.
</Callout>
<Callout type="info" title="You can also use it for analytics!">
PostHog is one of the preconfigured analytics providers for mobile apps. If you want product analytics (events, screens, funnels), see [analytics overview](/docs/mobile/analytics/overview) and the [PostHog configuration](/docs/mobile/analytics/configuration#posthog).
</Callout>
![Posthog banner](/images/docs/web/monitoring/posthog/banner.jpg)
## Configuration
PostHog makes it easy to monitor your mobile app for errors and issues, giving you full visibility into when things go wrong. With TurboStarter, you can enable PostHog-based monitoring in just a few steps, sending errors and related user actions to your PostHog dashboard for debugging and product improvement.
<Steps>
<Step>
### Create a project
Create a new PostHog [project](https://app.posthog.com/project/settings) for your mobile app. You can do this from the [PostHog dashboard](https://app.posthog.com) using the *New Project* action.
</Step>
<Step>
### Activate PostHog as your monitoring provider
TurboStarter chooses the mobile monitoring provider through exports in `packages/monitoring/mobile`. To route monitoring events to PostHog, export the PostHog implementation from the package entrypoint:
```ts title="index.ts"
// [!code word:posthog]
export * from "./posthog";
export * from "./posthog/env";
```
</Step>
<Step>
### Set environment variables
Add your PostHog project key (and host, if you're not using the default cloud region) to your mobile app env. Set these locally and in your build environment (for example, in your [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)):
```dotenv title="apps/mobile/.env.local"
EXPO_PUBLIC_POSTHOG_KEY="your-posthog-project-api-key"
EXPO_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
```
</Step>
</Steps>
That's it - launch the app, trigger an error, and confirm events are arriving in your PostHog project.
![Posthog error](/images/docs/web/monitoring/posthog/error.png)
If you want to go beyond basic capture (session replay, feature flags, richer device/session context), follow [PostHog's React Native setup guidance](https://posthog.com/docs/error-tracking/installation/react-native).
<Cards>
<Card title="Error tracking" href="https://posthog.com/docs/error-tracking" description="posthog.com" />
<Card title="React Native error tracking installation" href="https://posthog.com/docs/error-tracking/installation/react-native" description="posthog.com" />
</Cards>
## Uploading source maps
**Source maps** map the bundled/minified JavaScript running on devices back to your original source code. Without them, mobile stack traces are often hard to read and difficult to action.
<Callout>
With source maps uploaded to PostHog, error reports can be symbolicated so stack traces point to the real files and line numbers from your project.
</Callout>
PostHog's React Native source maps flow has two main parts:
* **Inject debug IDs** into the bundle during bundling (Metro)
* **Upload source maps** during your iOS/Android build (or via CLI in CI)
<Steps>
<Step>
### Install and authenticate the PostHog CLI
Install the CLI globally:
```bash
npm install -g @posthog/cli
```
Then authenticate:
```bash
posthog-cli login
```
If you're running in CI, you can authenticate with environment variables instead:
```dotenv
POSTHOG_CLI_HOST="https://us.posthog.com"
POSTHOG_CLI_ENV_ID="your-posthog-project-id"
POSTHOG_CLI_TOKEN="your-personal-api-key"
```
</Step>
<Step>
### Inject debug IDs with Metro
Automatic injection relies on Expo's debug ID support. Update `metro.config.js` to use PostHog's Expo config:
```js title="metro.config.js"
const { getPostHogExpoConfig } = require("posthog-react-native/metro");
const config = getPostHogExpoConfig(__dirname);
module.exports = config;
```
</Step>
<Step>
### Upload source maps during builds
If you can use the Expo plugin (recommended for managed EAS builds), add the plugin to your Expo config:
```ts title="app.config.ts"
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
plugins: ["posthog-react-native/expo"],
});
```
If you can't use the Expo plugin, PostHog also supports wiring uploads directly into:
* **Android**: your Gradle build (`android/app/build.gradle`)
* **iOS**: your Xcode “Bundle React Native code and images” build phase
Follow the [official PostHog instructions](https://posthog.com/docs/error-tracking/upload-source-maps/react-native) for the exact snippets for each platform.
</Step>
<Step>
### Verify uploads in PostHog
After a release build, confirm your symbol sets are present in [PostHog project error tracking dashboard](https://app.posthog.com/settings/project-error-tracking#error-tracking-symbol-sets) and then trigger a test error to ensure stack traces are resolving as expected.
</Step>
</Steps>
With debug IDs injected and source maps uploaded, PostHog can symbolicate React Native errors so stack traces point back to your original source files. If traces still look minified, double-check that you're testing a release build and that the latest symbol sets are present in your project settings.
<Cards>
<Card title="What are source maps?" href="https://web.dev/articles/source-maps" description="web.dev" />
<Card title="Upload source maps for React Native" href="https://posthog.com/docs/error-tracking/upload-source-maps/react-native" description="posthog.com" />
</Cards>

View File

@@ -0,0 +1,147 @@
---
title: Sentry
description: Learn how to setup Sentry as your mobile monitoring provider.
url: /docs/mobile/monitoring/sentry
---
# Sentry
[Sentry](https://sentry.io/) is a popular error monitoring platform that captures crashes and exceptions from production devices and helps you debug them with stack traces, breadcrumbs, and user context.
TurboStarter's mobile monitoring layer is provider-agnostic, but Sentry is a great default when you want reliable crash reporting plus readable stack traces in release builds.
<Callout type="warn" title="Prerequisite: Sentry account">
To use Sentry, create an [account in Sentry](https://sentry.io/signup) first.
</Callout>
![Sentry banner](/images/docs/web/monitoring/sentry/banner.png)
## Configuration
TurboStarter integrates effortlessly with Sentry, so you can capture application errors and analyze performance from development through production. Setting up Sentry as your provider lets you quickly find and fix issues, contributing to a more robust and dependable app.
Follow the steps below to integrate Sentry with your TurboStarter project.
<Steps>
<Step>
### Create a project
Begin by creating a [project](https://docs.sentry.io/product/projects/) in Sentry. You can set this up from your [dashboard](https://sentry.io/settings/account/projects/) by clicking the *Create Project* button.
</Step>
<Step>
### Activate Sentry as your monitoring provider
The monitoring provider to use is determined by the exports in `packages/monitoring/mobile` package. To activate Sentry as your monitoring provider, you need to update the exports in:
```ts title="index.ts"
// [!code word:sentry]
export * from "./sentry";
export * from "./sentry/env";
```
If you want to customize the provider, you can find its definition in `packages/monitoring/mobile/src/providers/sentry` directory.
</Step>
<Step>
### Set environment variables
Based on your [project settings](https://sentry.io/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your deployment environment (e.g. [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)):
```dotenv title="apps/mobile/.env.local"
EXPO_PUBLIC_SENTRY_DSN="your-sentry-dsn"
EXPO_PUBLIC_PROJECT_ENVIRONMENT="your-project-environment"
```
</Step>
<Step>
### Wrap your app
Install the Sentry React Native SDK in the `mobile` workspace.
```bash
pnpm i @sentry/react-native --filter mobile
```
And then wrap the root component of your application with Sentry.wrap:
```tsx title="app/_layout.tsx"
import * as Sentry from "@sentry/react-native";
export default Sentry.wrap(RootLayout);
```
<Callout>
TurboStarter initializes the SDK for you based on env + provider exports; you only need to wrap the root component.
</Callout>
</Step>
</Steps>
You're all set! Start your app and view any errors or exceptions directly in your [Sentry dashboard](https://sentry.io/settings/account/projects/).
![Sentry error](/images/docs/web/monitoring/sentry/error.jpg)
You can tailor the setup further if needed. For more details, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/features/).
<Cards>
<Card title="Quick Start" href="https://docs.sentry.io/platforms/react-native/" description="docs.sentry.io" />
<Card title="Manual Setup" href="https://docs.sentry.io/platforms/react-native/manual-setup/" description="docs.sentry.io" />
</Cards>
## Uploading source maps
Readable stack traces in Sentry require uploading source maps for release builds. For Expo projects, Sentry recommends enabling **two pieces**:
* the **Sentry Expo config plugin** (uploads during native builds)
* the **Sentry Metro plugin** (adds debug IDs so bundles and source maps match)
### Add the Sentry Expo plugin
Add `@sentry/react-native/expo` plugin to your Expo config (`app.config.ts`):
```ts title="app.config.ts"
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
plugins: [
[
"@sentry/react-native/expo",
{
url: "https://sentry.io/",
project: "your-sentry-project",
organization: "your-sentry-organization",
},
],
],
});
```
Then provide an auth token through environment variables (locally in `.env.local` file in `apps/mobile` directory) and your build environment:
```dotenv title="apps/mobile/.env.local"
SENTRY_AUTH_TOKEN="your-sentry-auth-token"
```
### Add the Sentry Metro plugin
To ensure unique Debug IDs are assigned to the generated bundles and source maps, add the Sentry Metro Plugin to the configuration.
Update `metro.config.js` to use `getSentryExpoConfig`:
```js title="metro.config.js"
const { getSentryExpoConfig } = require("@sentry/react-native/metro");
const config = getSentryExpoConfig(__dirname);
module.exports = config;
```
With the Expo plugin + Metro plugin in place, source maps are uploaded automatically during release native builds and EAS builds (debug builds typically rely on Metro's symbolication).
Take a moment to test your setup by triggering an error in your app, then confirm that source maps are resolving stack traces accurately in your [Sentry dashboard](https://sentry.io/settings/account/projects/). For advanced setup details, troubleshooting, or further customization with React Native and Expo, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/guides/expo/sourcemaps/).
<Cards>
<Card title="What are source maps?" href="https://web.dev/articles/source-maps" description="web.dev" />
<Card title="Uploading source maps" href="https://docs.sentry.io/platforms/react-native/sourcemaps/uploading/expo/" description="docs.sentry.io" />
</Cards>

View File

@@ -0,0 +1,77 @@
---
title: Active organization
description: Set and switch the current organization context within your application.
url: /docs/mobile/organizations/active-organization
---
# Active organization
The active organization on mobile mirrors the behavior used on the [web app](/docs/web/organizations/active-organization) and in the [extension](/docs/extension/organizations). It is tracked in the authenticated session as `activeOrganizationId` and used to scope all organization-bound data and actions.
Below you can find how to read and work with the active organization in your mobile app context.
## Reading the active organization
Use your auth client's helper to read the active organization from the session. This keeps the client in sync with the server and avoids duplicating tenancy logic.
```tsx title="organizations.tsx"
import { authClient } from "~/lib/auth";
export function OrganizationsScreen() {
const organization = authClient.useActiveOrganization();
const member = authClient.useActiveMember();
return (
<>
<Text>{organization?.name}</Text>
<Text>{member?.role}</Text>
</>
);
}
```
This mirrors the [extension](/docs/extension/organizations) approach and the [web hook](/docs/web/organizations/active-organization), ensuring the active organization and member role stay consistent with the server session.
## Performing actions
When invoking API routes from the mobile app, prefer passing the `organizationId` explicitly with the payload. This guarantees the correct tenant is targeted even if multiple devices or views are active simultaneously.
```tsx title="create-post.tsx"
import { api } from "~/lib/api";
export function CreatePost() {
const activeOrganization = authClient.useActiveOrganization();
const { mutate } = useMutation({
mutationFn: async (post: PostInput) =>
api.posts.$post({
...post,
organizationId: activeOrganization?.id,
}),
});
return (
<Form>
<Button onPress={onSubmit(mutate)}>Submit</Button>
</Form>
);
}
```
This mirrors the recommendation from the [web guide](/docs/web/organizations/active-organization#api-route) and avoids edge cases tied to stale session values.
## Switching organizations
TurboStarter ships an account switcher out of the box for mobile. You can drop it into your app and customize labels and styling as needed.
```tsx title="settings.tsx"
import { AccountSwitcher } from "~/modules/organization/account-switcher";
export function SettingsScreen() {
return <AccountSwitcher />;
}
```
When a user selects a new organization, it calls your backend to update the session's `activeOrganizationId` and then re-read the session or invalidate related queries.
For deeper background on how the active organization is resolved, see the [web guide](/docs/web/organizations/active-organization).

View File

@@ -0,0 +1,58 @@
---
title: Invitations
description: Send, track, and accept organization invites.
url: /docs/mobile/organizations/invitations
---
# Invitations
Invite teammates by email to join an organization directly from your mobile app. Acceptance is straightforward: we verify the invite, create or reuse the membership with the intended role, and set the user's active organization.
The implementation uses the same APIs and rules as the [web app](/docs/web/organizations/invitations) and is powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization).
![Mobile invitations list](/images/docs/mobile/organizations/invitations/list.png)
## Capabilities
* Send invitations by email.
* View and filter invitations by status or role, and search by email.
* Resend or revoke an invitation.
* Accept an invitation via a [deep link](https://docs.expo.dev/linking/into-your-app/).
<Callout>
Permissions are enforced by roles. Typically, only organization admins can
send or manage invites. See [RBAC (Roles &
Permissions)](/docs/mobile/organizations/rbac).
</Callout>
## Inviting members
Sending an invitation typically requires the invitee's email and the intended role. You can add multiple recipients in the invitation form to invite several members at once.
![Invite members bottom sheet](/images/docs/mobile/organizations/invitations/invite.png)
After sending, the invitee receives an email with a link to accept. It's a [deep link](https://docs.expo.dev/guides/linking) that opens your app and automatically validates the invite.
## Handling invitations
When a recipient opens an invite link on their device, the app automatically handles the entire flow - reading, parsing, and validating the invite - for you.
![Join organization prompt](/images/docs/mobile/organizations/invitations/join.png)
When the user accepts, we create or reuse their membership and set the active organization in their session. If they reject the invite, we redirect them to their account home.
## Learn more
For underlying details shared across platforms, see the web documentation:
<Cards>
<Card title="Data model" description="Schema for organizations and invitations" href="/docs/web/organizations/data-model" />
<Card title="Statuses and flow" description="Invitation status codes and how they update" href="/docs/web/organizations/invitations#status" />
<Card title="Automatic invalidation" description="How invitations are automatically cleaned up" href="/docs/web/organizations/invitations#automatic-invalidation" />
<Card title="Admin management" description="Admin tooling for managing invitations" href="/docs/web/organizations/invitations#invitation-management" />
</Cards>
These cover the schema, token lifecycle, and admin tooling shared by the mobile and web apps.

View File

@@ -0,0 +1,77 @@
---
title: Overview
description: Learn how to use organizations/teams/multi-tenancy in TurboStarter mobile app.
url: /docs/mobile/organizations/overview
---
# Overview
Organizations let you build teams and multi-tenant SaaS out of the box in the mobile app.
Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces — all from iOS/Android — with the same secure data isolation used on the [web app](/docs/web/organizations/overview).
<Callout title="What is multi-tenancy?">
[Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration.
</Callout>
The feature is powered by the same [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and shares TurboStarter's API, routing, and data layer with the [web app](/docs/web/organizations/overview) and [extension](/docs/extension/organizations). That means your mobile app benefits from the same tenancy rules, RBAC checks, and invitations flow without duplicating backend logic.
<ThemedImage light="/images/docs/web/organizations/multi-tenancy/light.png" dark="/images/docs/web/organizations/multi-tenancy/dark.png" alt="Architecture" width={1375} zoomable height={955} />
## Architecture
On mobile, TurboStarter uses the same pragmatic multi-tenant architecture as the [web app](/docs/web/organizations/overview):
* **Tenant context** lives in the authenticated session as the active organization ID. The mobile client reads this context from the API and includes it when making requests.
* **Data scoping** is performed server-side via `organizationId` on tenant-owned tables and guard clauses in queries. Mobile screens consume scoped endpoints so users only see data for their selected organization.
* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”.
* **Extensibility**: add new tenant-bound entities by including `organizationId` in your schema and using the provided helpers to read or switch the active organization in the app.
This keeps data isolated per organization while remaining simple to reason about across platforms.
<Callout>
For deeper details on the shared data model used by the mobile app, see [Data
model](/docs/web/organizations/data-model).
</Callout>
## Concepts
The same core concepts apply in the mobile app:
| Concept | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. |
| **Member** | A user assigned to an organization. |
| **Role** | Access level within an organization (see [RBAC](/docs/mobile/organizations/rbac)). |
| **Invitation** | Email request to join an organization (see [Invitations](/docs/mobile/organizations/invitations)). |
| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. |
These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications on mobile.
## Development data
In development, TurboStarter automatically [seeds](/docs/mobile/installation/commands#seeding-database) example data when you set up services. The mobile app connects to the same development API, so you can test the full organizations flow end-to-end:
* One organization is created by default.
* All default roles are created and assigned within that organization.
* Sample invitations are generated so you can test the invite flow.
You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features — [reset](/docs/mobile/installation/commands#resetting-database) or [reseed](/docs/mobile/installation/commands#seeding-database) anytime to return to the default state.
The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables.
<Callout type="error" title="Never run in production">
The default development data and setup are intended for local development and
testing only. **Never** use these seeds or configurations in a production
environment - they are insecure and may expose sensitive functionality.
</Callout>
## Customization
You have flexibility to adapt organizations to fit your mobile experience. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the app copy accordingly.
You can adjust the available [roles and permissions](/docs/mobile/organizations/rbac) to suit your access model.
The [invitation flow](/docs/mobile/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work.
Feel free to check how to configure all of these features inside mobile application in the dedicated sections linked above.

View File

@@ -0,0 +1,115 @@
---
title: RBAC (Roles & Permissions)
description: Manage roles, permissions, and access scopes.
url: /docs/mobile/organizations/rbac
---
# RBAC (Roles & Permissions)
Role-based access control (RBAC) lets you define who can do what in an organization.
<Callout title="New to RBAC?">
If you're new to the RBAC concept, a simple mental model is:
* Users belong to organizations.
* Users get roles.
* Roles map to permissions on resources.
</Callout>
In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting—roles, permissions, teams, and member management—while handling critical logic with our own code.
This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role.
<Callout title="Everything is configured out of the box!">
TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs.
On mobile, use conditional UI (disable or hide actions) together with client helpers to match each member's role.
</Callout>
## Roles
Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles:
```ts
const MemberRole = {
MEMBER: "member",
ADMIN: "admin",
OWNER: "owner",
} as const;
```
A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application).
<Callout type="warn" title="Don't confuse organization admin with super admin">
The organization's `admin` role is different from the user's global `admin` role.
The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview).
</Callout>
To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details.
## Permissions
Permissions represent what actions a role can perform on which resources.
To check if the current user has permission to perform an action on mobile, use the client helper and handle the boolean result in your component logic.
```tsx title="create-project.tsx"
import { useQuery, useMutation } from "@tanstack/react-query";
import { authClient } from "~/lib/auth";
export function CreateProject() {
const { data: canCreate } = useQuery({
queryKey: ["permission", "project", "create"],
queryFn: () =>
authClient.organization.hasPermission({
permissions: { project: ["create"] },
}),
});
const { mutate, isPending } = useMutation({
mutationFn: async () => {
// perform the create action
},
});
return (
<Button
disabled={canCreate === false}
loading={isPending}
onPress={() => canCreate && mutate()}
>
Create
</Button>
);
}
```
When you already have the active member's role, prefer the client-side `checkRolePermission` to avoid extra API calls.
```tsx title="update-project.tsx"
import { authClient } from "~/lib/auth";
export function UpdateProject() {
const activeMember = authClient.useActiveMember();
const canUpdate = authClient.organization.checkRolePermission({
permission: {
project: ["update"],
},
role: activeMember.role,
});
return <Button disabled={!canUpdate}>Update</Button>;
}
```
We leverage the existing hook to retrieve the active member role within the [active organization](/docs/mobile/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip.
<Callout type="warn">
This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions.
</Callout>
If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API.
For further customization—such as dynamic access control, lifecycle hooks, or team management—see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization) and the [web guide](/docs/web/organizations/rbac).

View File

@@ -0,0 +1,164 @@
---
title: Google Play (Android)
description: Learn how to publish your mobile app to the Google Play Store.
url: /docs/mobile/publishing/android
---
# Google Play (Android)
[Google Play](https://play.google.com/) is the primary platform for distributing Android apps to billions of users worldwide. It's a powerful marketplace that allows you to reach a large audience and monetize your app.
To submit your app to the Play Store, you'll need to follow a series of steps. We'll walk through those steps here.
<Callout title="Prerequisite" type="warn">
Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Google's policies to avoid common rejections.
</Callout>
## Developer account
A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up on the [Google Play Console](https://play.google.com/console/) and pay the one-time registration fee.
![Google Play Developer Account](/images/docs/mobile/publishing/android/developer-account.png)
To publish apps to Google Play, you must verify your identity. See the [official guide](https://support.google.com/googleplay/android-developer/answer/14177239) for more information. Next, you'll need to create a new app in the [Google Play Console](https://play.google.com/apps/publish/) by clicking the *Create app* button.
## Submission
After registering your developer account, setting it up, and preparing your app, you're ready to publish it to the Play Store.
There are multiple ways to submit your app:
* **Manual submission:** Upload your app bundle directly to the Play Store via the Play Console.
* **Local submission:** Use [EAS CLI](https://github.com/expo/eas-cli) to submit your app.
* **CI/CD submission:** Use ready-to-use GitHub Actions workflow to automatically submit your app.
**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through each approach in detail below.
### Manual submission
This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. However, it's still the **only way to submit your app for the first time**. You can also use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or if you prefer a fully manual flow.
**Create the app entry in Google Play Console**
1. Visit [Google Play Console](https://play.google.com/console/) and sign in. Accept any pending agreements if prompted.
2. Click *Create app*, then enter your app name, default language, app type, and pricing (free/paid). Confirm policy declarations.
3. Finish initial setup tasks (App access, Ads, Content rating, Target audience, Data safety, Privacy policy URL).
**Upload the `.aab` file to a track (internal/closed/open/production)**
1. The fastest route for a first upload is often *Internal testing*. Go to *Internal testing**Releases* (or choose *Closed/Open/Production*), then click *Create new release*.
2. Upload the `.aab` file, add release notes, and review any warnings.
3. Save and continue through the checks until you're ready to submit for review or roll out to [testers](https://play.google.com/console/about/internal-testing/).
**Verify and submit for review**
1. Complete Store listing assets and metadata if not already done.
2. Resolve any policy warnings. When ready, start the rollout to request a [review](/docs/mobile/publishing/android#review).
After your first manual upload is accepted, you can use [Local submission](/docs/mobile/publishing/android#local-submission) or [CI/CD submission](/docs/mobile/publishing/android#cicd-submission-recommended) for subsequent releases.
For more information, please refer to the guides listed below.
<Cards>
<Card title="First Android submission" url="https://github.com/expo/fyi/blob/main/first-android-submission.md" description="expo.fyi" />
<Card title="Create and set up your app" url="https://support.google.com/googleplay/android-developer/answer/9859152" description="google.com" />
</Cards>
### Local submission
<Callout title="First submission must be done manually" type="warn">
Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md).
</Callout>
First, you need to **upload and configure a Google Service Account Key with EAS**. This is the required first step to submit your Android app to the Google Play Store. Follow the [guide on uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) for detailed instructions.
Next, you have to get your app bundle — if you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.aab` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine.
Then, navigate to your app folder and run the following command to submit your app to the Play Store:
```bash
eas submit --platform android
```
The command will guide you through the submission process. You can also configure the steps of the submission process by adding a submission profile in `eas.json`.
<Callout>
If you upload your Google Service Account key to EAS credentials, you do not need to reference a local file path anywhere.
</Callout>
To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built:
```bash
eas build --platform android --auto-submit
```
This will automatically submit the build with all the required credentials to the Play Store right after it is built.
<Cards>
<Card title="Automate submissions" description="docs.expo.dev" href="https://docs.expo.dev/build/automate-submissions/" />
<Card title="Creating a Google Service Account" description="expo.fyi" href="https://github.com/expo/fyi/blob/main/creating-google-service-account.md" />
<Card title="eas.json reference" description="docs.expo.dev" href="https://docs.expo.dev/eas/json/#android-specific-options-1" />
</Cards>
### CI/CD submission (recommended)
<Callout title="First submission must be done manually" type="warn">
Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md).
</Callout>
TurboStarter comes with a pre-configured GitHub Actions workflow to automatically submit your mobile app to the Play Store. You'll find the workflow in the `.github/workflows/publish-mobile.yml` file.
To use this workflow, [upload your Google Play Service Account key to EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) and check your Android credentials setup by running:
```bash
eas credentials --platform android
```
This way, you avoid storing the JSON key in your repository or CI/CD provider.
<Callout title="Don't forget to set EXPO_TOKEN">
This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run.
</Callout>
That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the Play Store automatically 🎉
<Cards>
<Card title="Automate submissions" description="docs.expo.dev" href="https://docs.expo.dev/build/automate-submissions/" />
<Card title="Creating a Google Service Account" description="expo.fyi" href="https://github.com/expo/fyi/blob/main/creating-google-service-account.md" />
<Card title="eas.json reference" description="docs.expo.dev" href="https://docs.expo.dev/eas/json/#android-specific-options-1" />
</Cards>
## Review
After filling out the information about your item, you're ready to submit it for review. Click on the *Send for review* button and confirm that you want to proceed with the submission:
![Send for review](/images/docs/mobile/publishing/android/send-for-review.png)
To control **when** your app is released after review, you can configure [Managed publishing](https://support.google.com/googleplay/android-developer/answer/9859654) in the Google Play Console.
After submitting your app for review, it will enter Google's review process. The review time may vary depending on your app, and you'll receive a notification when the status updates. For more details, check out the [Google Play Review Process](https://developers.google.com/workspace/marketplace/about-app-review) documentation.
<Callout title="Your submission might be rejected" type="error">
If your submission is rejected, you'll receive an email from Google with the rejection reason. You'll need to fix the issues and upload a new version of your app.
![Google Play Rejection](/images/docs/mobile/publishing/android/rejection.png)
Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more info.
</Callout>
When your app is approved by Google, you'll be able to publish it on the Play Store.
![Your update is live email from Google](/images/docs/mobile/publishing/android/update-live.png)
You can learn more about the review process in the official guides listed below.
<Cards>
<Card title="App review process" description="google.com" href="https://developers.google.com/workspace/marketplace/about-app-review" />
<Card title="Google Play branding guidelines" description="google.com" href="https://developers.google.com/workspace/marketplace/terms/branding" />
</Cards>

View File

@@ -0,0 +1,212 @@
---
title: Checklist
description: Let's publish your TurboStarter app to stores!
url: /docs/mobile/publishing/checklist
---
# Checklist
When you're ready to publish your TurboStarter app to stores, follow this checklist.
This process may take a few hours and some trial and error, so buckle up - you're almost there!
<Steps>
<Step>
## Create database instance
**Why it's necessary?**
A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support.
**How to do it?**
You have several options for hosting your PostgreSQL database:
* [Supabase](/docs/mobile/recipes/supabase) - Provides a fully managed Postgres database with additional features
* [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments
* [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling
* [Turso](https://turso.tech/) - Edge database built on libSQL with global replication
* [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover
Choose a provider based on your needs for:
* Pricing and budget
* Geographic region availability
* Scaling requirements
* Additional features (backups, monitoring, etc.)
</Step>
<Step>
## Migrate database
**Why it's necessary?**
Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly.
**How to do it?**
You basically have two possibilities of doing a migration:
<Tabs items={["Using Github Actions (recommended)", "Running locally"]}>
<Tab value="Using Github Actions (recommended)">
TurboStarter comes with predefined Github Action to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file.
What you need to do is to set your `DATABASE_URL` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
Then, you can run the workflow which will publish the database schema to your remote database instance.
[Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow)
</Tab>
<Tab value="Running locally">
You can also run your migrations locally, although this is not recommended for production.
To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in `.env.local` file and run the following command:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:migrate
```
This command will run the migrations and apply them to your remote database.
[Learn more about database migrations.](/docs/web/database/migrations)
</Tab>
</Tabs>
</Step>
<Step>
## (Optional) Set up Firebase project
**Why it's necessary?**
Setting up a Firebase project is optional, and depends on which features your app is using. For example, if you want to use [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics), setting up a Firebase project is required.
**How to do it?**
Please refer to the [Firebase project](/docs/mobile/installation/firebase) section on how to set up and configure your Firebase project.
</Step>
<Step>
## Set up web backend API
**Why it's necessary?**
Setting up the backend is necessary to have a place to store your data and to have other features work properly (e.g. authentication, billing or storage).
**How to do it?**
Please refer to the [web deployment checklist](/docs/web/deployment/checklist) on how to set up and deploy the web app backend to production.
</Step>
<Step>
## Environment variables
**Why it's necessary?**
Setting the correct environment variables is essential for the application to function correctly. These variables include API keys, database URLs, and other configuration details required for your app to connect to various services.
**How to do it?**
Use our `.env.example` files to get the correct environment variables for your project. Then add them to your project on the [EAS platform](https://docs.expo.dev/eas/environment-variables/) for correct profile and environment:
![EAS environment variables](/images/docs/mobile/eas-environment-variables.png)
Alternatively, you can add them to your `eas.json` file under correct profile.
```json title="eas.json"
{
"profiles": {
"base": {
"env": {
"EXPO_PUBLIC_DEFAULT_LOCALE": "en",
"EXPO_PUBLIC_AUTH_PASSWORD": "true",
"EXPO_PUBLIC_AUTH_MAGIC_LINK": "false",
"EXPO_PUBLIC_THEME_MODE": "system",
"EXPO_PUBLIC_THEME_COLOR": "orange"
}
},
"production": {
"extends": "base",
"autoIncrement": true,
"env": {
"APP_ENV": "production",
"EXPO_PUBLIC_SITE_URL": "https://www.turbostarter.dev",
}
}
}
}
```
</Step>
<Step>
## Build your app
<Callout title="Prerequisite: EAS account">
Building your app requires an EAS account and project. If you don't have one, you can create it by following the steps [here](https://expo.dev/eas).
</Callout>
**Why it's necessary?**
Building your app is necessary to create a standalone application bundle that can be published to the stores.
**How to do it?**
You basically have two possibilities to build a bundle for your app:
<Tabs items={["Using Github Actions (recommended)", "Running locally"]}>
<Tab value="Using Github Actions (recommended)">
TurboStarter comes with predefined Github Action to handle building your app on EAS. You can find its definition in the `.github/workflows/publish-mobile.yml` file.
What you need to do is to set your `EXPO_TOKEN` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). You can obtain it from your EAS account, in the [Access Tokens](https://expo.dev/settings/access-tokens) section.
Then, you can run the workflow which will build the app on [EAS platform](https://expo.dev/eas).
[Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow)
</Tab>
<Tab value="Running locally">
You can also run your build locally, although this is not recommended for production.
To do it, you'll need to have [EAS CLI](https://github.com/expo/eas-cli) installed on your machine. You can install it by running the following command:
```bash
npm install -g eas-cli
```
Then, run the following command to build your app with the `production` profile:
```bash
eas build --profile production --platform all
```
This will build the app for both platforms (iOS and Android) and output the results in your app folder.
</Tab>
</Tabs>
</Step>
<Step>
## Submit to stores
**Why it's necessary?**
Releasing your app to the stores is essential for making it accessible and discoverable by your users. This allows users to find, install, and trust your application through official channels.
**How to do it?**
We've prepared dedicated guides for each store that TurboStarter supports out-of-the-box, please refer to the following pages:
<Cards>
<Card title="App Store" href="/docs/mobile/publishing/ios" description="Publish your app to the Apple App Store." />
<Card title="Google Play" href="/docs/mobile/publishing/android" description="Publish your app to the Google Play Store." />
</Cards>
</Step>
</Steps>
That's it! Your app is now live and accessible to your users, good job! 🎉
<Callout title="Other things to consider">
* Optimize your store listings with compelling descriptions, keywords, screenshots and preview videos
* Remove placeholder content and replace with your final production content
* Update all visual branding including favicon, scheme, splash screen and app icons
</Callout>

View File

@@ -0,0 +1,215 @@
---
title: App Store (iOS)
description: Learn how to publish your mobile app to the Apple App Store.
url: /docs/mobile/publishing/ios
---
# App Store (iOS)
[Apple App Store](https://www.apple.com/app-store/) is the primary platform for distributing iOS apps, making them available on iPhones, iPads, and other Apple devices to millions of users worldwide.
To submit your app to the App Store, you'll need to follow a series of steps. We'll walk through those steps here.
<Callout title="Prerequisite" type="warn">
Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Apple's policies to avoid common rejections.
</Callout>
## Developer account
An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/).
![Apple Developer Account](/images/docs/mobile/publishing/ios/developer-account.png)
To submit apps to the App Store, you must also be a member of the Apple Developer Program. You can join the program by paying the annual fee.
## Submission
There are two primary ways to submit your iOS app to the App Store:
* **Manual:** Uploading the build yourself through Apple's tools, such as [Transporter](https://apps.apple.com/app/transporter/id1450874784) or [Xcode](https://developer.apple.com/xcode/).
* **Automatic (recommended):** Using [EAS Submit](/docs/mobile/publishing/ios#local-submission) or [CI/CD](/docs/mobile/publishing/ios#cicd-submission-recommended), which simplifies the process, ensures consistency, and reduces manual error.
Below, you'll find guidance for both submission methods—choose the one that fits your workflow and project needs.
### Manual submission
This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. Use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or prefer a fully manual flow from macOS.
**Create the app entry in App Store Connect**
1. Visit [App Store Connect](https://appstoreconnect.apple.com/) and sign in. Accept any pending agreements if prompted.
2. From Apps, click the + button and select *New App*.
3. Enter the app name, primary language, bundle identifier, and a unique SKU (for example, your bundle ID, such as `com.company.myapp`).
4. Press Create to finish setting up the app record.
**Upload the IPA with Transporter**
1. Install [Apple's Transporter](https://apps.apple.com/app/transporter/id1450874784) from the Mac App Store.
2. Open Transporter and sign in with your Apple ID.
3. Drag the `.ipa` into Transporter (or click *Add App* to choose the file).
4. Press *Deliver* to upload. Transfer time varies by file size and network.
**Verify processing and select the build**
1. Once uploaded, Apple processes the binary (often 10-20 minutes).
2. Back in [App Store Connect](https://appstoreconnect.apple.com/), open My Apps and select your app.
3. Under the *App Store* tab, select the new build in the *Build* section. If it's missing, wait and refresh.
4. Proceed with the usual App Store steps (screenshots, metadata, compliance, then submit for review).
For more information about the required metadata, refer to the official guides.
<Cards>
<Card title="Submitting" url="https://developer.apple.com/app-store/submitting/" description="developer.apple.com" />
<Card title="App Information" url="https://developer.apple.com/help/app-store-connect/reference/app-information/" description="developer.apple.com" />
</Cards>
### Local submission
If you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.ipa` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine.
Then, navigate to your app folder and run the following command to submit your app to the App Store:
```bash
eas submit --platform ios
```
The command will guide you through the submission process. You can configure the submission process by adding a submission profile in `eas.json`:
```json title="eas.json"
{
"submit": {
"production": {
"ios": {
"ascAppId": "your-app-store-connect-app-id"
}
}
}
}
```
<Accordions>
<Accordion title="How to find ascAppId?">
1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and choose your team.
2. Open the [Apps](https://appstoreconnect.apple.com/apps) area.
3. Select your app from the list.
4. Switch to the *App Store* tab.
5. Go to *General* → *App Information*.
6. In *General Information*, the value labeled *Apple ID* is your `ascAppId`.
![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png)
</Accordion>
</Accordions>
To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built:
```bash
eas build --platform ios --auto-submit
```
This will automatically submit the build with all the required credentials to the App Store right after it is built.
<Cards>
<Card title="eas.json reference" description="docs.expo.dev" href="https://docs.expo.dev/eas/json/#ios-specific-options-1" />
<Card title="Automate submissions" description="docs.expo.dev" href="https://docs.expo.dev/build/automate-submissions/" />
</Cards>
### CI/CD submission (recommended)
TurboStarter comes with a pre-configured GitHub Actions workflow to submit your mobile app to the App Store automatically. It's located in the `.github/workflows/publish-mobile.yml` file.
To be able to use this workflow, you'd need to fulfill the following prerequisites:
1. **Configure your App Store Connect API Key**
Run the following command to configure your App Store Connect API Key:
```bash
eas credentials --platform ios
```
The command will prompt you to configure credentials:
1. Choose the `production` build profile.
2. Authenticate with your Apple Developer account and proceed through the prompts.
3. Pick **App Store Connect → Manage your API Key**.
4. Enable **Use an API Key for EAS Submit** for the project.
2. **Provide a submission profile in `eas.json`**
Next, add a submission profile in `eas.json` with the following:
```json title="eas.json"
{
"submit": {
"production": {
"ios": {
"ascAppId": "your-app-store-connect-app-id"
}
}
}
}
```
<Accordions>
<Accordion title="How to find ascAppId?">
1) Log into [App Store Connect](https://appstoreconnect.apple.com/) under the correct team.
2) Go to [Apps](https://appstoreconnect.apple.com/apps) and open your app.
3) Ensure the *App Store* tab is selected.
4) Navigate to *General* → *App Information*.
5) Copy the value shown as *Apple ID* — that is the `ascAppId`.
![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png)
</Accordion>
</Accordions>
<Callout title="Don't forget to set EXPO_TOKEN">
This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run.
</Callout>
That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the App Store automatically 🎉
<Cards>
<Card title="eas.json reference" description="docs.expo.dev" href="https://docs.expo.dev/eas/json/#ios-specific-options-1" />
<Card title="Automate submissions" description="docs.expo.dev" href="https://docs.expo.dev/build/automate-submissions/" />
</Cards>
## Review
After completing your app information, you're ready to submit it for review. Click the *Add for review* button and confirm that you want to proceed with the submission:
![Confirm submission](/images/docs/mobile/publishing/ios/confirm-submission.png)
On the *Distribution* tab, you can configure the release process after the review is complete — whether you want to release the app automatically after review, later, or manually.
![App Store Connect Version Release](/images/docs/mobile/publishing/ios/version-release.png)
Once you've submitted your app for review, it will go through Apple's review process. The duration can vary based on the specifics of your app and you'll be notified when the status changes. For more information, refer to the [App Review](https://developer.apple.com/distribute/app-review/) docs.
<Callout title="Your submission might be rejected" type="error">
If your submission is rejected, you'll receive an email from Apple with the rejection reason. You'll need to fix the issues and upload a new version of your app.
![App Store Connect Rejection](/images/docs/mobile/publishing/ios/rejection.png)
Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more information.
If you need to clarify anything with Apple, you can reply to the app review request in App Store Connect:
![App Store Connect Reply to Review](/images/docs/mobile/publishing/ios/reply-to-review.png)
This helps you understand the rejection and what you need to change to make your app eligible for distribution.
</Callout>
When your app is approved by Apple (by email or push notification), you'll be able to publish it on the App Store.
![Review notification](/images/docs/mobile/publishing/ios/review-notifications.jpeg)
You can learn more about the review process in the official guides listed below.
<Cards>
<Card title="App Review Process" url="https://developer.apple.com/distribute/app-review/" description="developer.apple.com" />
<Card title="App Review Guidelines" url="https://developer.apple.com/app-store/review/guidelines/" description="developer.apple.com" />
</Cards>

View File

@@ -0,0 +1,50 @@
---
title: Updates
description: Learn how to update your published app.
url: /docs/mobile/publishing/updates
---
# Updates
After you publish your app to the stores, you can release updates to provide your users with new features and bug fixes.
TurboStarter offers two ready-to-use methods for updating your apps; we'll walk through both of them below.
## Over-the-air (OTA) updates
[Over-the-air (OTA) updates](https://en.wikipedia.org/wiki/Over-the-air_update) allow you to push updates to your app without requiring users to download a new version from the app store. This powerful feature enables rapid iteration and quick fixes.
![OTA updates](/images/docs/mobile/ota-updates.png)
TurboStarter integrates with [EAS Update](https://docs.expo.dev/eas-updates/overview/) to provide you with a seamless experience for managing your app updates. We also shipped a native notification that you can use to notify your users about the new updates available.
Then, to push your update straight to your users, you'll just need to run single command:
```bash
eas update --channel [channel-name] --message "[message]"
```
The app will automatically download the update in the background and install it when your users are ready. You can also configure the update channel and message to be displayed to your users.
Feel free to check the [official documentation](https://docs.expo.dev/eas-update/getting-started/) for more information.
<Callout title="Working only for non-native changes" type="warn">
OTA updates are **only supported for non-native changes**. If you need to update your app with a new native feature (or add a package that uses native dependencies), you'll need to submit a new version to the stores - see below for more details.
</Callout>
## Submitting a new version
The most traditional way to update your app is to submit a new version to the stores. This is the most reliable approach, but it can take some time for the new version to be approved and made available to users.
To submit a new version, update the version number in both your `package.json` file and your `app.config.ts` file.
```json
{
...
"version": "1.0.0", // [!code --]
"version": "1.0.1", // [!code ++]
...
}
```
Next, follow the exact same steps as [when you initially published your app](/docs/mobile/publishing/checklist). When you submit your app for review, be sure to include release notes for the new version.

View File

@@ -0,0 +1,13 @@
---
title: Push notifications
description: Engage your users with personalized notifications.
url: /docs/mobile/push-notifications
---
# Push notifications
<Callout title="Push notifications are coming soon" type="info">
We are working on push notifications to help you engage your users. Stay tuned for updates.
[See roadmap](https://github.com/orgs/turbostarter/projects/1)
</Callout>

View File

@@ -0,0 +1,221 @@
---
title: Supabase
description: Learn how to set up Supabase as the database (and optional storage) provider for your TurboStarter project.
url: /docs/mobile/recipes/supabase
---
# Supabase
[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box.
You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once.
In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more.
## Prerequisites
Before you start, make sure you have:
* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds)
* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up)
* Basic familiarity with the core database docs:
* [Database overview](/docs/web/database/overview)
* [Migrations](/docs/web/database/migrations)
* [Database client](/docs/web/database/client)
<Steps>
<Step>
## Create a new Supabase project
1. Go to the [Supabase dashboard](https://supabase.com).
2. Create a **new project** (choose a strong database password and a region close to your users).
3. Supabase will automatically provision a **PostgreSQL database** for you.
![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png)
Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases.
![Security options](/images/docs/web/recipes/supabase/security-options.png)
Once the project is ready, you can fetch the connection string.
</Step>
<Step>
## Get the database connection string
In the Supabase dashboard:
1. Open your project.
2. Click on the **Connect** button at the top.
3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab).
![Connect application](/images/docs/web/recipes/supabase/connect-app.png)
Copy this value - you'll use it as your `DATABASE_URL`.
<Callout title="Replace password placeholder" type="warn">
In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project.
</Callout>
</Step>
<Step>
## Configure environment variables
TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package.
Create (or update) the `.env.local` file in the **monorepo root**:
```dotenv title=".env.local"
DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
```
Replace:
* `YOUR-PROJECT-REF` with your Supabase project ref
* `YOUR-PASSWORD` with the database password you set when creating the project
* `aws-region` with the region shown in the Supabase connection string
<Callout>
These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database.
</Callout>
For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview).
</Step>
<Step>
## Setup your Supabase database
With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database.
From the monorepo root, run:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:migrate
```
This will:
* Use your Supabase `DATABASE_URL` from `.env.local`
* Run all pending SQL migrations from `packages/db/migrations`
* Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase
If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations).
<Callout title="Seeding your database" type="info">
After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command:
```bash
pnpm with-env pnpm turbo db:seed
```
This will populate your Supabase database with some example data you can use to test your application.
</Callout>
</Step>
<Step>
## Use Supabase Storage as S3-compatible storage
TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end.
Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database.
### Create a storage bucket
1. In the Supabase dashboard, go to **Storage → Buckets**.
2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`).
3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.)
![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png)
You can create multiple buckets (for documents, images, videos, etc.) if needed.
### Generate S3 access keys in Supabase dashboard
1. Go to **Storage → S3 → Access keys**.
2. Click **New access key**.
3. Give it a descriptive name and create the key.
4. Copy the **Access key ID** and **Secret access key** to use in your application.
![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png)
### Configure S3 environment variables for Supabase Storage
In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer:
```dotenv title=".env.local"
S3_REGION="us-east-1"
S3_BUCKET="avatars"
S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3"
S3_ACCESS_KEY_ID="your-access-key-id"
S3_SECRET_ACCESS_KEY="your-secret-access-key"
```
These variables integrate directly with the storage configuration described in:
* [Storage overview](/docs/web/storage/overview)
* [Storage configuration](/docs/web/storage/configuration)
Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs.
</Step>
<Step>
## Run your API on Supabase Edge Functions
As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users.
At a high level:
1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`.
2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`.
3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`:
```ts
import { Hono } from "jsr:@hono/hono";
// change this to your function name
const functionName = "hono-backend";
const app = new Hono().basePath(`/${functionName}`);
app.get("/hello", (c) => c.text("Hello from hono-server!"));
Deno.serve(app.fetch);
```
4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL.
5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions).
This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project.
</Step>
<Step>
## Explore additional Supabase features
Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview).
Some features that fit especially well with TurboStarter's design are:
* [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top.
* [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai).
* [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration.
Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow.
</Step>
<Step>
## Start the development server
With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root:
```bash
pnpm dev
```
TurboStarter will now:
* Use **Supabase Postgres** as your database through `DATABASE_URL`
* Use **Supabase Storage** as your file storage through the S3-compatible endpoint
* Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend
</Step>
</Steps>
That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices.

View File

@@ -0,0 +1,80 @@
---
title: Tech Stack
description: A detailed look at the technical details.
url: /docs/mobile/stack
---
# Tech Stack
## Turborepo
[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages.
<Card href="https://turbo.build/" title="Turborepo - Make Ship Happen" description="turbo.build" icon={<Turborepo />} />
## React Native + Expo
[React Native](https://reactnative.dev/) is an open-source mobile application development framework created by Facebook. It is used to develop applications for Android and iOS by enabling developers to use [React](https://react.dev) along with native platform capabilities.
> It's like Next.js for mobile development.
[Expo](https://expo.dev/) is a framework and a platform built around React Native. It provides a set of tools and services that help you develop, build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript/TypeScript codebase. It's like Next.js for mobile development.
<Cards className="grid-cols-2">
<Card href="https://reactnative.dev/" title="React Native" description="reactnative.dev" icon={<React />} />
<Card href="https://expo.dev/" title="Expo" description="expo.dev" icon={<Expo />} />
</Cards>
## Tailwind CSS
[Uniwind](https://uniwind.dev/) uses Tailwind CSS as scripting language to create a universal style system for React Native. It allows you to use Tailwind CSS classes in your React Native components, providing a familiar styling experience for web developers. We also use [React Native Reusables](https://github.com/mrzachnugent/react-native-reusables) for our headless components library with support of CLI to generate pre-designed components with a single command.
<Cards className="grid-cols-2">
<Card href="https://uniwind.dev/" title="Uniwind" description="uniwind.dev" icon={<Uniwind />} />
<Card href="https://github.com/mrzachnugent/react-native-reusables" title="react-native-reusables" description="github.com" icon={<ReactNativeReusables />} />
</Cards>
## Hono & React Query
[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints.
To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX.
<Cards>
<Card href="https://hono.dev" title="Hono" description="hono.dev" icon={<Hono />} />
<Card
href="https://tanstack.com/query/latest"
title="React Query"
description="tanstack.com"
icon={
<img src="/images/icons/tanstack.png" alt="" width={32} height={32} />
}
/>
</Cards>
## Better Auth
[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application.
<Card href="https://www.better-auth.com" title="Better Auth" description="better-auth.com" icon={<BetterAuth />} />
## Drizzle
[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way.
We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines.
<Cards>
<Card href="https://orm.drizzle.team/" title="Drizzle" description="orm.drizzle.team" icon={<Drizzle />} />
<Card href="https://www.postgresql.org" title="PostgreSQL" description="postgresql.org" icon={<Postgres />} />
</Cards>
## EAS (Expo Application Services)
[EAS](https://expo.dev/eas) is a set of cloud services provided by Expo for React Native app development. It includes tools for building, submitting, and updating your app, as well as over-the-air updates and analytics.
<Card href="https://expo.dev/eas" title="EAS (Expo Application Services)" description="expo.dev/eas" icon={<Expo />} />

View File

@@ -0,0 +1,15 @@
---
title: E2E tests
description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples.
url: /docs/mobile/tests/e2e
---
# E2E tests
<Callout title="E2E testing is coming soon">
End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application.
Stay tuned for updates as we roll out robust E2E testing resources and examples.
[See roadmap](https://github.com/orgs/turbostarter/projects/1)
</Callout>

View File

@@ -0,0 +1,136 @@
---
title: Unit tests
description: Write and run fast unit tests for individual functions and components with instant feedback.
url: /docs/mobile/tests/unit
---
# Unit tests
Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected.
TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects.
<Callout title="Why Vitest?">
If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes.
It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration.
</Callout>
## Why write unit tests?
Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation.
They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later.
In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or endtoend tests.
## Configuration
TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects).
```ts title="vitest.config.ts"
import { mergeConfig } from "vitest/config";
import baseConfig from "@turbostarter/vitest-config/base";
export default mergeConfig(baseConfig, {
test: {
/* your extended test configuration here */
},
});
```
* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace.
* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests.
* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo.
This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development.
You can read more about this setup in the official documentation guides listed below.
<Cards>
<Card title="Vitest" description="turborepo.com" href="https://turborepo.com/docs/guides/tools/vitest" />
<Card title="Test Projects" description="vitest.dev" href="https://vitest.dev/guide/projects" />
</Cards>
## Running tests
There are a few different ways to run unit tests, depending on what you're doing:
* **CI / full test run** - at the root of the repo:
```bash
pnpm test
```
This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline.
* **One-off local run with Vitest Projects**:
```bash
pnpm test:projects
```
This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally.
* **Watch mode during development**:
```bash
pnpm test:projects:watch
```
This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work.
## Code coverage
Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions.
To generate a code coverage report for all unit tests, run:
```bash
pnpm turbo test:coverage
```
This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output.
To open the coverage report in your browser:
```bash
pnpm turbo test:coverage:view
```
This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered.
<Callout title="Uploading coverage as an artifact">
You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job:
```yaml title=".github/workflows/ci.yml"
# your workflow job configuration here
- name: 📊 Generate coverage
run: pnpm turbo test:coverage
- name: 🗃️ Archive coverage report
uses: actions/upload-artifact@v5
with:
name: coverage-${{ github.sha }}
path: tooling/vitest/coverage/report
```
This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection.
</Callout>
A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health.
![Code coverage](/images/docs/code-coverage.png)
## Best practices
Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard.
Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of highvalue tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain.
When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it.
Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole.

View File

@@ -0,0 +1,89 @@
---
title: Installation
description: Find answers to common mobile installation issues.
url: /docs/mobile/troubleshooting/installation
---
# Installation
## Cannot clone the repository
Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github.
If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh)
If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown.
Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same.
## Local database doesn't start
If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running.
Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine.
Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance.
## I don't see my translations
If you don't see your translations appearing in the application, there are a few common causes:
1. Check that your translation `.json` files are properly formatted and located in the correct directory
2. Verify that the language codes in your configuration match your translation files
3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs
[Read more about configuration for translations](/docs/mobile/internationalization#configuration)
## Expo cannot detect XCode
If you get the following error:
```bash
Expo cannot detect Xcode Xcode must be fully installed before you can continue
```
This is usually related to the Xcode CLI not being installed. You can fix this by running the following command:
```bash
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
```
If you still face the issue, please make sure you have the latest version of Xcode installed.
## "Module not found" error
This issue is mostly related to either dependency installed in the wrong package or issues with the file system.
The most common cause is incorrect dependency installation. Here's how to fix it:
1. Clean the workspace:
```bash
pnpm clean
```
2. Reinstall the dependencies:
```bash
pnpm i
```
If you're adding new dependencies, make sure to install them in the correct package:
```bash
# For main app dependencies
pnpm install --filter mobile my-package
# For a specific package
pnpm install --filter @turbostarter/ui my-package
```
If the issue persists, please check the file system for any issues.
### Windows OneDrive
OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this:
1. Move your project to a location outside of OneDrive-synced folders (recommended)
2. Disable OneDrive sync specifically for your development folder
This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files.

View File

@@ -0,0 +1,75 @@
---
title: Publishing
description: Find answers to common mobile publishing issues.
url: /docs/mobile/troubleshooting/publishing
---
# Publishing
## My app submission was rejected
If your app submission was rejected, you probably got an email with the reason. You'll need to fix the issues and upload a new build of your app to the store and send it for review again.
Make sure to follow the [guidelines](/docs/mobile/marketing) when submitting your app to ensure that everything is setup correctly.
## App Store screenshots don't match requirements
If your app submission was rejected due to screenshot issues, make sure:
1. Screenshots match the required dimensions for each device
2. Screenshots accurately represent your app's functionality
3. You have provided screenshots for all required device sizes
4. Screenshots don't contain device frames unless they match Apple's requirements
[See Apple's screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications/)
## Version number conflicts
If you get version number conflicts when submitting:
1. Ensure your `app.json` version matches what's in the store
2. Increment the version number appropriately:
```bash
"version": "1.0.1",
"android.versionCode": 2,
"ios.buildNumber": "2"
```
3. Make sure both stores have unique version numbers
## Missing or incorrect environment variables
If your build succeeds but the binary is misconfigured (e.g., API URL shows as `undefined`, Sentry auth fails, or `app.config.*` settings dont apply), verify your EAS environment variables:
1. Define variables on EAS and assign them to the correct environment (`development`, `preview`, `production`).
2. For values used in app code, prefix with `EXPO_PUBLIC_` and read via `process.env.EXPO_PUBLIC_...`.
3. For config-time values (bundle identifiers, file paths), read `process.env.VARNAME` from your `app.config.*`.
4. Explicitly set `environment` in `eas.json` build profiles, or pass `--environment` to `eas update` so updates use the same variables as builds.
5. For local development, pull variables into a `.env` file:
```bash
eas env:pull --environment development
```
6. Use secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`.
7. Keep `.env` out of git; cloud builds dont rely on your local `.env`.
See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/).
## My app crashes on production build
If the app works in development but crashes in a production build, check these common causes:
1. **Missing or incorrect environment variables at build time**. EAS cloud jobs dont use your local `.env` by default. Ensure variables exist on EAS, are assigned to the correct environment, and use `EXPO_PUBLIC_` for values read in app code. See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/).
2. **Missing native config files**. Provide `google-services.json` / `GoogleService-Info.plist` via secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`.
3. **Production-only code paths**. Guard dev-only code with `__DEV__`, avoid importing dev tools in production, and ensure feature flags dont access undefined values.
4. **Misconfigured native modules or plugins**. Verify required plugins/babel config are present and rebuild after cache clears.
Try this:
1. Run the app with a production JS bundle locally to surface minification issues:
```bash
npx expo start --no-dev --minify
```
2. Inspect device logs when the crash occurs (Android: `adb logcat`, iOS: Console.app or Xcode Devices).
3. Rebuild with a clean cache if needed:
```bash
eas build --clear-cache
```