# Set up your app

Add Squiggle to a React Native app and check that it works. Most of the time goes on one native rebuild.

## Before you start

- An Apple Silicon Mac with macOS 15 or later.
- Xcode, with a working iOS simulator (`xcrun simctl` runs).
- React Native 0.76 or later and React 18.3 or later, in an app you can rebuild: an Expo development build or a bare React Native app. Expo Go can’t load the SDK, because it includes native code.

## Set it up

1. ### Install Squiggle
   
   Studio, the app you download, includes the `squiggle` command: open Settings and press Install next to Command line. To use only the terminal, install it from npm. Then start the trial: 14 days, every feature, no card.
   
   ```shell
   npm install -g @squiggle.sh/cli
   squiggle activate --trial
   ```
2. ### Set up your app
   
   Run this in your app’s folder. It adds the SDK and the Babel plugin, loads Squiggle before React, and connects your coding agent. It lists every change and asks before writing anything.
   
   ```shell
   squiggle init --agent claude
   ```
   
   For Claude Code: also registers Squiggle in .mcp.json and installs the agent kit (skills, subagents and commands) in .claude/.
   
   ```shell
   squiggle init --agent copilot
   ```
   
   For GitHub Copilot in VS Code: also registers Squiggle in .vscode/mcp.json.
   
   ```shell
   squiggle init --agent codex
   ```
   
   For Codex: also registers Squiggle in ~/.codex/config.toml.
   
   ```shell
   squiggle init
   ```
   
   Sets up the app only. Run it again with --agent when you want one.
3. ### Track your screens
   
   The one edit `init` leaves to you, because it is your own component. Wrap your root so Squiggle knows which screen is showing: waiting for a screen, checking it, and the Screen map all need it. With React Navigation, pass your `NavigationContainer` ref.
   
   ```ts
   import { Squiggle } from '@squiggle.sh/react-native';
   import { Stack, useNavigationContainerRef } from 'expo-router';
   
   export default function RootLayout() {
     return (
       <Squiggle navigationRef={useNavigationContainerRef()}>
         <Stack />
       </Squiggle>
     );
   }
   ```
4. ### Rebuild once
   
   The SDK includes native code, so a reload is not enough. `squiggle up` relaunches the app with Squiggle attached; `init` added it to your `ios` script, so from now on that script does both. Bare React Native: `npx react-native run-ios`.
   
   ```shell
   npx expo run:ios && squiggle up
   ```
5. ### Check the setup
   
   `doctor` checks the simulator, the app, screen tracking, source lines and your licence, and tells you how to fix anything that is missing.
   
   ```shell
   squiggle doctor
   ```
6. ### Run your first flow
   
   `smoke` is the flow `init` wrote: it checks the app starts. Then record a journey of your own: use the app, press Ctrl-C to stop, and Squiggle drafts the flow from the recording.
   
   ```shell
   squiggle flow smoke
   squiggle record login
   squiggle flow --from-recording .squiggle/recordings/login.sqrec -o .squiggle/flows/login.yaml
   ```

The first `squiggle up` in a project takes a few seconds longer: it watches the app and keeps what it saw as a performance baseline named `first`, so you have something to compare a change with from day one. `squiggle up --no-baseline` skips it. See [The first baseline is automatic](https://squiggle.sh/docs/performance/baselines#the-first-baseline-is-automatic).

## What init changes

`squiggle init` lists each change and asks before writing anything. `squiggle init --dry-run` prints the list and writes nothing. Running it again later changes nothing that is already done.

| File | What init does |
| --- | --- |
| `package.json` | Adds @squiggle.sh/react-native and @squiggle.sh/babel-plugin with your package manager, and adds && squiggle up to your ios script so a rebuild relaunches with Squiggle attached. |
| `babel.config.js` | Adds the Babel plugin. In an Expo app with no Babel config, it creates the file. |
| `metro.config.js` | Wraps your Metro config in withSquiggle, so the report card can say which modules your app evaluates at startup. In an Expo app with no Metro config, it creates the file. |
| `index.js` | Puts the SDK import on the first line of your entry file. An Expo Router app gets a new index.js, and "main" points at it. |
| `.squiggle/flows/smoke.yaml` | A first flow, smoke, that waits for the app to settle and checks the screen isn’t blank. |
| `.squiggle/config.jsonc` | Adds default performance budgets for every screen under `"*"`, for you to tighten. See [Set a budget per screen](https://squiggle.sh/docs/performance/baselines#set-a-budget-per-screen). |
| `.squiggle/.gitignore` | Keeps the project folder out of git, except your flows, your declared interfaces (interfaces/) and config.jsonc. |
| `.github/workflows/squiggle.yml` | Only in a git repository, and only if the file doesn’t exist: a pull request check that fails when your iOS bundle grows past its allowance. It builds the bundle with `npx expo export`, so in an app without Expo, change those steps to your own bundle command. See [Run in CI](https://squiggle.sh/docs/run/ci#keep-the-app-from-getting-worse). |
| `.mcp.json` | With --agent claude: registers the MCP server, and installs the agent kit in .claude/. Copilot uses .vscode/mcp.json and Codex uses ~/.codex/config.toml. |

`init` merges into files that already exist. It never replaces your `.mcp.json` or the other servers in it.

## If init can’t make a change

When `init` doesn’t recognise a file, it leaves it alone and prints a to-do instead. These are the edits it would have made.

### Load the SDK first

The SDK import must be the first line of your app’s entry file, before anything loads React. Otherwise Squiggle misses the screens your app renders at startup. If `package.json` has `"main": "expo-router/entry"`, create an `index.js` like this and set `"main"` to `"index.js"`:

```ts
// index.js: the SDK first, then your app's usual entry
import '@squiggle.sh/react-native';
import 'expo-router/entry';
```

### Add the Babel plugin

The plugin gives each component its file and line, so the Inspector can open your editor at the right place. It also records reads and writes for MMKV, AsyncStorage, SQLite and Legend State. Keep `babel-preset-expo` as the only preset:

```ts
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['@squiggle.sh/babel-plugin'],
  };
};
```

### Wrap your Metro config

`withSquiggle` times each module your app evaluates while it starts, in a development build. The report card uses it to name the screens your app loads at startup though it doesn’t open on them. It changes nothing in a production build. Wrap whatever your config exports:

```ts
const { getDefaultConfig } = require('expo/metro-config');
const { withSquiggle } = require('@squiggle.sh/react-native/metro');

const config = getDefaultConfig(__dirname);

module.exports = withSquiggle(config);
```

In a bare React Native app, wrap the config `mergeConfig` returns. `withSquiggle` works with other wrappers such as `withNativeWind`, in either order. Without it, the report card’s startup row reports “not measured”, and so can the cold-start phase budgets.

### Relaunch with Squiggle attached after a rebuild

If your `ios` script isn’t a plain `expo run:ios` or `react-native run-ios`, add `&& squiggle up` to the end of it yourself.

> **If doctor reports a problem**
>
> Each line `doctor` prints names the fix. For anything else, including a monorepo, Yarn 1 and `command not found`, see [Troubleshooting](https://squiggle.sh/docs/troubleshooting).

## Next

- [Your first flow](https://squiggle.sh/docs/first-flow): Run the smoke flow, then record a journey of your own.
- [Licence and trial](https://squiggle.sh/docs/licence): What happens when the trial ends, and how to run Squiggle in CI.
