Copilot Path-Specific Instructions: applyTo Explained
How Copilot path-specific instructions work: .instructions.md files, applyTo glob syntax, the stacking problem, and per-language rule patterns.
Copilot path-specific instructions let you scope rules to parts of your codebase instead of dumping everything into one repo-wide file. The mechanism is simple: you create .instructions.md files in .github/instructions/, and each file declares an applyTo glob in its frontmatter. When Copilot works on a file that matches the glob, those instructions get added to the context. When it doesn't, they stay out.
That's the whole feature in two sentences — but the details matter. Globs that silently never match, multiple instruction files stacking in unpredictable order, and no built-in way to see what actually applied are the three problems that bite most teams. This guide covers all of them.
Repo-wide vs path-specific: the instruction layers
GitHub Copilot reads instructions from several layers, and they combine rather than replace each other:
| Layer | Location | When it applies |
|---|---|---|
| Repo-wide | .github/copilot-instructions.md | Every request in the repo |
| Path-specific | .github/instructions/*.instructions.md | Requests touching files that match the applyTo glob |
| Agent instructions | AGENTS.md | Agent-mode requests, nearest file wins |
| Personal | GitHub settings → Copilot | All your requests, every repo |
| Organization | Org settings → Copilot | All members, every org repo |
The repo-wide file is the right place for things that are true everywhere: tech stack, package manager, error-handling conventions. We covered that file in depth in the GitHub Copilot custom instructions guide.
Path-specific .instructions.md files exist because "true everywhere" runs out fast. Your React component conventions are irrelevant when Copilot is editing a Terraform file. Your SQL migration rules are noise in a CSS change. Scoping instructions to paths keeps the context relevant — and keeps your repo-wide file short enough that the model actually follows it.
How Copilot path-specific instructions work: .instructions.md and applyTo
Setting up Copilot path-specific instructions takes four steps:
- Create the directory. Path-specific files live in
.github/instructions/by default (in VS Code you can add more locations with thechat.instructionsFilesLocationssetting):
mkdir -p .github/instructions
- Create a file ending in
.instructions.md. The suffix is required —typescript.mdis ignored,typescript.instructions.mdis picked up:
touch .github/instructions/typescript.instructions.md
- Add frontmatter with
applyTo. This is the key that makes the file conditional:
---
applyTo: "**/*.ts,**/*.tsx"
description: "TypeScript and React conventions"
---
# TypeScript rules
- Strict mode is on; never use \`any\`, prefer \`unknown\` with narrowing
- Named exports only, except Next.js page.tsx and layout.tsx
- Zod schemas for all external data; infer types with z.infer
- Use early returns over nested conditionals
- Commit it. These files are plain markdown in git, so they version, review, and diff like any other code. That also makes them a natural fit for treating AI rules as versioned artifacts.
applyTo: Copilot's glob targeting field
The applyTo value is one string containing one or more glob patterns, comma-separated:
# Single pattern
applyTo: "**/*.py"
# Multiple patterns in one string
applyTo: "**/*.test.ts,**/*.spec.ts,tests/**"
# Everything (equivalent to repo-wide, but kept in a separate file)
applyTo: "**"
Two behaviors worth knowing:
applyTo: "**"makes the file unconditional. Useful when you want to split a huge repo-wide file into topical files without changing when they load.- Omitting
applyTomakes the file manual-only. In VS Code, an instructions file without anapplyTopattern isn't attached automatically — you add it to a chat request by hand. That's a feature: keep rarely-needed playbooks (release process, incident response) as manual attachments instead of permanent context.
The description field is optional but worth filling in — it's what you (and your editor's UI) see when browsing which instruction files exist.
Glob patterns that actually match
Most "my instructions aren't applying" reports trace back to a glob that never matched anything. Patterns are matched against paths relative to the workspace root, and the rules are stricter than people expect:
# WRONG: matches only .ts files in the repo root
applyTo: "*.ts"
# RIGHT: matches .ts files at any depth
applyTo: "**/*.ts"
# WRONG: a bare directory name matches nothing
applyTo: "src/api"
# RIGHT: match everything under the directory
applyTo: "src/api/**"
# RIGHT: scope by directory AND extension
applyTo: "src/api/**/*.ts"
# RIGHT: multiple targets, one string
applyTo: "apps/web/**,packages/ui/**"
A quick checklist when a pattern misbehaves:
- Prefix with
**/unless you genuinely mean root-level files only. - Suffix directories with
/**— a directory path alone is not a match-all. - Mind case sensitivity.
**/*.MDand**/*.mdare different patterns. - Test against real paths. Run
git ls-files 'src/api/**/*.ts'in your shell — if git's pathspec finds nothing, your glob almost certainly matches nothing too.
If you're coming from Cursor, this will feel familiar: .mdc rules use a globs frontmatter field with the same trip hazards. The concepts transfer almost one-to-one, which is exactly why teams that maintain rules across multiple tools write the glob logic once and generate each tool's format.
The stacking problem: when instructions merge unpredictably
Here's the part the docs undersell. When Copilot builds context for a request, it doesn't pick the "best" instruction file — it includes all of them that apply: the repo-wide file, plus every .instructions.md whose applyTo matches, plus personal and organization instructions. Copilot instructions stacking is additive, and there is no priority field, no override keyword, and no documented ordering you can rely on.
That means if two layers disagree, you've written a coin flip:
# .github/copilot-instructions.md
- Use named exports for all modules
# .github/instructions/legacy.instructions.md (applyTo: "legacy/**")
- This area uses default exports; match the existing style
Both instructions land in the same context when Copilot edits legacy/utils.ts. Sometimes the model favors the specific one, sometimes the general one. The behavior can change between model versions. You cannot fix this with clever wording — "this overrides the repo-wide rule" is a suggestion to a language model, not a precedence system.
The fix is structural, not verbal:
- Give each file a disjoint domain. The repo-wide file covers stack and universal conventions; each
.instructions.mdcovers one language, directory, or file type. No rule appears in two files. - Never restate a repo-wide rule in a scoped file — not even to agree with it. Duplication today becomes contradiction after the next edit to one copy.
- Carve exceptions out of the general rule at the source. Instead of a legacy file that contradicts the repo-wide export rule, write the repo-wide rule as "Use named exports (exception:
legacy/matches existing style)". One source of truth, zero ambiguity. - Audit for overlap when globs change. Two files matching
**/*.tsandsrc/**both fire onsrc/index.ts. Overlapping globs are fine only if the content is disjoint.
Copilot path-specific instructions: patterns that scale
Three shapes cover almost every real codebase.
Per-language rules
One file per language, glob on extension:
---
applyTo: "**/*.py"
description: "Python conventions"
---
- Python 3.12, type hints on all public functions
- Use pytest fixtures over setUp/tearDown
- Ruff handles formatting; never hand-format
Per-directory rules (monorepos)
Glob on workspace path — essential when apps/web and apps/api follow different conventions:
---
applyTo: "apps/api/**"
description: "API service conventions"
---
- Fastify with typed route schemas; every route declares response types
- Database access only through the repository layer in src/db
- Errors: throw ApiError subclasses, never raw Error
Per-file-type rules
Glob on naming conventions rather than extensions — tests, migrations, config:
---
applyTo: "**/*.test.ts,**/*.spec.ts"
description: "Test conventions"
---
- Vitest, not Jest — import from "vitest" explicitly
- No mocking modules under test; mock at network boundaries only
- Test names describe behavior: "returns 404 when the skill is missing"
Keep each file under roughly 50 lines. Path scoping buys you relevance; long files spend that budget on rules the model skims. If a file grows past that, it's usually two domains pretending to be one — split it and tighten both globs.
If your team also uses Copilot's custom agents, the same scoping mindset applies to their configuration — see the Copilot custom agents guide for how instruction files and agent definitions interact.
Verifying which instructions applied to a response
VS Code custom instructions are invisible by default, which makes debugging feel like guesswork. It isn't — there are two reliable checks:
- Read the References list. In Copilot Chat, expand the references/used-files section on a response. Instruction files that were included in context are listed there by name. If
typescript.instructions.mdisn't in the list while you're editing a.tsfile, your glob didn't match — go back to the checklist above. - Ask a canary question. Add a distinctive, harmless rule to the file — "start test names with the word 'verifies'" — then ask Copilot to generate a test in a matching file and in a non-matching file. The rule should appear in one and not the other. This catches both broken globs and stacking conflicts, because a canary that appears inconsistently in matching files means another instruction file is fighting it.
Make the canary check part of your rules-review workflow: any PR that touches an applyTo pattern should include a one-line note on how it was verified.
Keeping instruction files consistent across repos and tools
Path-specific instructions are repo-local files, which is exactly right for one repo and painful for ten. Every repo gets its own copy of the TypeScript rules; copies drift; the stacking discipline you enforced in one repo quietly erodes in the others.
A registry fixes the drift. With localskills.sh you publish each rule set once as a versioned skill, then install it wherever it's needed — and because the CLI writes each tool's native format from the same source, your Copilot instruction files, Cursor .mdc rules, and Claude Code skills stay in sync:
npm install -g @localskills/cli
localskills login
# One skill, three tool-native outputs
localskills install my-org/typescript-rules --target copilot cursor claude
Updates ship as new versions with rollback, so "we tightened the test conventions" propagates deliberately instead of by copy-paste.
FAQ
What does applyTo do in Copilot?
applyTo is a frontmatter field in .instructions.md files that holds one or more comma-separated glob patterns. When a Copilot request involves a file matching the pattern, the instructions file is added to the model's context; otherwise it's left out. applyTo: "**" applies the file to everything, and omitting it makes the file manual-attach only.
Can I use applyTo in .github/copilot-instructions.md?
No. The repo-wide copilot-instructions.md file always applies and ignores frontmatter-based scoping. Path targeting only works in .instructions.md files under .github/instructions/ (or extra locations configured in VS Code).
What happens when multiple instruction files match the same file?
They all get included — repo-wide, every matching path-specific file, plus personal and org instructions. There's no precedence mechanism, so contradictory rules resolve unpredictably. Keep each file's domain disjoint and never state the same rule in two places.
Why aren't my .instructions.md files being picked up?
The three usual causes, in order: the filename doesn't end in .instructions.md, the glob doesn't actually match (a bare *.ts only matches the repo root — use **/*.ts), or the file lives outside .github/instructions/ and the location isn't configured. Confirm with the References list on a chat response.
Do path-specific instructions work outside VS Code?
Support is broadest in VS Code and for Copilot's repo-based features on github.com; other editors have been adding support for the .instructions.md format on their own schedules. Since the files are plain markdown in your repo, they're safe to commit either way — editors that don't read them simply ignore them.
One rule set, every repo, every tool. Create a free localskills.sh account and publish your instruction files as versioned, installable skills.
npm install -g @localskills/cli
localskills login
localskills publish