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 simctlruns). - 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
Install Squiggle
Studio, the app you download, includes thesquigglecommand: 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.npm install -g @squiggle.sh/clisquiggle activate --trialSet 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.ClaudeCopilotCodexNonesquiggle init --agent claudeFor Claude Code: also registers Squiggle in .mcp.json and installs the agent kit (skills, subagents and commands) in .claude/.
squiggle init --agent copilotFor GitHub Copilot in VS Code: also registers Squiggle in .vscode/mcp.json.
squiggle init --agent codexFor Codex: also registers Squiggle in ~/.codex/config.toml.
squiggle initSets up the app only. Run it again with --agent when you want one.
Track your screens
The one editinitleaves 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 yourNavigationContainerref.import { Squiggle } from '@squiggle.sh/react-native';import { Stack, useNavigationContainerRef } from 'expo-router';export default function RootLayout() { return ( <Squiggle navigationRef={useNavigationContainerRef()}> <Stack /> </Squiggle> );}Rebuild once
The SDK includes native code, so a reload is not enough.squiggle uprelaunches the app with Squiggle attached;initadded it to youriosscript, so from now on that script does both. Bare React Native:npx react-native run-ios.npx expo run:ios && squiggle upCheck the setup
doctorchecks the simulator, the app, screen tracking, source lines and your licence, and tells you how to fix anything that is missing.squiggle doctorRun your first flow
smokeis the flowinitwrote: 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.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.
| 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. |
.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. |
.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":
// 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:
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:
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.