Prompting Guide
The five principles of effective Claude Code prompting, what separates good prompts from bad ones, and 10 real before/after examples from a school management app.
Claude Code is only as good as the instructions you give it. Two developers working on identical projects — one prompting well, one prompting carelessly — will get dramatically different results. This is a skill that improves with deliberate practice.
The Core Insight
With ChatGPT, a vague prompt gets a generic answer. With Claude Code, a vague prompt gets code that technically runs but doesn't fit your project — wrong naming conventions, different patterns from the rest of your codebase, missing error handling, or a completely different approach from what you had in mind.
Claude Code knows your project. Your prompts should take advantage of that. A prompt that works in Claude.ai ("write me a login form") is not a prompt that gets you production-quality code from Claude Code. You need to be more specific — not because Claude is worse, but because you're asking for something harder.
The Five Principles
Principle 1: Give Context First
Don't start with the task. Start with where you are and what already exists.
The context Claude Code has is the session history plus what it can read from your files. If you've been working on something for 30 minutes, Claude has full context. If you're starting a new session, or jumping to a new part of the project, you need to orient it.
Think of it like briefing a colleague before asking them to help: "We're in the fee payment module, the parent has already logged in, the form is at src/pages/FeePaymentPage.tsx, and it submits to the Supabase fee_records table. Now — help me add validation."
Principle 2: Be Specific About the Result
Describe what done looks like, not just what you want in the abstract.
"Fix the form" could mean anything. "The phone number field currently accepts letters — it should only accept numbers, exactly 10 digits, with a red error message appearing below the field when it's wrong" is specific. Claude knows exactly what success looks like.
Principle 3: State Constraints Explicitly
Tell Claude what you don't want, not just what you do want.
Constraints are as important as requirements. If you're working inside an existing pattern, say so. If you can't add new dependencies, say so. If the database schema is fixed, say so. Claude will try to give you the best solution it can think of — and its best solution may involve changing things you didn't want changed.
Principle 4: Point to Existing Examples
Claude Code can read your files. Use this.
"Follow the same pattern as src/components/UserTable.tsx" is one of the most powerful things you can say. Claude will read that file, understand the pattern, and replicate it. This is how you get consistent code across your project — not by hoping Claude makes the same choices twice.
Principle 5: Iterate, Don't Restart
Build on the previous response. You don't have to accept it as final.
If Claude gives you 80% of what you need, say what the remaining 20% is: "Good. Now add loading state while the form submits" or "The validation is working but the error messages should appear below each field, not at the top of the form." You're in a conversation. Use it.
Bad Prompt → Better Prompt (10 Real Examples)
These examples are from EduTrack — the school management app you'll build in Project 3.
| # | Bad Prompt | Better Prompt | Why It's Better |
|---|---|---|---|
| 1 | "Write a fee payment form" | "In src/pages/FeePaymentPage.tsx, create a form with fields: fee type (dropdown from fee_structures), amount (read-only, populated from the selected fee), and payment method (radio buttons). Use React Hook Form + Zod for validation. Follow the same pattern as the existing EnrollmentForm.tsx." | Specifies location, fields, data source, tools, and a reference pattern |
| 2 | "Fix the bug" | "The form at FeePaymentPage.tsx submits successfully but the toast notification never appears. Check the onSuccess handler in the mutation — I think the toast import is wrong." | Describes the symptom, the location, and the theory |
| 3 | "Add auth" | "Add a protected route wrapper so that /fee-payment redirects to /login if the parent isn't authenticated. The auth state is managed in src/store/authStore.ts. Don't change the Supabase client setup." | Specifies what already exists and what not to touch |
| 4 | "Make it look better" | "The FeeDashboard header is using text-2xl but other page headers on this site use text-3xl font-bold. Make the FeeDashboard header consistent with the rest. Don't change anything else." | Describes the specific inconsistency, not a vague aesthetic preference |
| 5 | "Add error handling" | "If the fee payment fails because the fee is already marked paid (the Supabase error code is 23505), show a specific error: 'This fee has already been paid.' For all other errors, show 'Something went wrong. Please try again.'" | Specifies the exact error case, the error code, and the exact messages |
| 6 | "Connect to the database" | "Read src/lib/supabase.ts for the Supabase client setup. Then create a useRecordFeePayment mutation hook in src/hooks/useRecordFeePayment.ts that updates the fee_records table. The table has columns: student_id, fee_structure_id, amount, status, paid_at. Return type should match the FeeRecord interface in src/types/feeRecord.ts." | Complete: tells Claude where the client is, where to put the hook, what the table looks like, and where the types live |
| 7 | "Write tests" | "Write Vitest unit tests for the useRecordFeePayment hook in src/hooks/useRecordFeePayment.ts. Test: successful update, handling a 23505 duplicate error, and handling a network failure. Mock the Supabase client. Follow the test pattern in src/hooks/__tests__/useAuth.test.ts." | Specifies the tool, what to test, and the reference pattern |
| 8 | "Make it faster" | "The StudentDashboard takes 3 seconds to load because it's fetching attendance, fee records, and results sequentially. Read src/hooks/useStudentDashboard.ts and refactor so all three fetches run in parallel using Promise.all." | Identifies the file, the exact problem, and the solution approach |
| 9 | "Update the types" | "I added a paid_at column (timestamptz, nullable) to the fee_records table in Supabase. Update the FeeRecord interface in src/types/feeRecord.ts and check if any existing queries or components need to handle this new field." | Gives the exact schema change and asks Claude to check downstream impacts |
| 10 | "Deploy it" | "Run npm run build and confirm it succeeds without errors. If there are TypeScript errors, list them — don't fix them yet. If the build passes, tell me what to do next to deploy to Vercel." | Specific action, specific scope (don't auto-fix), specific next step |
Common Prompting Mistakes
Asking for too much in one prompt. "Build the entire fee management system with auth, database, real-time updates, Razorpay payment, and email confirmation" will produce a large amount of code that doesn't integrate well with your existing project and is hard to review. Break it down. One feature at a time.
Not pointing to existing files. If you have a pattern elsewhere in the project that Claude should follow, say so. Don't let Claude invent a new pattern when an existing one is right there.
Accepting the first response without testing it. Claude is confident in its responses. Confidence does not equal correctness. Always run the code. Always check the actual behavior. Always review what was written before moving to the next task.
Asking Claude to "just do it." When you say "just implement it however you think is best," you're handing over product decisions to an AI. Claude will make choices — and they may be technically correct but wrong for your project. Keep the product decisions with yourself.
Starting over instead of iterating. If Claude's first attempt has one thing wrong, don't /clear and start from scratch. Fix the specific thing: "That's mostly right but the error message is appearing at the wrong time — it should only show after the user tries to submit, not as they're typing."
A Template for Complex Tasks
When you're about to ask Claude to do something substantial — a new feature, a refactor, a complex bug fix — use this structure:
You don't have to use these exact labels. But mentally covering all five before you type your prompt will consistently produce better results than typing whatever comes to mind.
The single highest-impact habit: always start a complex prompt by pointing to an existing file Claude should read first. "Read X, then do Y" gives Claude real context rather than having it guess your patterns.
Interactive: How API Calls Work
Every time Claude Code talks to Supabase, it makes an API call. Click any request to watch it travel from your browser to the database and back.