Cursor Composer Mode: Multi-File Code Generation Tutorial

Use Cursor Composer to generate and edit code across multiple files at once. Step-by-step guide with real examples for Next.js and TypeScript projects.

Problem: Cursor Chat Only Edits One File at a Time

You're building a feature — a new API route, a React component with its types, a database schema with a migration. You open Cursor Chat, describe what you want, and it writes perfect code... for one file. Then you paste it, switch to the next file, and repeat.

Composer mode eliminates this. It reads and writes across your entire codebase in a single prompt.

You'll learn:

  • How to open and configure Composer for multi-file edits
  • How to write prompts that produce correct diffs across 3–5 files
  • How to review, accept, and reject individual file changes before they land

Time: 20 min | Difficulty: Intermediate


Why Chat Falls Short for Multi-File Work

Cursor Chat operates on a single active file plus whatever context you manually pin. It generates code you copy-paste yourself. For anything spanning more than two files — routes + types + tests + schema — the cognitive overhead compounds fast.

Composer uses the same model but operates in Agent mode by default. It:

  • Reads existing files before writing
  • Creates new files when needed
  • Proposes diffs you review before applying
  • Can run terminal commands (npm install, migrations, etc.) mid-generation

The tradeoff: Composer uses more context tokens per run and requires you to scope prompts carefully.


Solution

Step 1: Open Composer

macOS:  Cmd + Shift + I
Windows/Linux:  Ctrl + Shift + I

Or go to View → Composer from the menu bar.

You'll see a new panel — separate from Chat — with a larger input area and a file list sidebar on the left. That sidebar shows every file Composer has touched in the current session.

If you don't see Composer:

  • Cursor version < 0.40 → Update via Cursor → Check for Updates. Composer shipped in 0.40; Agent mode became default in 0.42.
  • Keyboard shortcut doesn't open it → Check Settings → Keyboard Shortcuts and search "Composer" to reassign.

Step 2: Set the Right Model

Composer works with any model Cursor supports, but for multi-file generation you want maximum context and instruction-following.

Click the model selector at the top-right of the Composer panel:

  • claude-sonnet-4-5 — best for large refactors, understands existing code structure well
  • gpt-4o — fast, good for greenfield generation
  • cursor-small — avoid for multi-file work; context window is too small

For most multi-file generation tasks, set this to claude-sonnet-4-5 or gpt-4o.


Step 3: Add Context Files Explicitly

Composer reads your codebase but it doesn't read everything by default. Tell it exactly what to look at.

Use @ to reference files, folders, or symbols:

@src/lib/db.ts @src/types/user.ts

Add a POST /api/users/invite route that:
- Accepts { email: string, role: "admin" | "member" }
- Validates with the existing UserSchema in db.ts
- Inserts into the users table
- Returns the created user with a 201 status

Why explicit context matters: Without @, Composer uses embeddings to guess relevant files. For new features it may miss your existing types, enums, or utility functions — and invent its own.

Reference your @ context before the instruction, not after. The model processes context top-to-bottom.


Step 4: Write a Scoped Prompt

Vague prompts produce vague multi-file output. The more files Composer touches, the more specific your prompt needs to be.

Bad prompt:

Build a user invite feature

Good prompt:

@src/lib/db.ts @src/types/user.ts @src/app/api/

Create a POST /api/users/invite route in a new file
src/app/api/users/invite/route.ts.

Requirements:
- Import UserSchema from @src/lib/db.ts for validation
- Accept body: { email: string, role: "admin" | "member" }
- Use the existing db client from @src/lib/db.ts
- Return 201 with the created user on success
- Return 422 with validation errors on bad input

Also update src/types/user.ts to add InvitePayload type.
Do NOT modify any other files.

The Do NOT modify any other files line is important — without it, Composer sometimes "helpfully" updates index files, barrel exports, or configs you didn't ask for.


Step 5: Review the Diff Before Accepting

Composer shows a unified diff for every file it wants to change. Do not click Accept All without reviewing.

The diff view works like a PR review:

  • Green lines — additions
  • Red lines — deletions
  • File tabs at top — switch between changed files

For each file, you can:

  • Accept — apply this file's changes
  • Reject — discard and keep original
  • Edit — open the file and manually adjust before accepting

A common pattern: accept the new route file, reject an unwanted barrel export update, then re-run Composer with a narrower prompt for any file it got wrong.

# After rejecting src/types/user.ts:

@src/types/user.ts

Only add this type to the file, nothing else:

export type InvitePayload = {
  email: string;
  role: "admin" | "member";
};

Step 6: Let Composer Run Terminal Commands (Optional)

If you have Agent mode enabled (default in Cursor 0.42+), Composer can run shell commands mid-task. It will ask before executing.

Useful for:

After creating the files, run:
npm run typecheck

Composer will generate the files, then execute npm run typecheck and feed the output back into the same context window. If there are type errors, it attempts a fix automatically.

To disable auto-run prompts: Settings → Cursor Settings → Composer → Ask before running terminal commands → toggle on.


Verification

After accepting all changes, run your project's type checker and linter:

# TypeScript projects
npx tsc --noEmit

# With ESLint
npx eslint src/app/api/users/invite/route.ts

You should see: Zero errors. If Composer imported a type incorrectly or used the wrong db client method, tsc --noEmit catches it before you run the dev server.

Test the new route manually:

curl -X POST http://localhost:3000/api/users/invite \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","role":"member"}'

Expected: 201 with the created user object. A 422 with a validation message means the route exists and is working — just rejecting invalid input as intended.


What You Learned

  • Composer operates across your full codebase; Chat operates on one file
  • Explicit @file context prevents Composer from inventing types or utilities you already have
  • Do NOT modify any other files keeps multi-file runs scoped
  • Review diffs per-file — accept, reject, or edit before applying
  • Agent mode with npm run typecheck closes the feedback loop inside Composer itself

Limitation: Composer struggles with monorepos that have more than ~8 packages. For large refactors across deeply nested workspaces, scope your prompt to one package at a time and run Composer in sequence.

Tested on Cursor 0.45.x, Next.js 15, TypeScript 5.4, macOS and Ubuntu 22.04