Skip to main content
  1. Blog/

Elevating Code Quality with Custom GitHub Copilot Instructions

·15 mins
Nitin Kumar Singh
Author
Nitin Kumar Singh
I build enterprise AI solutions and cloud-native systems. I write about architecture patterns, AI agents, Azure, and modern development practices — with full source code.
Table of Contents

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:

@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:

@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:

  1. Repository custom instructions - These apply to any conversation held in the context of the repository, providing codebase-wide context.
  2. 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:

  1. Create a .github folder at the root of your repository
  2. Inside the .github folder, create a file named copilot-instructions.md
  3. 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:

  1. Open the command palette (Ctrl+Shift+P on Windows/Linux or Cmd+Shift+P on Mac)
  2. Type “Open Workspace Settings (JSON)” and select it
  3. Add "chat.promptFiles": true to enable the .github/prompts folder as the location for prompt files

Creating Prompt Files
#

  1. Open the command palette (Ctrl+Shift+P on Windows/Linux or Cmd+Shift+P on Mac)
  2. Type “prompt” and select “Chat: Create Prompt”
  3. Enter a name for your prompt file (the .prompt.md extension will be added automatically)
  4. Write your prompt instructions using Markdown formatting
  5. 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
#

  1. At the bottom of the Copilot Chat view, click the Attach context icon
  2. In the dropdown menu, click “Prompt…” and choose the prompt file you want to use
  3. Optionally, attach additional files, including other prompt files, to provide more context
  4. Type any additional information in the chat prompt box if needed
  5. Submit the chat prompt

Prompt File Examples
#

Here’s an example of a prompt file for generating a new React form component:

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:

  1. Use VS Code settings:
    • Create a .vscode/settings.json file
    • Add custom instructions under the GitHub Copilot section
  "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`
  }
],
  1. 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
  2. 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:

  1. Open the Setting editor by using the keyboard shortcut Cmd+, (Mac) or Ctrl+, (Linux/Windows)
  2. Type “instruction file” in the search box
  3. 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
#

/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:

# 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: {{user()?.username}}</p>
          <p class="text-on-surface-variant">Email: {{user()?.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:

# 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:

  1. Version Control: Keep the .github/copilot-instructions.md file in version control to track changes over time.
  2. Regular Updates: Update instructions when adopting new framework versions or when evolving coding standards.
  3. Team Contributions: Allow team members to suggest improvements to the instruction files through pull requests.
  4. Periodic Review: Schedule regular reviews of instruction effectiveness and refine as needed.
  5. 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:

  1. Verify the file path: Ensure your file is exactly at .github/copilot-instructions.md
  2. Check settings: Confirm “Use Instruction Files” is enabled in VS Code settings
  3. File format: Make sure your instruction file uses proper Markdown formatting
  4. Repository context: Instructions only apply when working within the repository containing the file

Poor Suggestion Quality
#

If Copilot suggestions don’t match your instructions:

  1. Be specific: Use concrete examples rather than vague descriptions
  2. Add context: Include technology versions and specific frameworks
  3. Use constraints: Explicitly state what NOT to do alongside what to do
  4. Test iteratively: Refine instructions based on actual suggestions received

Performance Issues
#

If Copilot becomes slow with custom instructions:

  1. Optimize file size: Keep instruction files concise and focused
  2. Split complex instructions: Use multiple prompt files for different scenarios
  3. Regular cleanup: Remove outdated or conflicting instructions

Team Adoption Challenges
#

To improve team adoption of custom instructions:

  1. Document benefits: Share before/after examples of improved suggestions
  2. Gradual rollout: Start with core patterns and expand incrementally
  3. Training sessions: Conduct workshops on using Copilot Chat effectively
  4. 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{:target="_blank" rel=“noopener noreferrer”} 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.

Related

Building an AI-Driven Chat Application with .NET, Azure OpenAI, and Angular

·11 mins
Introduction # Artificial Intelligence is transforming how we build applications, particularly in creating natural, conversational user experiences. This article guides you through building a full-stack AI chat application using .NET on the backend, Angular for the frontend, and Azure OpenAI for powerful language model capabilities, all connected through real-time SignalR communication.

GitHub Codespaces: Streamlining Cloud-Based Development

·8 mins
Say goodbye to “It works on my machine” problems forever! GitHub Codespaces provides ready-to-code development environments in the cloud that work exactly the same for everyone on your team. This guide walks through everything you need to know to get started, even if you’ve never used cloud-based development environments before.