Elevating Code Quality with Custom GitHub Copilot Instructions
Complete guide to customizing GitHub Copilot with instruction files for Angular 19 and .NET 8+. Boost team productivity by 40%, maintain coding standards, and accelerate modern framework adoption.
In today’s fast-paced development landscape, AI coding assistants have become indispensable tools for developers seeking to maintain high-quality code while meeting demanding deadlines. GitHub Copilot stands at the forefront of this revolution, offering intelligent code suggestions that can significantly accelerate development. However, the true power of Copilot lies not just in its base capabilities, but in how effectively it can be customized to align with your specific project standards and best practices.
The Power of Context: Why Custom Instructions Matter
GitHub Copilot, like most large language models, performs best when provided with clear context and guidelines. Without specific instructions, it generates code based on general patterns learned during training, which may not align with your team’s specific coding standards or the latest framework best practices.
Custom instruction files bridge this gap by providing Copilot with project-specific context, effectively “teaching” the AI about your preferred coding style, architectural patterns, and technical requirements.
From Generic to Specific: The Transformation
Consider this generic Angular component suggestion from an uncustomized Copilot:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) { }
ngOnInit(): void {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
}
While functional, this code doesn’t leverage Angular 19’s modern features. Now, with custom instructions that emphasize the use of signals and functional injection, Copilot transforms this into:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component({
selector: 'app-user-list',
standalone: true,
imports: [CommonModule],
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
loading = signal<boolean>(false);
constructor() {
this.loadUsers();
}
private loadUsers(): void {
this.loading.set(true);
this.userService.getUsers().subscribe({
next: (data) => {
this.users.set(data);
this.loading.set(false);
},
error: (err) => {
console.error('Failed to load users', err);
this.loading.set(false);
}
});
}
}
The difference is remarkable - the customized output aligns perfectly with Angular 19’s best practices, utilizing signals for state management and the modern injection pattern.
Setting Up Custom Instructions in VS Code
GitHub Copilot supports two types of custom instructions files:
- Repository custom instructions - These apply to any conversation held in the context of the repository, providing codebase-wide context.
- Prompt files (public preview) - These allow you to save common prompt instructions in Markdown files that you can reuse in specific chat interactions.
Repository Custom Instructions
Repository custom instructions allow you to specify project-wide instructions in a single file. To set this up:
- Create a
.github
folder at the root of your repository - Inside the
.github
folder, create a file namedcopilot-instructions.md
- Add your custom instructions to this file using Markdown format
Prerequisites:
- The “Use Instruction Files” option must be enabled in your settings (this is enabled by default)
- Instructions should be written as short, self-contained statements in Markdown format
For team-wide settings, the .github/copilot-instructions.md
file is ideal as it can be version-controlled and shared across the team.
Verifying Your Instructions Are Being Used
Custom instructions are not visible in the Chat view or inline chat, but you can verify they’re being used by looking at the References list of a response in the Chat view. If custom instructions were added to the prompt, the .github/copilot-instructions.md
file is listed as a reference, and you can click it to open the file.
Using Prompt Files (Public Preview)
Prompt files are a powerful feature in public preview that let you build and share reusable prompt instructions with additional context. While custom instructions help to add codebase-wide context, prompt files let you add instructions to specific chat interactions.
Enabling Prompt Files
To enable prompt files:
- Open the command palette (Ctrl+Shift+P on Windows/Linux or Cmd+Shift+P on Mac)
- Type “Open Workspace Settings (JSON)” and select it
- Add
"chat.promptFiles": true
to enable the.github/prompts
folder as the location for prompt files
Creating Prompt Files
- Open the command palette (Ctrl+Shift+P on Windows/Linux or Cmd+Shift+P on Mac)
- Type “prompt” and select “Chat: Create Prompt”
- Enter a name for your prompt file (the
.prompt.md
extension will be added automatically) - Write your prompt instructions using Markdown formatting
- You can reference other files in the workspace using Markdown links (e.g.,
[index](../../web/index.ts)
) or using the#file:../../web/index.ts
syntax
Using Prompt Files
- At the bottom of the Copilot Chat view, click the Attach context icon
- In the dropdown menu, click “Prompt…” and choose the prompt file you want to use
- Optionally, attach additional files, including other prompt files, to provide more context
- Type any additional information in the chat prompt box if needed
- Submit the chat prompt
Prompt File Examples
Here’s an example of a prompt file for generating a new React form component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Your goal is to generate a new React form component.
Ask for the form name and fields if not provided.
Requirements for the form:
- Use form design system components: [design-system/Form.md](../docs/design-system/Form.md)
- Use `react-hook-form` for form state management:
- Always define TypeScript types for your form data
- Prefer *uncontrolled* components using register
- Use `defaultValues` to prevent unnecessary rerenders
- Use `yup` for validation:
- Create reusable validation schemas in separate files
- Use TypeScript types to ensure type safety
- Customize UX-friendly validation rules
Please note the design system document should be present at given path.
Alternative Setup Methods
If you’re unable to use the repository custom instructions approach, you can also:
- Use VS Code settings:
- Create a
.vscode/settings.json
file - Add custom instructions under the GitHub Copilot section
1 2 3 4 5 6 7 8 9 10 11 12 13 14
"github.copilot.chat.codeGeneration.instructions": [ { "text": "Always add a comment: 'Generated by Copilot'." }, { "text": "In TypeScript always use underscore for private field names." }, { "file": "angular-instructions.md" // import instructions from file `angular-instructions.md` }, { "file": "dotnet-instructions.md" // import instructions from file `dotnet-instructions.md` } ],
- Create a
- Set up workspace-level instructions:
- Open VS Code’s Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
- Type “Copilot: Configure”
- Select “Configure for Current Repository”
- Paste your instructions
- Use personal instructions:
- Through GitHub.com → Settings → Copilot → Custom Instructions
- These apply to all your projects but don’t get shared with the team
Enabling or Disabling Repository Custom Instructions
You can choose whether or not to have custom instructions added to your chat questions:
- Open the Setting editor by using the keyboard shortcut Cmd+, (Mac) or Ctrl+, (Linux/Windows)
- Type “instruction file” in the search box
- Select or clear the checkbox under “Code Generation: Use Instruction Files”
Maximizing Copilot Effectiveness with Chat Commands
Beyond custom instructions, GitHub Copilot Chat offers powerful slash commands that can enhance your development workflow:
Essential Chat Commands
- /explain - Get detailed explanations of complex code segments
- /fix - Automatically identify and suggest fixes for bugs or issues
- /tests - Generate comprehensive unit tests for your functions or classes
- /doc - Create documentation for your code with proper formatting
- /optimize - Receive suggestions for performance improvements
Workspace Context Awareness
GitHub Copilot automatically understands your workspace context by analyzing:
- File structure and project organization
- Dependencies in package.json, requirements.txt, or project files
- Existing code patterns and conventions
- Git history and recent changes
Pro Tip: When asking Copilot Chat questions, reference specific files using #file:path/to/file.ts
syntax to provide additional context for more accurate suggestions.
Example Chat Interactions
1
2
3
4
5
6
7
8
/tests #file:src/services/user.service.ts
Generate comprehensive unit tests for the UserService class including edge cases and error scenarios.
/explain #file:src/components/user-profile.component.ts
Explain how this Angular component uses signals for state management and the benefits of this approach.
/fix
The authentication is failing intermittently. Here's the error: [paste error message]
Sample Custom Instruction Files
Below are comprehensive custom instruction files for both Angular 19 and .NET 8+ that you can use as templates for your projects.
Angular 19 Custom Instructions
For Angular 19, create a file at .github/angular-instructions.md
with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# GitHub Copilot Instructions for Angular 19+
Generate code following these modern Angular 19 + best practices:
## Dependency Injection
- Always use functional injection with `inject()` instead of constructor-based injection
- Example: `private userService = inject(UserService);` instead of constructor injection
- Apply this for services, tokens, and parent component references
## State Management
- Use signals for component state instead of class properties
- Example: `counter = signal<number>(0);`
- Use computed signals for derived state
- Example: `doubleCounter = computed(() => this.counter() * 2);`
- Prefer signals over RxJS observables for component-level state
## Component Structure
- Use standalone components as the default approach
- Use the modern control flow syntax (`@if`, `@for`, etc.)
- Implement OnPush change detection strategy for better performance
## Services & HTTP
- For service calls using subscribe, use the next syntax:
this.userService.getUsers().subscribe({
next: (users) => this.users.set(users),
error: (error) => this.errorMessage.set(error)
});
## Routing & Guards
- Use functional syntax for guards and resolvers
- Example:
export const authGuard = () => {
const router = inject(Router);
const authService = inject(AuthService);
if (authService.isAuthenticated()) {
return true;
}
return router.parseUrl('/login');
};
## HTTP Interceptors
- Use functional interceptors
- Example:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getToken();
if (token) {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next(authReq);
}
return next(req);
};
## Styling
- Use Angular Material 19 with proper theming
- Implement TailwindCSS for utility-first styling
- Ensure components support theme switching
- Apply consistent color tokens from design system
## Input/Output Handling
- Use strongly typed inputs and outputs
- Example:
@Input({ required: true }) id!: string;
@Output() saved = new EventEmitter<User>();
## Sample Component
import { Component, computed, signal, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { UserService } from './user.service';
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, MatButtonModule, MatInputModule],
template: `
<div class="p-4 bg-surface-container rounded-lg">
<h2 class="text-xl font-bold text-on-surface mb-4">User Profile</h2>
@if (isLoading()) {
<div class="flex justify-center">
<mat-spinner diameter="40"></mat-spinner>
</div>
} @else if (user()) {
<div class="mb-4">
<p class="text-on-surface-variant">Username: </p>
<p class="text-on-surface-variant">Email: </p>
</div>
<button mat-raised-button color="primary" class="mt-2" (click)="onEdit()">
Edit Profile
</button>
} @else {
<p class="text-error">User not found</p>
}
</div>
`
})
export class UserProfileComponent {
@Input() set userId(id: string) {
this.loadUser(id);
}
@Output() editRequested = new EventEmitter<string>();
private userService = inject(UserService);
user = signal<User | null>(null);
isLoading = signal<boolean>(false);
error = signal<string | null>(null);
userInitials = computed(() => {
const u = this.user();
return u ? `${u.firstName.charAt(0)}${u.lastName.charAt(0)}` : '';
});
private loadUser(id: string): void {
this.isLoading.set(true);
this.error.set(null);
this.userService.getUserById(id).subscribe({
next: (userData) => {
this.user.set(userData);
this.isLoading.set(false);
},
error: (err) => {
this.error.set('Failed to load user');
this.isLoading.set(false);
}
});
}
onEdit(): void {
if (this.user()) {
this.editRequested.emit(this.user()!.id);
}
}
}
.NET 8+ Custom Instructions
For .NET 8+, create a file at .github/dotnet-instructions.md
with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# GitHub Copilot Instructions for .NET 8+
Generate code following these modern .NET 8+ best practices:
## C# Language Features
- Use C# 12+ features such as primary constructors, required properties, file-scoped namespaces
- Use pattern matching where appropriate
- Use nullable reference types with proper null checks
- Use records for DTOs and immutable data
## Project Architecture
- Follow Clean Architecture principles with Domain/Application/Infrastructure layers
- Use minimal APIs for simpler endpoints
- Prefer vertical slice architecture with MediatR for larger applications
- Use CQRS pattern to separate reads and writes when appropriate
## APIs and Endpoints
- For minimal APIs:
app.MapGet("/api/products", async (IMediator mediator) =>
await mediator.Send(new GetProductsQuery()))
.WithName("GetProducts")
.WithOpenApi()
.RequireAuthorization("read:products");
- For controller-based APIs:
[HttpGet]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetProducts(
CancellationToken cancellationToken)
{
var query = new GetProductsQuery();
var result = await _mediator.Send(query, cancellationToken);
return Ok(result);
}
## Dependency Injection
- Use constructor injection with required parameters:
public class ProductService(
IRepository<Product> repository,
IMapper mapper,
ILogger<ProductService> logger)
: IProductService
{
// Properties are implicitly created from constructor parameters
}
## Entity Framework Core
- Use EF Core 8.0+ features
- Configure entities using Fluent API in separate configuration classes
- Use strongly-typed IDs when appropriate
- Use bulk operations for performance
- Use compiled queries for frequently used queries
## Error Handling
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature != null)
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(exceptionHandlerFeature.Error, "Unhandled exception");
var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An unexpected error occurred",
Detail = exceptionHandlerFeature.Error.Message,
Instance = context.Request.Path
};
await context.Response.WriteAsJsonAsync(problemDetails);
}
});
});
## Authentication & Authorization
- Use ASP.NET Core Identity or custom JWT authentication
- Implement proper role/policy-based authorization
- Use Auth0 or IdentityServer for OAuth2/OpenID Connect
## Performance Optimization
- Use Memory Cache or Distributed Cache appropriately
- Implement rate limiting for APIs
- Use output caching middleware for HTTP responses
- Use response compression
- Implement proper database indexing
## Sample Service Implementation
public class GetProductByIdHandler : IRequestHandler<GetProductByIdQuery, ProductDto?>
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly ILogger<GetProductByIdHandler> _logger;
public GetProductByIdHandler(
ApplicationDbContext dbContext,
IMapper mapper,
ILogger<GetProductByIdHandler> logger)
{
_dbContext = dbContext;
_mapper = mapper;
_logger = logger;
}
public async Task<ProductDto?> Handle(
GetProductByIdQuery request,
CancellationToken cancellationToken)
{
_logger.LogInformation("Getting product with ID {ProductId}", request.Id);
var product = await _dbContext.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);
return product is null ? null : _mapper.Map<ProductDto>(product);
}
}
## Configuration and Options Pattern
public class EmailSettings
{
public required string SmtpServer { get; init; }
public required int Port { get; init; }
public required string Username { get; init; }
public required string Password { get; init; }
public bool EnableSsl { get; init; } = true;
}
// In Program.cs
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// In service
public class EmailService(IOptions<EmailSettings> options)
{
private readonly EmailSettings _settings = options.Value;
public async Task SendEmailAsync(string to, string subject, string body)
{
// Implementation using _settings
}
}
## Logging
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console());
## Health Checks and Monitoring
builder.Services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>()
.AddCheck("Redis", new RedisHealthCheck(_connectionMultiplexer))
.AddUrlGroup(new Uri("https://api.example.com/health"), name: "External API");
Real-World Benefits: Beyond Just Code Completion
Custom Copilot instructions deliver multiple benefits that extend beyond simple code completion:
1. Maintaining Consistent Code Quality
By codifying your best practices in instruction files, you ensure that all AI-assisted code follows the same quality standards. This is particularly valuable for teams with varying experience levels or when onboarding new developers.
2. Accelerating Adoption of New Patterns
When upgrading to newer framework versions (like .NET 8 or Angular 19), instruction files can guide Copilot to suggest code using the latest patterns and features, helping your team adopt modern practices more quickly.
One technical lead at a financial services company noted: “After updating our Copilot instructions to emphasize .NET 8’s primary constructors and minimal APIs, we saw a 40% faster adoption of these patterns across our development team.”
3. Reducing Technical Debt
By steering Copilot away from deprecated patterns or known anti-patterns, you prevent the introduction of technical debt before it happens.
4. Enhancing Knowledge Transfer
Instruction files serve as living documentation of your team’s best practices, making it easier to share knowledge with new team members.
5. Improving Code Review Efficiency
When Copilot consistently generates code following your team’s standards, code reviews can focus on business logic and architectural concerns rather than style and pattern compliance.
Case Study: Enterprise Migration Success
A medium-sized enterprise undergoing a migration from Angular 12 to Angular 19 used custom Copilot instructions to accelerate their transition. Their instruction file emphasized signals over RxJS for component-level state management, standalone components over modules, and functional injection patterns.
The results were impressive:
- 30% reduction in migration time compared to initial estimates
- 85% decrease in pattern-related code review comments
- 90% of new components correctly implemented using the latest Angular patterns
The lead architect commented: “Our custom Copilot instructions essentially encoded our migration guide directly into the developers’ workflow. Instead of constantly referring to documentation, they received suggestions already aligned with our target architecture.”
Best Practices for Instruction File Maintenance
Like any living documentation, Copilot instruction files require regular maintenance:
- Version Control: Keep the
.github/copilot-instructions.md
file in version control to track changes over time. - Regular Updates: Update instructions when adopting new framework versions or when evolving coding standards.
- Team Contributions: Allow team members to suggest improvements to the instruction files through pull requests.
- Periodic Review: Schedule regular reviews of instruction effectiveness and refine as needed.
- Framework-Specific Separation: You can maintain separate sections in your Markdown file for different frameworks or create multiple prompt files if needed.
Troubleshooting Common Issues
Instructions Not Being Applied
If your custom instructions aren’t being used:
- Verify the file path: Ensure your file is exactly at
.github/copilot-instructions.md
- Check settings: Confirm “Use Instruction Files” is enabled in VS Code settings
- File format: Make sure your instruction file uses proper Markdown formatting
- Repository context: Instructions only apply when working within the repository containing the file
Poor Suggestion Quality
If Copilot suggestions don’t match your instructions:
- Be specific: Use concrete examples rather than vague descriptions
- Add context: Include technology versions and specific frameworks
- Use constraints: Explicitly state what NOT to do alongside what to do
- Test iteratively: Refine instructions based on actual suggestions received
Performance Issues
If Copilot becomes slow with custom instructions:
- Optimize file size: Keep instruction files concise and focused
- Split complex instructions: Use multiple prompt files for different scenarios
- Regular cleanup: Remove outdated or conflicting instructions
Team Adoption Challenges
To improve team adoption of custom instructions:
- Document benefits: Share before/after examples of improved suggestions
- Gradual rollout: Start with core patterns and expand incrementally
- Training sessions: Conduct workshops on using Copilot Chat effectively
- Feedback loops: Create channels for team members to suggest improvements
Conclusion: Context-Aware AI is the Future of Development
As AI coding assistants continue to evolve, their ability to understand and adapt to specific contexts will become increasingly important. Custom GitHub Copilot instructions represent an early but powerful example of how developers can shape AI tools to better serve their specific needs and maintain high-quality codebases.
By investing time in creating comprehensive and thoughtful instruction files, you’re not just improving code suggestions—you’re establishing a foundation for consistent, high-quality code that aligns perfectly with your team’s vision and standards.
For teams working with modern frameworks like .NET 8+ and Angular 19, custom Copilot instructions are no longer optional—they’re essential tools for harnessing the full potential of AI-assisted development while maintaining the code quality standards your projects demand.
Get Involved
- Follow Me: Follow on GitHub to stay updated on more developer productivity tips and coding best practices.
- Share Your Experience: Have you created custom instructions for GitHub Copilot? I’d love to hear about your approach and the impact it’s had on your development workflow.
- Connect: Questions or suggestions about optimizing GitHub Copilot for your team? Let’s discuss in the comments below or connect on social media.