Writing Effective Copilot Instructions
Most copilot-instructions.md files are too long, too vague, or too generic. The ones that work are short, specific, and testable.
The 30-second version: A good custom instructions file is under 500 words, tells Copilot about your project’s specific conventions (not general advice), and can be verified. A bad instruction file is a wall of text that says “write clean code” and “use best practices.” Copilot already knows that. Tell it what your codebase does differently.
⚠️ This page assumes you know what instruction files are. If you haven’t read that page, start there. This page is about how to write good custom instructions, not what instruction files are.
GitHub’s official tutorial covers how to use custom instructions. This page builds on that foundation with practical patterns for team adoption, testing your instructions, and avoiding common anti-patterns.
The difference between instructions that help vs instructions that are ignored
Copilot reads your instruction file as part of its prompt context. But context windows are finite. If your instruction file is 2000 words, Copilot will skim it. If it’s generic advice (“write clean code”), Copilot already has that in its training data and will ignore it.
Instructions that help:
- Tell Copilot something it doesn’t already know
- Are specific to your codebase
- Can be verified by a test or linter
Instructions that are ignored:
- Repeat general advice (“follow best practices”)
- Are too vague to act on (“make sure the code is maintainable”)
- Contradict each other (“always use TypeScript strict mode” + “use any when types are complex”)
Personal custom instructions vs repo custom instructions
There are two places you can put custom instructions:
- Personal custom instructions (applies to all your projects)
- Repo custom instructions (applies to one specific repo)
| File | Scope | What to put there |
|---|---|---|
| Personal settings in your IDE | Global — all your projects | Your personal coding style, preferred frameworks, languages you use |
.github/copilot-instructions.md | This repo only | Tech stack, naming conventions, project structure, testing rules |
Use personal custom instructions for:
- “I prefer functional programming over OOP”
- “Always use Python type hints”
- “Prefer React functional components over class components”
Use repo custom instructions for:
- “API routes are in
/src/routes/api/” - “All database queries use Prisma — never raw SQL”
- “Test files must be colocated with source files (e.g.,
Button.tsx+Button.test.tsx)”
Team adoption strategies
Custom instructions become most powerful when adopted across a team. Here’s how to get buy-in:
Start with one high-impact rule
Don’t write 50 rules on day one. Start with one thing that causes the most PR review friction (e.g., “Use Tailwind classes, not inline styles”). Get the team to agree on it. Add it to .github/copilot-instructions.md. Monitor whether Copilot follows it.
Make it a living document
Custom instructions go stale. Schedule quarterly reviews. When a new convention emerges (e.g., migrating from Jest to Vitest), update the file in the same PR that updates the code.
Test before you commit
Before merging custom instructions changes, test them. Write a prompt that should trigger the new rule and verify Copilot follows it. If it doesn’t, refine the instruction.
Version it like code
Treat .github/copilot-instructions.md as code. Require PR reviews. Discuss changes in team meetings. This file shapes what Copilot generates — it’s infrastructure, not documentation.
What Copilot actually reads carefully vs what it skims
Copilot’s context window is limited. It prioritizes:
- The current file you’re editing (highest priority)
- Files you have open in your IDE
- Instruction files (this is where your instructions fit)
- Other files in the workspace (if relevant)
Within the instruction file, Copilot reads:
- The first 200–300 words most carefully — put your most important rules at the top
- Bulleted lists — easier to scan than paragraphs
- Code examples — concrete and unambiguous
Copilot skims or ignores:
- Long paragraphs — hard to parse quickly
- Redundant advice — “always write tests” + “testing is important” is one rule, not two
- Negatives without alternatives — “don’t use callbacks” is less useful than “use async/await instead of callbacks”
Anti-patterns: what NOT to do
Anti-pattern 1: Walls of text
Bad example:
This project uses TypeScript and we have very strict rules about how TypeScript
should be used. Always use strict mode. Never use any. If you don't know the type,
use unknown instead of any. Always define interfaces for objects. Prefer type over
interface unless you need to extend it. Use readonly for properties that don't change.
Always use const instead of let unless you need reassignment...
Why it fails: This is 100 words and covers 8 different rules. Copilot will skim it and remember maybe 2 of them.
Good version:
## TypeScript Rules
- Use `strict: true` in tsconfig.json
- Never use `any` — use `unknown` if type is unclear
- Prefer `type` over `interface` (unless extending)
- All objects must have explicit types
Why this works: 4 rules, bulleted, scannable. Copilot can read this in one glance.
Anti-pattern 2: Listing everything
Bad example:
We use React, TypeScript, Tailwind, Prisma, Next.js, ESLint, Prettier, Jest,
React Testing Library, Storybook, Docker, GitHub Actions, and AWS.
Why it fails: This is a dependency list, not instructions. Copilot can see your package.json — it already knows your stack.
Good version:
## Stack Notes
- Next.js: All API routes are in `pages/api/` — do NOT put them in `src/routes/`
- Tailwind: Use Tailwind classes, not inline styles or CSS modules
- Prisma: All database queries use Prisma Client — never raw SQL
Why this works: These are conventions specific to this project. Copilot can’t infer these from package.json.
Anti-pattern 3: Using negatives without alternatives
Bad example:
- Don't use callbacks
- Don't use class components
- Don't use var
Why it fails: These tell Copilot what not to do, but not what to do instead.
Good version:
- Use async/await instead of callbacks
- Use functional components instead of class components
- Use const or let instead of var
Why this works: Copilot now knows the alternative. This is actionable.
Good patterns: specific, actionable, short, verified
Pattern 1: Start with tech stack differences
Don’t list your entire stack. List the parts that are non-standard or likely to trip up Copilot.
Example:
## Tech Stack Notes
- This project uses Deno, not Node.js — imports must use full file extensions (`.ts`)
- We use htmx, not React — server-rendered HTML with htmx attributes for interactivity
- Database: SQLite in dev, PostgreSQL in production — use only standard SQL features
Pattern 2: Codify naming conventions
If your team has naming conventions, write them down. Copilot will follow them.
Example:
## Naming Conventions
- React components: PascalCase (`UserProfile.tsx`)
- Utility functions: camelCase (`formatDate.ts`)
- Test files: colocated, `.test.ts` suffix (`formatDate.test.ts`)
- API routes: kebab-case (`/api/user-preferences`)
Pattern 3: Specify testing requirements
If you have rules about when to write tests, make them explicit.
Example:
## Testing Rules
- All exported functions must have unit tests
- API routes must have integration tests
- Do NOT test internal helper functions unless they have complex logic
- Run `npm test` before committing — all tests must pass
Pattern 4: Use code examples for clarity
If a rule is hard to explain in words, show a code example.
Example:
## Error Handling Pattern
Always wrap errors in our custom error classes:
```typescript
// Good
throw new NotFoundError('User not found', { userId });
// Bad
throw new Error('User not found');
```
All API routes must catch errors and return standardized JSON:
```typescript
try {
// route logic
} catch (error) {
return res.status(error.statusCode || 500).json({
error: error.message,
code: error.code
});
}
```
This is unambiguous. Copilot can copy the pattern exactly.
How to test your instructions
After writing your instruction file, test it:
Write a prompt that should trigger the instruction
Example: “Add a new API route for user preferences”Run it in agent mode or chat
See if Copilot follows your instructionsCheck the output
Did it follow your naming convention? Did it write tests? Did it use the right error handling pattern?Iterate
If Copilot ignored a rule, either:- Move it higher in the file (top rules get read first)
- Make it more specific (vague rules get ignored)
- Add a code example (concrete examples are easier to follow)
Versioning and maintenance: custom instructions go stale
⚠️ Custom instructions go stale. Your codebase changes. New conventions emerge. Old conventions get deprecated. If your custom instructions say “use callbacks” but your codebase now uses async/await, Copilot will be confused.
Maintenance tips:
- Review your custom instructions every 3–6 months — does it still match your codebase?
- Update it when conventions change — if you migrate from Jest to Vitest, update the testing section
- Version it in git — custom instructions are code. They should be reviewed in PRs.
- Keep it short — the longer it is, the harder it is to maintain. Aim for under 500 words.
- Track what’s not working — if Copilot consistently ignores a rule, either the rule is too vague or it contradicts another rule. Debug it like you’d debug code.
Example: a complete, effective custom instructions file
Here’s a real example of a well-structured .github/copilot-instructions.md file for a team:
# Copilot Instructions for acme-api
## Tech Stack
- **Language:** TypeScript 5.0 (strict mode enabled)
- **Framework:** Next.js 14 (App Router, not Pages Router)
- **Database:** Prisma with PostgreSQL
- **Testing:** Jest + React Testing Library
## Code Conventions
- **React components:** Functional components only, no class components
- **Naming:** PascalCase for components, camelCase for utilities, kebab-case for API routes
- **File structure:** Colocate tests with source files (`Button.tsx` + `Button.test.tsx`)
- **Imports:** Use `@/` alias for absolute imports (e.g., `import { db } from '@/lib/db'`)
## Database Rules
- All queries use Prisma Client — never raw SQL
- Always use transactions for multi-step writes
- Include `include` clauses explicitly — avoid lazy loading
## Testing Requirements
- All API routes must have integration tests
- All exported functions must have unit tests
- Run `npm test` before committing
## Error Handling
Use custom error classes from `@/lib/errors`:
~~~typescript
throw new NotFoundError('User not found', { userId });
~~~
API routes must return standardized error JSON:
~~~typescript
catch (error) {
return NextResponse.json(
{ error: error.message, code: error.code },
{ status: error.statusCode || 500 }
);
}
~~~
## What NOT to Do
- Don't use `any` — use `unknown` if type is unclear
- Don't bypass TypeScript strict mode with `@ts-ignore`
- Don't commit directly to `main` — always use a feature branch + PR
Why this works:
- Short: 250 words
- Specific: Every rule is actionable
- Verifiable: You can check if code follows these rules
- Concrete: Code examples are unambiguous
- Maintained: Versioned in git, reviewed in PRs
Dive deeper
- GitHub’s Custom Instructions Tutorial — official tutorial on setting up custom instructions
- Instruction Files — what instruction files are and where to put them
- Prompting — how to write effective prompts that work with your custom instructions
Related Concepts
- Copilot Agent Mode — where your instructions take effect in the IDE
- Copilot Coding Agent — the cloud agent that also reads
AGENTS.md
📍 Layer 4a — Instructions (effective patterns) · Back to the AICA Onion
What’s next?
- When NOT to Use AI — learn when to skip Copilot and code yourself