Set up your app

For AI agents: this page is Markdown at https://squiggle.sh/docs/setup.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.

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.
    Terminal
    npm install -g @squiggle.sh/clisquiggle 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.
    Terminal
    squiggle init --agent claude

    For Claude Code: also registers Squiggle in .mcp.json and installs the agent kit (skills, subagents and commands) in .claude/.

  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.
    app/_layout.tsx
    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.
    Terminal
    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.
    Terminal
    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.
    Terminal
    squiggle flow smokesquiggle record loginsquiggle 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.

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.

FileWhat init does
package.jsonAdds @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.jsAdds the Babel plugin. In an Expo app with no Babel config, it creates the file.
metro.config.jsWraps 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.jsPuts 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.yamlA first flow, smoke, that waits for the app to settle and checks the screen isn’t blank.
.squiggle/config.jsoncAdds default performance budgets for every screen under "*", for you to tighten. See Set a budget per screen.
.squiggle/.gitignoreKeeps the project folder out of git, except your flows, your declared interfaces (interfaces/) and config.jsonc.
.github/workflows/squiggle.ymlOnly 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.
.mcp.jsonWith --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":

index.js
// index.js: the SDK first, then your app's usual entryimport '@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:

babel.config.js
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:

metro.config.js
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.

Next