Writing Features from Scratch
Building complete features is where Claude Code truly shines. You describe what you want in plain English, Claude makes a plan, writes the code across multiple files, and verifies it works. This lesson teaches you how to guide Claude through complex feature development with confidence.
Learning Objectives
- Decompose large features into clear, verifiable steps
- Write prompts that guide Claude to follow existing codebase patterns
- Orchestrate multi-file changes with consistency
- Apply the Explore → Plan → Implement → Commit workflow
- Verify features work before considering them complete
The Feature Development Workflow
At its core, feature development with Claude Code is simple: you describe what you want, and Claude handles the implementation details. But there's an important truth: the quality of your prompt directly determines the quality of the output.
Think of it like working with a senior developer who's new to your codebase. They're highly capable, but they need context about your patterns, conventions, and constraints. Give them that context upfront, and they'll deliver exactly what you need.
Here's the fundamental workflow:
- You describe the feature in plain English
- Claude explores your codebase to understand existing patterns
- Claude creates a plan showing what files will change and why
- Claude implements the feature across multiple files
- Claude verifies the implementation works
- You review and commit the changes
The magic happens when you learn to guide each of these steps effectively.
Requirement Decomposition
Large features are overwhelming. "Add user authentication" is a massive undertaking that touches dozens of files, requires security considerations, and involves multiple integration points. Claude can handle it, but you'll get better results by breaking it down.
| Specific, Verifiable Steps | Vague, Broad Requests |
|---|---|
| Add Google OAuth login button to the login page | add user authentication |
| Create a /auth/google/callback route that exchanges codes for tokens | make login work with google |
| Store OAuth tokens in the session using our existing session pattern | handle oauth stuff |
| Write integration tests for the OAuth flow | add social login |
| Update the User model to include a googleId field |
Notice how the specific prompts tell Claude:
- What to build (OAuth button, callback route)
- Where to put it (login page,
/auth/google/callback) - How to integrate (use existing session pattern, update User model)
- Verification (write integration tests)
This decomposition gives Claude clear success criteria. After each step, you can verify the feature works before moving to the next piece.
Progressive Prompt Refinement
Let's see how a vague prompt becomes actionable:
Attempt 1 (Too Vague):
add user authentication
Claude will ask clarifying questions because it doesn't know what authentication method, how to store credentials, what your session strategy is, etc.
Attempt 2 (Better, But Still Broad):
Add Google OAuth login to our app
Claude knows the authentication method, but still needs to know where your auth logic lives, how you handle sessions, and what existing patterns to follow.
Attempt 3 (Specific and Actionable):
Add Google OAuth login. Check src/auth/ for how we currently handle sessions—we use
express-session with Redis. Follow the same session storage pattern for OAuth tokens.
The login button should go in src/components/LoginForm.tsx next to the existing email
login. Write integration tests for the OAuth callback handler in tests/auth/oauth.test.ts
matching the style of other auth tests.
This prompt gives Claude:
- The feature scope (Google OAuth)
- Where to find patterns (src/auth/, existing tests)
- Architectural constraints (express-session, Redis)
- Integration points (LoginForm.tsx)
- Verification requirements (integration tests)
Claude can now implement the feature with confidence because it knows your conventions.
Following Existing Patterns
The best way to ensure consistency is to point Claude at existing code as examples. Every codebase has patterns—component structures, naming conventions, test styles, architectural decisions. Claude excels at recognizing and replicating these patterns.
Pattern Recognition in Action
Imagine you're building a new dashboard widget. Instead of explaining your entire component architecture, you can show Claude an example:
Look at how existing widgets work on the home page. HotDogWidget.php is a good example
of our standard widget structure. Follow that exact pattern to create a new
CalendarWidget that shows upcoming events. It should use the same base Widget class,
follow the same file structure, and register itself in the widget registry the same way.
Claude will:
- Read
HotDogWidget.phpto understand your widget pattern - Check how widgets register themselves
- Look at the base Widget class to understand required methods
- Create
CalendarWidget.phpfollowing the exact same structure - Register it properly in your widget system
You didn't have to explain your widget architecture. You just pointed to an example and said "do it like that."
When pointing to examples, choose the most representative file. If you have one widget with legacy patterns and another with modern patterns, explicitly tell Claude which one to follow: "Use NewWidget.tsx as the template, not OldWidget.jsx—we're migrating away from that pattern."
Test Pattern Consistency
Test consistency is especially important. Every project has testing conventions—framework choices, mocking strategies, assertion styles. Claude can match these perfectly when given examples:
Write unit tests for the new validateEmail function. Look at tests/utils/validators.test.ts
to see how we structure our validator tests. We use Jest with describe/it blocks, and we
always test the happy path, edge cases, and error cases in separate it() blocks.
Claude examines the existing test file and creates new tests that:
- Use the same testing framework (Jest)
- Follow the same structure (describe/it nesting)
- Match the same assertion style
- Cover the same categories (happy path, edge cases, errors)
Your test suite stays consistent without you having to document every testing convention.
Multi-File Feature Development
Real features span multiple files. An API endpoint might require:
- A route definition
- A controller with business logic
- A database model
- TypeScript types
- Input validation schemas
- Unit tests
- Integration tests
- API documentation
Claude handles this complexity naturally. It understands dependencies between files and maintains consistency across the entire feature.
Example: Creating a Blog Post API
Let's walk through a complete multi-file feature. You want to add a REST API for blog posts:
Your Prompt:
Create a REST API for blog posts at /api/posts. Look at /api/users for the pattern—we
use Express routes with controller functions, Prisma for database access, Zod for
validation, and Jest for testing. I need:
- GET /api/posts (list all, with pagination)
- GET /api/posts/:id (single post)
- POST /api/posts (create new, authenticated users only)
- PUT /api/posts/:id (update, author only)
- DELETE /api/posts/:id (delete, author only)
Write full tests covering auth checks and validation.
Claude's Implementation Plan:
I'll create:
1. src/routes/posts.ts - Express route definitions
2. src/controllers/postController.ts - Business logic for CRUD operations
3. src/models/Post.ts - Prisma model and queries
4. src/schemas/postSchema.ts - Zod validation schemas
5. src/types/post.ts - TypeScript interfaces
6. tests/api/posts.test.ts - Integration tests
7. Update src/app.ts to register the new routes
Following the /api/users pattern for consistency.
Files Claude Creates:
src/routes/posts.ts:
import { Router } from 'express';
import { requireAuth } from '../middleware/auth';
import * as postController from '../controllers/postController';
const router = Router();
router.get('/', postController.listPosts);
router.get('/:id', postController.getPost);
router.post('/', requireAuth, postController.createPost);
router.put('/:id', requireAuth, postController.updatePost);
router.delete('/:id', requireAuth, postController.deletePost);
export default router;src/controllers/postController.ts:
import { Request, Response } from 'express';
import { createPostSchema, updatePostSchema } from '../schemas/postSchema';
import * as PostModel from '../models/Post';
export async function listPosts(req: Request, res: Response) {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 20;
const posts = await PostModel.findAll({ page, limit });
res.json(posts);
}
export async function createPost(req: Request, res: Response) {
const result = createPostSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
const post = await PostModel.create({
...result.data,
authorId: req.user!.id
});
res.status(201).json(post);
}
// ... other handlersNotice how Claude:
- Used the same middleware pattern (
requireAuth) - Followed the same validation approach (Zod with
safeParse) - Matched the same error response format
- Applied the same pagination pattern
- Maintained consistent naming conventions
All without you having to document these conventions. Claude learned them from the existing /api/users code.
The Explore → Plan → Implement → Commit Workflow
For complex features, use this four-step workflow to ensure quality:
Explore (Plan Mode)
First, have Claude explore the relevant parts of your codebase to understand existing patterns:
Read src/auth and understand how we handle sessions. Pay attention to how we store
session data, how middleware checks authentication, and how we handle OAuth tokens.
Claude explores in plan mode, reads the necessary files, and builds a mental model of your architecture.
Plan (Plan Mode)
Ask Claude to create an implementation plan showing what will change:
I want to add Google OAuth login. What files need to change? Create a detailed plan
showing where the OAuth button goes, how the callback route works, how tokens get
stored in sessions, and what tests we need.
Claude outputs a structured plan:
Implementation Plan: Google OAuth Login
Files to Create:
1. src/auth/oauth/google.ts - OAuth client configuration and token exchange
2. src/routes/auth/google.ts - OAuth initiation and callback routes
3. tests/auth/oauth/google.test.ts - Integration tests for OAuth flow
Files to Modify:
1. src/components/LoginForm.tsx - Add "Sign in with Google" button
2. src/models/User.ts - Add googleId and googleEmail fields
3. src/middleware/auth.ts - Update session validation to handle OAuth sessions
4. prisma/schema.prisma - Add OAuth fields to User model
Implementation Steps:
1. Create OAuth client with credentials from env vars
2. Add initiation route that redirects to Google
3. Implement callback handler that exchanges code for tokens
4. Store OAuth tokens in session (same pattern as password auth)
5. Update User model to link Google accounts
6. Add UI button with Google branding
7. Write tests for full OAuth flow
This follows our existing session pattern and integrates with current auth middleware.
Review this plan. If something's wrong, adjust now before Claude writes code.
Implement (Normal Mode)
Exit plan mode and let Claude implement:
Implement the Google OAuth flow from your plan. Write all the files, run the tests, and
fix any failures. Make sure the OAuth button appears correctly in the LoginForm.
Claude:
- Creates all the new files
- Modifies existing files as planned
- Writes comprehensive tests
- Runs the test suite
- Fixes any failures
- Verifies the implementation works
Commit
Once everything works, commit with a clear message:
Commit these changes with a descriptive message and open a PR targeting the main branch.
Claude creates a commit that summarizes the feature and opens a pull request with a detailed description of what changed and why.
This workflow ensures you catch architectural issues in the planning phase, before any code is written. It's much easier to adjust a plan than to refactor working code.
Use plan mode liberally for exploration. Claude can read dozens of files, understand complex architectures, and spot patterns you might miss. "Read all the controllers in src/api and tell me the common patterns" is a powerful way to ensure new code fits your existing architecture.
Verification is Key
Features aren't done until they're verified. Always include specific success criteria in your prompts so Claude can check its own work:
Without Verification:
Write a validateEmail function
With Verification:
Write a validateEmail function. Test cases:
- user@example.com returns true
- user returns false
- user@.com returns false
- user@domain returns false
- user@domain.co.uk returns true
- '' (empty string) returns false
Write the tests first, then implement, then run the tests and fix any failures.
This test-first approach forces Claude to verify correctness. If the tests fail, Claude sees the failures and fixes the implementation.
Types of Verification
Different features need different verification strategies:
API Endpoints:
After implementing, use curl to test all the endpoints. Check that:
- GET /api/posts returns 200 with an array
- POST /api/posts without auth returns 401
- POST /api/posts with invalid data returns 400 with error details
- POST /api/posts with valid data returns 201 with the created post
UI Components:
After implementing the CalendarWidget, take a screenshot of it rendered on the dashboard.
It should show three upcoming events with dates, and match the styling of other widgets.
Data Processing:
After implementing the CSV parser, test it with the sample file at fixtures/sample.csv.
The output should be an array of 150 objects, each with name, email, and joinDate fields.
Specific verification criteria ensure Claude doesn't just write code—it writes working code.
Tips for Better Results
1. Scope the Task
Tell Claude exactly where to focus:
Vague:
add error handling
Scoped:
Add error handling to the payment processing functions in src/payments/stripe.ts. Wrap
Stripe API calls in try/catch, log errors to our logger, and return user-friendly error
messages. Don't change the function signatures.
2. Point to Sources of Truth
Your codebase has history and context. Use it:
Look through the git history for src/api/orders.ts and summarize how the order processing
logic evolved. I need to understand why we handle refunds the way we do before adding
partial refund support.
Claude examines commits, pull requests, and code evolution to understand the "why" behind current patterns.
3. Give Rich Context
Use @ to reference files, paste screenshots, pipe data:
Look at @src/components/Dashboard.tsx and @docs/design-system.md. Create a new
StatsCard component matching our design system. Here's a screenshot of how it should
look: [paste screenshot]
Claude uses all available context—code, documentation, and visual references—to implement exactly what you need.
4. Let Claude Fetch What It Needs
Don't copy/paste issues or pull requests. Let Claude fetch them:
Use gh to read issue #247 and implement the requested feature. Check the discussion
comments for any implementation guidance from the team.
Claude fetches the issue, reads the discussion, and implements with full context.
5. Be Explicit About Preferences
Every developer has preferences. Share yours:
I prefer async/await over promises, explicit types over inference, and early returns
over nested ifs. Keep these preferences in mind when implementing the new API client.
Claude adapts to your style and produces code that feels like you wrote it.
Common Pitfalls to Avoid
Pitfall 1: Implementing Before Exploring
Don't jump straight to implementation. Let Claude explore first:
❌ "Create a new payment processor using Stripe"
✅ "Read src/payments/ and understand our current payment processing architecture. Then
create a plan for adding Stripe support that follows our existing patterns."
Pitfall 2: Forgetting to Specify Tests
Without explicit testing requirements, Claude might skip tests:
❌ "Add a CSV export feature"
✅ "Add a CSV export feature. Write Jest tests covering empty datasets, single row,
multiple rows, and special characters in fields."
Pitfall 3: Vague Integration Points
Be specific about how new code integrates with existing systems:
❌ "Add email notifications"
✅ "Add email notifications using our existing email service in src/services/email.ts.
Follow the same template pattern as order confirmations. Queue emails through our
Bull queue, don't send synchronously."
Build a Feature End-to-End
intermediate30 minBuild a Feature from Scratch
Choose one of these features and implement it using the Explore → Plan → Implement → Commit workflow:
Option A: REST API Endpoint
Create a /api/comments endpoint for blog comments. Requirements:
- GET /api/comments?postId=X (list comments for a post)
- POST /api/comments (create comment, authenticated users only)
- DELETE /api/comments/:id (delete comment, author or admin only)
- Follow your existing API patterns for validation, error handling, and testing
Option B: React Component
Create a reusable <DataTable> component that:
- Accepts data and column definitions as props
- Supports sorting by clicking column headers
- Shows a loading state while data fetches
- Includes pagination controls
- Follows your existing component patterns and styling
Option C: CLI Script Create a data migration script that:
- Reads user data from an old JSON format
- Transforms it to your current database schema
- Validates each record before inserting
- Logs progress and any validation failures
- Includes a dry-run mode for testing
Steps:
- Explore: Have Claude read relevant existing code to understand patterns
- Plan: Ask Claude to create a detailed implementation plan
- Implement: Let Claude write the code, tests, and run verification
- Commit: Create a commit with a descriptive message
Success Criteria:
- All files follow existing project patterns
- Tests pass and cover edge cases
- The feature works as specified
- Code is committed with a clear message
Key Takeaway
Writing features from scratch with Claude Code is a collaborative process. You provide clear requirements, point to existing patterns, and specify verification criteria. Claude explores your codebase, creates a plan, implements across multiple files, and verifies the result. The quality of your prompts—how well you decompose requirements, how clearly you specify integration points, and how explicitly you define success criteria—directly determines the quality of the implementation. Master these techniques, and you can build complex, multi-file features with confidence.
Next Steps
In the next lesson, you'll learn how to edit existing code safely. We'll cover the three permission modes (Normal, acceptEdits, and Plan), how Claude's Edit tool performs exact string replacements, and how to recover from unwanted changes using checkpoints and rewind.