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.

Screenshot size. New captures prefer WebP at quality 85 when it is smaller than PNG, after privacy masking and at the same dimensions. PNG remains a fallback, and existing queues and bundles stay readable. Rebuild the native app (and install iOS pods) to enable the optional encoder; Expo Go and older app binaries continue with PNG. See screenshot storage.

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.

The documents folder is in the device's backups (iCloud, Android's auto backup), and Expo cannot keep one file out of them, so a tester's screenshots can end up there. To keep them out, exclude the notato folder in your Android backup rules (<exclude domain="file" path="notato/" /> in dataExtractionRules and fullBackupContent), or pass a StorageProvider that writes where backups do not reach.

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, in development builds only. The device token opens notato dev on your machine to whoever has it, so keep it out of anything you hand to others: never put it in an EXPO_PUBLIC_ variable (Expo writes those into every bundle, release builds included), and never commit it. Read it from a file your repository ignores, required only in development, which Metro leaves out of a release bundle:

const tunnel = __DEV__ ? require("./notato.local.json") : {}   // { "server": "https://…devtunnels.ms", "token": "notato_device_…" }

<Notato project="shop" server={tunnel.server} token={tunnel.token} storage={expoStorage}>

Notato never sends a token from a release build unless you set allowTokenInRelease, and never the device token, or any token to a dev tunnel, whatever you set.

Builds for testers. A build you hand out (TestFlight, an internal track, a device farm) holds whatever is in it, and anyone with it can read that out. Give it a shared server (notato serve) and a capture-only project token, and set allowTokenInRelease:

npx notato token create shop --purpose app --capture-only --expires 30

A capture-only token can send notes and nothing else: one read out of the app cannot read, change or delete anyone's feedback. Notato asks the server what the token may do before it connects, then sends notes (and those kept on the device) without following the project; replies, status and revert are on the board, and a note's card says so. A test-mode package uploads its captured notes to the configured server too; it grants no access to existing feedback.

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. Sent from development builds only, unless allowTokenInRelease
allowTokenInReleasefalseSend token from a release build too: a capture-only token only. Never a dev tunnel's device token (see Builds for testers)
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
screenshotstrueWith a server set (test mode's too), none is taken until it says it takes them, and its "off" wins either way
captureSecureScreensfalseScreenshot Android screens marked FLAG_SECURE too. Off, they get none (apps built with the native module; Expo Go cannot tell)
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#

Replies reach the agent within its active feedback scope. Dev feedback is private to its creator by default; Test feedback is Shared. Change a note's visibility in the board to share it with the project. People only and Aside are additional ways to keep feedback between people:

Known limits#

Hiding completed annotations#

On a resolved or reverted note, open More options → Hide from this app to hide its pin and card on this device. In Settings, Hide completed annotations hides completed notes across every screen at once, including notes beyond the 150 drawn pins. Restore hidden annotations brings them back.

Hiding keeps the feedback, screenshots and thread in the dashboard and exports. It never changes the note on the server. Reopened tasks and new agent questions remain visible. The preference is scoped to the server, project, mode, credential and name, and survives app restarts when runtime preferences are remembered.