Add features, screens and API calls
pnpm gen writes the code for a new feature, screen or API call, already following the project’s rules
(folders, imports, translations, accessibility, tests) and wired into the app. You start from something that
runs and passes pnpm check, then fill in what’s specific to your app.
There are three commands:
| You want to | Run |
|---|---|
| Add a section of the app that lists things (orders, messages, products) | pnpm gen feature orders |
| Add a screen to a feature (a detail view, a form) | pnpm gen screen orders order-detail |
| Call another API endpoint | pnpm gen endpoint orders cancel-order --method post --path /orders/:id/cancel |
Names are kebab-case: orders, order-history. Run the commands from the project root. Each one prints the
files it changed and what to do next.
Add a feature
Section titled “Add a feature”pnpm gen feature ordersReload the app (press r in Metro) and open the new screen with its deep link, for example on Android:
adb shell am start -W -a android.intent.action.VIEW -d "acme-shop-dev://orders"Use your own scheme in place of acme-shop-dev (it’s APP_SCHEME in .env.dev). On iOS:
xcrun simctl openurl booted "acme-shop-dev://orders".
You get a list of five sample orders, with a loading state, an empty state, an error state with Try again, and pull to refresh. The sample data comes from the mock backend, so it works before your API exists.
What it adds:
| Where | What |
|---|---|
src/features/orders/schema.ts |
The shape the server sends and the Order type the app uses |
src/features/orders/api.ts |
The GET /orders request (ordersQueryOptions) |
src/features/orders/screens/OrdersScreen.tsx |
The list screen, and a test next to it |
src/features/orders/index.ts |
What the rest of the app may import from the feature |
src/mocks/orders.ts |
Five sample orders and a fake GET /orders |
en.json, ar.json |
The screen title and the empty and error messages |
RootNavigator.tsx |
An Orders screen with the deep link orders |
The item name comes from the feature name without its last “s” (orders → Order). When that’s wrong,
name it yourself:
pnpm gen feature people --item personThen make it yours
Section titled “Then make it yours”- Fields. The sample has
id,titleandcreated_at. Change them inschema.ts(both the server shape and the app type) and insrc/mocks/orders.ts. Onlyschema.tsuses the server’s field names; the rest of the app uses the app type. - Arabic. The new keys in
ar.jsonare in English. Translate them. - A way in. Nothing links to the screen yet. Add a button, a Settings row or a tab that calls
navigation.navigate('Orders'). For a tab, move theOrdersentry intoTabsinRootNavigator.tsx. - Run
pnpm check.
Add a screen
Section titled “Add a screen”pnpm gen screen orders order-detail --param idThe feature must exist first. This adds OrderDetailScreen (and a test) to src/features/orders/screens/, a
translated title, and an OrderDetail screen in the navigator with the deep link orders/order-detail/:id.
Open it from code with:
navigation.navigate('OrderDetail', { id: order.id });--param is optional; leave it out for a screen without one. The screen checks the param when it opens,
because a deep link can send anything, and shows an error state when it’s missing.
The screen starts as a title on a page. Build it with the components in src/shared/ui/primitives/, and put
all text in en.json and ar.json.
Call an API endpoint
Section titled “Call an API endpoint”To read data (GET):
pnpm gen endpoint orders order-detail --path /orders/:idTo change data (POST, PUT, PATCH or DELETE):
pnpm gen endpoint orders cancel-order --method post --path /orders/:id/cancelEach :param in the path becomes an argument. --path defaults to /<feature>, and --method to get.
For a GET you get orderDetailQueryOptions(id), which you use in a screen with
useQuery(orderDetailQueryOptions(id)). For a change you get a hook, useCancelOrder(), which reloads the
feature’s data when it succeeds:
const cancel = useCancelOrder();cancel.mutate({ id: order.id });Both come with the response shape in schema.ts, a fake handler in src/mocks/orders.ts and a test.
Then make it yours
Section titled “Then make it yours”- Response fields. The sample response is just
{ id }. Add the real fields inschema.tsand the mock. - Request body. Replace
body?: Record<string, unknown>with the fields you send. - Offline behaviour. A generated change fails straight away when the phone is offline. That’s right for payments and sign-in. For likes, edits and other small changes, switch it to a queued write that applies at once and syncs later: see the offline patterns in AGENTS.md and the example in architecture.md.
- Run
pnpm check.
With an AI agent
Section titled “With an AI agent”Ask for the outcome, for example “add an orders feature with a cancel button”. Claude Code uses the project’s
add-feature, add-screen and add-endpoint skills: it runs these commands, then does the “make it yours”
steps. Cursor and Copilot are pointed at the same instructions. Check its work with pnpm check and by
opening the screen.
When a command stops
Section titled “When a command stops”The generator checks before writing, so when it stops, nothing has changed.
| Message | What to do |
|---|---|
"Orders" isn't kebab-case |
Use lowercase words joined by hyphens: orders, order-history |
src/features/orders already exists. |
Pick another name, or use pnpm gen screen / pnpm gen endpoint to add to it |
src/features/orders doesn't exist. |
Create the feature first: pnpm gen feature orders |
The navigator already has a "OrderDetail" route. |
Route names are shared across the app. Pick another screen name |
… schema.ts already has OrderSchema. Pick another endpoint name. |
The feature already uses that name, usually for its list item. A GET for one order in orders is order-detail |
… has no "// gen:screens" line (or // gen:handlers) |
Someone removed a marker comment. Put // gen:screens back at the end of the signed-in screens in RootNavigator.tsx, or // gen:handlers at the end of the list in src/mocks/handlers.ts |
already has "orders.title" |
That translation key exists. Remove the old key or pick another name |
What it doesn’t do
Section titled “What it doesn’t do”- It never replaces or deletes a file; it creates new ones and adds to existing ones. To undo a run, discard the changes with git.
- It doesn’t add a tab, a button or a menu entry that leads to the new screen. You choose where it goes.
- It doesn’t translate. The Arabic strings start as English copies.
- It doesn’t know your API. Fields, request bodies and error cases come from you; the generated mock is a starting point to change to match your server.