Mobile apps · React Native

Docs › Mobile apps

React Native

Expo or bare, on the New Architecture: wrap the app, tap a view, and the note has its component and file.

People tap a view in your running React Native app and write what should change, and the note reaches your coding agent (Claude Code, Codex, Cursor and others) over MCP through a Notato server. Each note has a screenshot with the view outlined and numbered, a crop of the view, its text, test id and label, the app's recent warnings, and its component: the one you wrote, the components around it, and the file and line it is in (ProductCard in App › Shop › ProductList › ProductCard, at src/App.tsx:18:1).

It is the same client as the Swift, Android, Flutter and .NET MAUI SDKs: the toolbar and its ⋯ menu, the three modes, threads and replies, People only and asides, revert, an offline queue, masking, and a runtime API. Variants are web-only.

React Native 0.76 and later, with the New Architecture, in Expo (Expo Go included) or a bare app.

Set up#

npm i -D notato
npx expo install @notato/react-native react-native-view-shot expo-file-system expo-sharing
npx notato        # the server, the board and MCP for your agent

Wrap your app in it:

import { Notato } from "@notato/react-native"
import { expoStorage } from "@notato/react-native/expo"

export default function App() {
    return (
        <Notato project="shop" appName="Shop" storage={expoStorage} route={() => navigationRef.getCurrentRoute()?.name}>
            <Navigation />
        </Notato>
    )
}

A small dark toolbar appears in the corner. Drag it anywhere; it stays where you leave it, and its chevron folds it into a round button. Tap Annotate, tap a view, write the note, and press Send. A numbered pin marks it, and turns amber when the agent is on it and green when it is resolved; tap a pin for the note's card. Then ask your agent to "watch Notato and fix what comes in". Everything else is in the toolbar's ⋯ sheet: pins, the notes list, settings, hiding the toolbar or turning Notato off, and the server's state, with Retry when it cannot be reached. React Native's developer menu has Notato: annotate and Notato: show or hide the toolbar.

Storage. storage={expoStorage} keeps notes not sent yet, test-mode notes, their screenshots and people's choices (on or off, the toolbar's place, a name) in the app's documents folder, and shares a test-mode package through the share sheet. It needs expo-file-system and expo-sharing, which work in Expo Go and in any app with Expo modules. Without it, all of that lasts until the app restarts. A bare app without Expo modules can pass its own StorageProvider: its open(project) returns a NotatoStorage, and both types are exported.

Reaching the server. The default is http://localhost:4747. The iOS simulator reaches it as it is. The Android emulator, and a phone plugged in over USB, reach it once the port is forwarded:

adb reverse tcp:4747 tcp:4747

A phone on Wi-Fi needs the server's dev tunnel: pass its URL as server and the device token as token, from your app's environment (EXPO_PUBLIC_NOTATO_SERVER in Expo).

Props#

PropDefault
project(required)Project id on the server. Letters, digits and . _ - @, not only dots
modedevdev, test or agent (see below)
serverhttp://localhost:4747 (none in test mode)null for no server: notes stay on the device
tokenA project token (notato_…) for a shared notato serve
storagein memoryexpoStorage, or your own StorageProvider
route/The screen, or a function that says it: notes are filed under it, and its pins shown on it
enableddevelopment builds (__DEV__)Whether Notato is on at launch. The app can switch it at runtime. Picking a view needs a development build either way
showToolbar, toolbarPositiontrue, bottom-rightThe toolbar and the corner it starts in (bottom-left, top-right, top-left). Where people drag it is remembered
authorThe name on this person's notes. They can change it in the settings
screenshotstrueA server that has screenshots off wins either way
maskInputson in test and agent modeCover text fields in screenshots and leave their values out of notes (password fields always are). See What is never recorded
rememberRuntimeStatetrueKeep runtime choices (on or off, toolbar, name, a server typed in, the toolbar's place) across launches, with storage
captureLogs, logLimittrue, 50Attach the app's recent console.warn and console.error messages
appName, appVersionReact Native appRecorded on every note
maxScreenshotScale2Pixels per point screenshots are kept at. Phones are 2x to 3.5x; 2x is plenty

In a release build <Notato> is your app and nothing else, unless enabled is true when it mounts (that is decided once, so the app is never mounted again because Notato came or went). Mounting <Notato> again with the same props, as React's StrictMode does, leaves Notato running, with the notes it had.

At runtime: notato#

useNotato()A hook with Notato's state: on or off, toolbar, annotating, connection, the notes, how many are not sent yet
enable(), disable(), setEnabled(on)On and off. A choice made here wins over enabled until resetRuntimeState()
showToolbar(), hideToolbar()The toolbar. Off still lets the app drive Notato from code
startAnnotating(), stopAnnotating()The next tap selects what is under it
select(ref), select("#save")Select a view (a ref, or a selector) as if it had been tapped, and open the note for it
annotate("#save", comment, options)Make a note with no UI, as a person or (with agentName) an agent. peopleOnly: true keeps a person's note from the agent
packageNotes({ upload })Test mode: the device's notes as a bundle zip
recordRequest(entry)Add an HTTP request (method, url, status, durationMs) to the network context of later notes
import { notato, useNotato } from "@notato/react-native"

const state = useNotato()
<Switch value={state.enabled} onValueChange={(on) => (on ? notato.enable() : notato.disable())} />

The example's Feedback card switches Notato and its toolbar, and annotates a price from code.

What the agent gets#

A view is picked the way React Native's own Element Inspector picks one, through the development build's inspector, so it is the view under your finger: the innermost one. Parent in the composer steps out to the view around it. The note says:

What is never recorded#

import { NotatoMask } from "@notato/react-native"

<NotatoMask><Text>{user.email}</Text></NotatoMask>                  // private: this and everything in it
<NotatoMask private={false}><TextInput placeholder="Search" /></NotatoMask>   // recorded even with maskInputs

Password fields stay masked whatever the mark, and a private mark around a private={false} one wins over it. <NotatoMask> renders its children and nothing else. It is the web SDK's data-notato-mask.

Selectors#

What a note's selector looks like, and what an agent passes to notato_annotate or notato.annotate:

#saveThe view's testID
Text, buttonThe view's kind (Text, View, TextInput), its role, or the component that rendered it; * for any
[label="Pay now"]The accessibility label, exactly
:text("Add to cart")The text or label contains this, ignoring case
:nth(2)The second match, in drawing order
ProductCard > …Leading names are the components around it; one the app does not have (a screen's name) is ignored

Modes#

People only and asides#

Everything people write reaches the agent: new notes and every reply. Two switches keep something between people instead:

Known limits#