Scaffold a SaaS starter in a new project: {{language}} on the backend, {{database}} for data, and {{addon}} integrated for billing or email as appropriate. Set up auth with signup, login, password reset, and session middleware. Model organizations with a members table and an owner, admin, member role enum, and enforce roles in middleware, not in individual handlers. Ship three pages: a public landing page, an app shell behind auth, and a settings page for profile and org management. Add environment variable handling with a checked-in .env.example and a config module that fails fast on missing keys. Include seed and migration scripts wired to package scripts. Do not stub payments with fake success responses; leave a clearly marked integration point if keys are absent. Verify by booting the app, running migrations against a fresh database, and summarizing the directory structure.What GitHub Copilot is good at
GitHub Copilot started as an autocomplete engine and has grown into a full assistant inside VS Code, JetBrains IDEs, Visual Studio, and github.com. The pieces you will use most are Copilot Chat for questions, Copilot Edits for multi-file changes, and agent mode, which can run terminal commands and iterate on failing builds until the task is done. It fits developers who already live in VS Code and want AI help without switching editors, and teams on GitHub who want the same assistant in pull requests and the web UI. Compared with Cursor or Windsurf, Copilot is an extension rather than a fork of the editor, so it inherits your existing setup, keybindings, and extensions. Compared with Claude Code, it is driven from panels and inline widgets rather than a terminal session. Context is the main thing you manage: Copilot sees open files by default and reaches the rest of the repo through @workspace and #file references.
How to prompt GitHub Copilot
Match the prompt to the surface. Inline completions need nothing more than a good function name and a comment. Copilot Chat and Copilot Edits are where the prompts on this page go, and they reward the same structure: name the task in the first sentence, then list constraints, then say how to verify.
Be explicit about context. Copilot weights your open files heavily, so open the two or three files that matter before you send the prompt, or reference them directly with #file. Use @workspace when the answer depends on code you have not opened, such as where a route is registered or which test runner the repo uses.
Scope each request to one reviewable change. Copilot Edits shows you a diff per file; a prompt that asks for one endpoint plus its tests produces a diff you can actually read, while "build the whole feature" produces twenty files of churn. In agent mode, always state the command that proves the work, such as npm test or pytest, because the agent will run it and fix failures.
Always specify: language and framework version, the file or folder to touch, error handling expectations, and what done means. Copilot fills gaps with the most popular answer on GitHub, which is often not your stack.
All 20 GitHub Copilot prompts
App 4 prompts
Open Copilot Edits and add src/App.tsx, src/components/TaskList.tsx, src/components/TaskItem.tsx, and src/lib/storage.ts to the working set. Build a React to-do app in TypeScript with three views, all, active, completed, driven by a single tasks array in App state. Each task needs id, title, done, and createdAt. Persist through src/lib/storage.ts using localStorage under the key todo.v1, and hydrate on first render so a page refresh keeps tasks. Handle these cases: adding a blank title is rejected with an inline message, toggling done updates the count in the footer, and clearing completed removes only done items. Keep components under 80 lines each and put no fetch calls anywhere. When the diff appears, I will review each file, accept, then run npm run dev and check the three filter views by hand.
Build a blog where content lives as markdown files with frontmatter, title, date, slug, draft, in content/posts/. Write lib/posts with two functions: getAllPosts, sorted newest first and excluding draft true, and getPostBySlug, which parses the body to HTML. Add a listing page at /blog showing title, date, and a 30-word excerpt cut at a word boundary, and a detail page at /blog/[slug] that returns a real 404 for unknown slugs rather than an empty shell. Generate an RSS feed at /feed.xml from the same getAllPosts call so listing and feed can never disagree. Malformed frontmatter should fail the build with the offending filename in the error, not render a broken card. Ask me for the framework in this repo before scaffolding routes. Verification: I will add one draft post and one published post, build, and confirm exactly one appears in both the list and the feed.
Build an internal admin panel for managing {{resource}} records on top of the existing API. Use @workspace to find the current model fields and auth middleware, then generate routes under /admin guarded by a role check that returns 404, not 403, so the panel's existence is not advertised. Screens: a paginated table with server-side sorting on created date and a text filter, a detail view showing every field read-only, and an edit form covering only fields marked editable in a single config object at the top of the module. Every write goes through the existing service layer, no direct database calls from admin handlers, and each change appends who, when, before, after to an audit_log table. Destructive actions need a typed confirmation, the record's own identifier, not a yes button. Acceptance: a non-admin session hitting /admin sees a 404, and editing one field produces exactly one audit row.Backend 4 prompts
Working with Copilot Edits file by file inside VS Code: begin by opening src and mirroring its existing layout. Step 1: a route module in {{language}} declaring five operations for {{resource}}: collection GET paged by a cursor token plus a limit count, single-record GET, POST, PATCH, DELETE. Step 2: a service module holding business rules, and a repository that talks to {{database}} only through bound parameters or the ORM this project already ships; concatenated SQL strings are forbidden. Step 3: register the router inside the application entry file. Behavior contract: schema-check incoming JSON and reply 400 naming which fields failed, 404 when an id matches nothing, 409 for uniqueness conflicts. Step 4: integration tests for one successful round trip, one schema-violating payload, one lookup of an absent record. Run the project's test command and report each added or modified file.Create a GraphQL API for a book catalog in {{language}}, schema first. Start in schema.graphql: types Book, Author, and Review, a books query with cursor pagination taking first and after, and an addReview mutation returning the updated Book. Then open src/resolvers/ and write one resolver file per type, reading from {{database}} through the existing db module, no raw connection strings. Wire a DataLoader for Author so resolving fifty books issues one author query, not fifty. Mutations must validate that rating sits between 1 and 5 and return a typed error in the payload rather than throwing. Reference #file:schema.graphql in follow-ups so the chat stays anchored to the contract. To verify, I will start the server and run a query fetching ten books with authors while logging SQL, expecting two statements.Add JWT authentication to this API. Use @workspace to locate the route registration file and the user model backed by {{database}} before touching anything. Implement POST /auth/login, verifying the password with the hashing library already in package.json, and POST /auth/refresh, which rotates a refresh token stored as an httpOnly, Secure, SameSite strict cookie. Access tokens live fifteen minutes, carry sub and role claims only, and are signed with a secret read from the environment, fail startup if it is missing rather than defaulting. Write middleware/requireAuth that returns 401 for absent or expired tokens and 403 when a role guard fails, with distinct machine-readable error codes. Do not store tokens in localStorage anywhere. List every changed file when done. I will verify with three curl calls: login, a protected route with the token, and the same route after tampering with one signature character, expecting 200, 200, 401.Design a Postgres schema for a multi-tenant app as numbered SQL migration files in db/migrations/, no ORM models yet. Tables: organizations, users, memberships joining the two with a role column constrained by a CHECK to owner, admin, or member, and invitations with a unique token and an expires_at. Emails use citext with a unique index, every table gets created_at and updated_at as timestamptz defaulting to now(), and updated_at is maintained by one shared trigger function, written once, applied per table. Foreign keys choose deletion behavior deliberately: memberships cascade when their organization dies, invitations do too, but users never cascade from anything. Add a partial unique index so a user holds at most one pending invitation per organization. Include a down migration for every up. To verify, I will run the migrations against a scratch database, insert one org with two members, delete the org, and confirm users survive while memberships vanish.
Features 3 prompts
Open Copilot Chat with the backend folder as context and describe the query path before generating code. Server side: expose GET /search accepting q, a limit, and an optional filter argument; comparison against {{resource}} name and description must ignore case. Prefer the full-text capability {{database}} offers natively; when that is missing, fall back to ILIKE supported by a trigram index, or a prefix one, added through a new migration. Treat % and _ from users as literal characters, never wildcards. Client side: wire a text box that waits 300ms after typing stops, aborts any in-flight fetch when a newer one starts, renders an empty-state card echoing the sanitized query when nothing matches, and truncates long result sets behind a show-more control. A blank q must yield an empty array with status 200, not an error. Cover the literal-character escaping and the empty-string request in tests, run them, and summarize the diff.Add an LLM chatbot to this app without exposing the provider key to the browser. Create an api/chat endpoint that accepts {messages: [{role, content}]}, forwards to the model provider with a system prompt loaded from prompts/support.md, and streams tokens back as server-sent events. In the client, build a ChatPanel component that renders the stream incrementally, disables the send button while a response is in flight, and offers a cancel control wired to AbortController. Handle the ugly paths: provider timeout returns a retry hint, a 429 shows a wait message, and an empty user message never leaves the client. Cap history sent upstream at the last twelve turns. Use @workspace first to find where API routes and env config live in this repo. I will verify by watching the network tab for one streaming response and confirming the key appears only in server code.Add transactional email to this app using {{addon}} for delivery. Create lib/mailer exposing one function per message type, welcomeEmail and passwordResetEmail to start, each taking a typed payload rather than a raw template string. Templates live in emails/ as one file per message with plain-text and HTML variants generated from the same content, since some corporate inboxes strip HTML. Sends must happen after the triggering database write commits, never inside the transaction, and a provider failure logs the message id and payload without crashing the request that triggered it. Gate everything behind an EMAIL_ENABLED flag that defaults off in development so local signups never mail real users. Add a dev-only preview route listing rendered templates in the browser. When you finish, list changed files and point out where retries would go. I will verify by triggering a signup locally with the flag on and a sandbox key.Frontend 3 prompts
Build a dashboard page in this codebase that summarizes {{resource}} activity. Use the component library and styling approach already in the project; check package.json before picking anything new. Layout: a four-stat summary row, a line chart of {{resource}} over the last 30 days, and a recent-items table with column sorting and a date-range filter. Fetch data through the existing API client with loading skeletons per section, so one slow request does not blank the page. Handle the empty account case with a short explainer instead of a zeroed chart, and show a retry button on fetch errors. Keep chart logic in its own component and type the API response instead of using any. Verify by running the dev server and the type checker, then list the components you added.Build a single-page marketing site as index.html plus one stylesheet, no framework. Sections in order: a hero with one headline and one primary call to action, a three-column feature row that collapses to stacked cards under 640px, a code sample block with a copy button, and a footer with a mailto link. Use semantic elements, header, main, section, footer, and give every image an alt attribute or an empty alt if decorative. Load zero external fonts and zero scripts except the copy button handler. Work section by section with inline chat so each diff stays reviewable, and keep the stylesheet under 200 lines by reusing custom properties for color and spacing. Acceptance: the page renders sensibly at 360px and 1440px, tab order reaches the call to action before the footer, and the copy button writes the sample to the clipboard.
Build a pricing page component at src/components/PricingTable with three tiers, Free, Team, and Business, fed from a plans array in src/data/plans so copy edits never touch markup. Add a monthly and annual billing toggle implemented as a radiogroup, not a bare div with onClick, and show the per-month price under annual billing with the yearly total in smaller text beside it. Mark one tier as featured through a boolean in the data, rendered as a border and a badge, no duplicated card markup. Below the cards, render a feature comparison table where the tier row stays visible while scrolling, and yes and no cells carry text for screen readers, not just icons. The Free tier button says Start, paid tiers say Choose plus the plan name. Acceptance: toggling billing updates all three prices from data, keyboard arrows move between billing options, and no price string is hardcoded in JSX.
Refactor 3 prompts
Refactor the component I have open; it has grown too large to review. Work in this order and stop after each step so I can check the diff in Copilot Edits. Step 1: inventory what the component does, its state, effects, and render sections, and propose a split into child components and hooks with names. Step 2: extract pure logic into plain functions or a custom hook, no behavior changes, props typed explicitly. Step 3: extract the render sections into child components, keeping state at the lowest level that still works. Do not rename public props, change emitted events, or touch files outside this component's folder. After each step run the existing tests and the type checker; if either fails, fix the extraction rather than adapting the tests. End with a summary of what moved where.
Migrate this package from JavaScript to TypeScript one directory at a time, starting with src/utils because nothing imports it from outside. First add tsconfig.json with allowJs true, checkJs false, and strict true so converted files are held to the full standard while the rest compiles untouched. Rename each file to .ts, type exported function signatures explicitly, and let inference handle locals. Where a shape crosses module boundaries, lift it into src/types.ts rather than repeating inline object types. Do not change any runtime behavior, no logic edits, no reordering, and flag any spot where an implicit any hides a probable bug instead of silently casting. Never use ts-ignore, prefer unknown plus a narrowing check. After each directory, I will run npx tsc --noEmit and the existing test suite, and the diff must show only renames and type annotations.
Review the data access layer in src/repositories/ for query waste against {{database}}. Start by asking me for the slowest endpoint, then trace its call path and list every query it issues per request, including ones hidden behind ORM lazy loads. Targets: replace per-row lookups in loops with a single IN query or a join, cut SELECT * down to the columns the caller reads, and convert OFFSET pagination on the activity feed to keyset pagination on (created_at, id). For each proposed index, output the CREATE INDEX statement in a migration file plus the EXPLAIN you expect before and after, and I will paste back the real plan to confirm the index is used. Do not denormalize anything and do not add caching, this pass is queries only. Done means the endpoint issues a fixed number of queries regardless of row count, verified by the query log.Testing 3 prompts
Write unit tests for the module I have open in the editor. First read the file and list its exported functions, the branches inside each, and any external calls that need mocking, then generate the test file next to it following the naming pattern the repo already uses. Cover each branch, not just the happy path: boundary values, empty inputs, thrown errors from mocked dependencies, and any async rejection paths. Mock at module boundaries only; do not mock the function under test or private helpers. Use the assertion style and test runner already configured in the project rather than introducing a new one. If a function is untestable without refactoring, say so and stop instead of testing implementation details. Run the new tests, confirm they pass, then deliberately break one assertion to prove the test can fail, and restore it.
Write Playwright end-to-end tests for the checkout flow in tests/e2e/checkout.spec.ts. Cover three journeys: guest adds an item and pays, a saved-card user pays in two clicks, and a declined card returns the shopper to the payment step with the cart intact. Select elements by role and accessible name, never by CSS class, and add data-testid only where no accessible handle exists, noting each addition in the diff. Attach #file:src/pages/Checkout.tsx to the chat so locators match the real markup instead of guessed ids. Stub the payment provider at the network layer with page.route, one fixture for success and one for decline, so no test hits a live gateway. Each test must assert the final URL and one on-page confirmation string, not just the absence of errors. I will run npx playwright test --project=chromium and expect all three to pass headless.
Audit src/services/ for swallowed and unhandled errors, then fix and prove each fix with a test. Hunt for four patterns: awaited calls with no try around them in request handlers, catch blocks that only console.log, fetch responses used without checking res.ok, and JSON.parse on external input. Replace them with a small error hierarchy in src/errors.ts, AppError with an HTTP status and a code, plus NotFoundError and UpstreamError subclasses, and one boundary in the request pipeline that maps AppError to a response and everything else to a logged 500 with no stack in the body. Every fix gets a companion test in the matching .test file that forces the failure, a 404 from an upstream stub, malformed JSON, a rejected promise, and asserts the mapped status and code. Print a table of file, pattern found, fix applied. I will verify by reverting one fix locally and watching its companion test fail.
Prompting patterns that work in GitHub Copilot
Pin context with #file
Instead of hoping Copilot finds the right module, reference it directly: #file:src/routes/orders.ts. This works in Chat and Edits and removes the most common failure, edits applied to a lookalike file. Combine two or three references for cross-file changes.
Lead with the diff you want
Describe the change the way a reviewer would see it: which files change and what gets added to each. Copilot Edits maps that structure onto its per-file diffs, so the output arrives pre-organized for review.
Verification clause
End agent-mode prompts with the exact command that must pass, like npx vitest run, and tell it to iterate until green. Without this, the agent stops at code that compiles, not code that works.
Ask @workspace before you build
Run a reconnaissance question first, such as "@workspace how is auth middleware wired up?". Fold the answer into your build prompt. Two small prompts beat one large one that guesses wrong about your conventions.
Common mistakes
Prompting against closed files
Chat answers from what it can see. If the relevant service file is closed and unreferenced, Copilot invents a plausible version of it and writes code against the invention. Open the file or add a #file reference before asking.
One prompt, whole feature
Asking Edits for an entire feature across a dozen files produces diffs nobody reviews carefully. Split the work into slices, each with its own accept-or-reject pass.
Letting defaults pick your stack
Left unspecified, Copilot reaches for Express, Jest, and REST-by-convention because those dominate public code. State your framework, test runner, and versions every time, even when they feel obvious from the repo.