These 24 prompts build a real full-stack app with Windsurf and its Cascade agent, from the first scaffold to a deploy config. They are grouped by build stage — scaffold, frontend, data, auth, integrations, ship — so you can work top to bottom or jump to the feature you need next. Every prompt is complete text you paste straight into the Cascade pane in Write mode, with [bracketed placeholders] for your own stack and names.
Stacks are shown as placeholders on purpose — swap in Next.js, React, Node, Postgres, Prisma or Drizzle, Tailwind, shadcn/ui, or whatever you run. For the wider set, keep the 40 best Windsurf prompts roundup handy, and if you get stuck mid-build, the debugging prompts pack pairs well with this one.
1. Scaffold the project
Start by giving Cascade a spec and letting it stand up the project skeleton, then lock in conventions as rules so every later prompt inherits them. A clean scaffold plus a .windsurf/rules file is the difference between an app Cascade can extend and one it fights.
1. Scaffold a full-stack app from a spec
You are in Write mode. Scaffold a new full-stack app in this empty folder.
Stack:
- Frontend + server: [Next.js App Router / React + Node API]
- Database: [Postgres] via [Prisma / Drizzle]
- Styling: [Tailwind + shadcn/ui]
- Package manager: [pnpm]
Requirements:
- Create the project, install dependencies, and set up a runnable dev server.
- Folders: app/ (routes), components/ui, lib/ (db client, utils), server/ (API + data access), tests/.
- Add a health-check route at /api/health that returns { ok: true }.
- Add a .env.example with every variable the app needs and no real values.
- Add README with setup, dev, and test commands.
Plan the file tree first and show it to me, then create the files. Run the build and the dev server, read @terminal, and fix anything that fails. Done when the build passes and /api/health returns 200.Why it works: it fixes the stack, the folder layout, and an acceptance test up front, so Cascade produces a runnable skeleton instead of a pile of half-wired files.
2. Set project conventions in .windsurf/rules
Create rule files under .windsurf/rules so you follow our conventions in every session.
1. rules/00-core.md — trigger: Always On. One short rule: "Always follow the matching glob rules below. Prefer server components, colocate tests, never commit secrets."
2. rules/frontend.md — trigger: Glob, pattern app/**,components/**. Conventions: [functional components], [Tailwind only, no inline styles], accessible markup, one component per file.
3. rules/data.md — trigger: Glob, pattern server/**,lib/db/**. Conventions: all DB access goes through lib/db/[client]; use [Prisma/Drizzle] queries, never raw SQL in routes; validate input with [Zod] at the boundary.
4. rules/tests.md — trigger: Glob, pattern tests/**,**/*.test.*. Conventions: [Vitest], one test file per module, cover the happy path and one failure case.
Keep each rule short and specific. Show me the files before writing them.Best for: stopping yourself from re-explaining your stack in every prompt — a short always-on rule plus scoped glob rules beats one giant file.
3. Scaffold from a PRD file with @file
Read @file:docs/prd.md — that is the product spec for this app.
In Write mode, turn it into a working first version:
- Extract the entities, screens, and user flows from the PRD.
- Scaffold the routes and pages for each screen listed.
- Create placeholder data-access functions for each entity (typed, returning mock data for now).
- Do NOT build auth or integrations yet — leave clearly marked TODOs where the PRD requires them.
Before writing code, list the screens and entities you found and the file you'll create for each, so I can correct you. Then build it and run the dev server.Why it works: @file pins the PRD as the source of truth, and the read-back step catches misread requirements before any code exists.
4. Define the folder structure and boundaries
@codebase Review the current structure and enforce clean layer boundaries.
Rules to apply:
- UI components never import from server/ or the db client directly — they call functions in lib/api.
- All database access lives in server/data/*; routes call these, never the ORM directly.
- Shared types live in lib/types and are imported everywhere; no duplicate type definitions.
In Write mode: move any misplaced code to the right layer, add the lib/api and server/data folders if missing, and update imports. Only touch files that violate a boundary — don't reformat anything else. Run the build and fix any broken imports.Best for: imposing structure on an app that grew organically, without a full rewrite. See the refactoring pack for deeper cleanups.
2. Frontend & UI
Cascade builds UI fastest when you hand it a component library and a concrete target — a description, a screenshot, or a spec of states. Name the library and tokens so it composes existing primitives instead of inventing one-off markup.
5. Build a page from a description
In Write mode, build a [dashboard] page at [app/dashboard/page.tsx].
Layout:
- A top bar with the page title and a primary [New project] button.
- A responsive grid of stat cards (label, big number, trend arrow).
- A table below listing [projects] with columns [name, status, updated], sortable by clicking a header.
- An empty state when there are no rows.
Use our [shadcn/ui] components from components/ui — do not hand-roll buttons, cards, or tables. Pull data from [lib/api/getProjects] (create a typed stub returning sample rows for now). Make it fully responsive: cards stack to one column below [640px]. Run the dev server and confirm it renders without console errors.Why it works: it lists the exact regions, states, and breakpoints, so Cascade builds a complete page rather than a header and a "TODO" comment.
6. Rebuild a UI from a screenshot
I've attached a screenshot of the [settings screen] I want. In Write mode, build it as [app/settings/page.tsx].
- Match the layout, spacing, and grouping in the image as closely as you can.
- Use our existing components/ui primitives and Tailwind tokens — reuse, don't invent new colors or fonts.
- Recreate every interactive element you see (tabs, toggles, inputs) as real, working controls wired to local state.
- Where the image implies data, use a typed placeholder from lib/api.
List the sections you see in the screenshot first, then build. Show me the rendered result and note anything you couldn't infer from the image.Best for: turning a design mockup into working markup that already matches your token system.
7. Wire up a component library and design tokens
Set up [shadcn/ui] and a design-token layer for this app in Write mode.
- Install and configure [shadcn/ui]; add the base components [button, input, card, dialog, table, badge].
- Define design tokens in [tailwind.config] and a CSS variables file: color scale, spacing scale, radius, and typography. Support light and dark.
- Create a components/ui/README that lists each primitive and when to use it.
- Refactor any existing hand-rolled buttons/inputs to use the new primitives — @codebase find them.
Only change presentational code; don't alter data or routing. Run the build and confirm the app still renders.Why it works: defining tokens once and pointing later prompts at the primitives keeps the whole UI visually consistent.
8. Build a responsive form with validation
In Write mode, build a [create project] form at [app/projects/new/page.tsx].
Fields: [name (required, 2-60 chars), description (optional, max 500), visibility (select: private/public), due date (optional)].
- Use [react-hook-form + Zod] for validation; define the schema in lib/schemas and reuse it on the server.
- Show inline field errors and disable submit while pending.
- On submit, call [lib/api/createProject]; on success redirect to the project page, on error show a toast.
- Fully keyboard-accessible and responsive; labels tied to inputs.
Share the Zod schema between client and server — do not duplicate the rules. Run the dev server and test the validation states.Best for: a form whose validation rules are enforced identically on the client and the server from one schema.
9. Add client state management
Add client state management for [the current workspace and open filters] in Write mode.
- Use [Zustand / React context + reducer] — pick the lighter option for this scope and explain your choice in one line.
- Store: [selected workspace id, sidebar collapsed, active filters]. Persist [selected workspace] to localStorage.
- Expose typed hooks (useWorkspace, useFilters). No component reads the store shape directly.
- Wire the existing [sidebar] and [filter bar] to the store.
Don't introduce global state for server data — that stays in [our data fetching layer]. Only touch the components that consume this state. Run the build.Why it works: it scopes state to genuine client concerns and blocks the common mistake of dumping server data into a global store.
3. Backend & data
For the data layer, design the schema first, generate migrations, then build typed API routes and connect the UI to them. Keeping all database access behind a data layer — and pointing Cascade at @docs for your ORM — keeps queries correct and the routes thin.
10. Design a database schema and migrations
Design the database schema for this app using [Prisma / Drizzle] on [Postgres]. Reference @docs for [Prisma/Drizzle] so the syntax is current.
Entities and relationships:
- [User] 1—* [Project]; [Project] 1—* [Task]; [Task] belongs to [User] (assignee, optional).
- Each entity needs id, createdAt, updatedAt. Add the fields implied by the app: [describe].
- Add sensible indexes on foreign keys and any column we filter or sort by.
In Write mode: write the schema, generate the first migration, and run it against the local dev database. Read @terminal and fix errors. Then generate 5 rows of seed data per table in a seed script. Done when the migration applies cleanly and the seed runs.Best for: a correct schema with indexes and a repeatable migration, instead of tables you patch by hand later.
11. Build CRUD API routes
In Write mode, build full CRUD for [Project] as [Next.js route handlers / server actions].
Endpoints: list, get by id, create, update, delete.
- Validate all input with the [Zod] schema in lib/schemas; return 400 with field errors on bad input.
- All DB access goes through server/data/projects.* — routes never call the ORM directly.
- Return consistent JSON: { data } on success, { error } on failure, with correct status codes.
- Scope every query to the current user; never return another user's rows.
Write a test per endpoint (happy path + one failure), then run the tests via @terminal and fix failures. Done when all pass.Why it works: a consistent response shape, per-user scoping, and tests-that-run make the API safe to build the frontend against.
12. Wire the frontend to the API
Replace the placeholder data stubs with real calls to the [Project] API. @codebase find every place that imports from lib/api stubs.
In Write mode:
- Implement lib/api/projects.* to call the real endpoints with typed request/response.
- Use [React Query / server component fetching] for reads; show loading and error states in the UI.
- After create/update/delete, [invalidate the relevant query / revalidate the path] so the list stays fresh.
- Remove the now-unused mock data.
Only change the data-fetching seams and the components that show loading/error — don't restyle anything. Run the dev server and click through create, edit, and delete to confirm the UI updates.Best for: swapping stubs for live data without touching layout, because @codebase finds every seam first.
13. Add pagination, filtering, and search
Add pagination, filtering, and search to the [Project] list, end to end, in Write mode.
Server (server/data/projects + the list route):
- Cursor-based pagination with a [pageSize] limit; return the next cursor.
- Filter by [status] and search by [name] (case-insensitive, indexed).
- Push filtering and paging into the database query — never fetch all rows and filter in memory.
Client:
- Read filters and cursor from the URL query string so state is shareable and refresh-safe.
- Add a search box (debounced), a status filter, and a "Load more" / pager.
Add a test for the paginated query. Run the tests and the dev server; confirm large lists page correctly.Why it works: it forces the filtering into SQL and puts state in the URL, which is where pagination features usually go wrong.
4. Auth & security
Add authentication as a distinct stage, then layer roles, input validation, rate limiting, and correct secret handling on top. Give Cascade the exact library and route-protection strategy so it builds guards you can trust rather than a token check it forgets to apply.
14. Add authentication and protected routes
Add authentication to this app using [Auth.js / Clerk / Lucia]. Reference @docs for [the library] so the setup is current.
In Write mode:
- Configure the provider(s): [email + OAuth with GitHub]. Add the required env vars to .env.example.
- Build sign-in, sign-out, and a session helper (getCurrentUser) usable in server code.
- Protect [everything under /app except /login]: unauthenticated users are redirected to /login.
- Attach the user to requests so data routes can scope queries to them.
Don't hardcode any secret — read from env. Add a test that a protected route returns 401/redirect when unauthenticated. Run it and the dev server; confirm the login flow works end to end.Best for: a login flow plus enforced route protection in one pass, with the guard actually tested.
15. Add role-based access control
Add role-based access control on top of the existing auth. Roles: [owner, admin, member, viewer].
In Write mode:
- Add a role field to [membership] and a can(user, action, resource) helper in lib/authz — one place that owns all permission logic.
- Enforce it on the server: [only owner/admin can delete a project; viewers are read-only]. Return 403 when denied.
- Hide or disable UI actions the current role can't perform, but never rely on the UI for enforcement — the server is the gate.
@codebase find every mutating route and add the check. Write tests for one allowed and one denied case per role. Run them and fix failures.Why it works: a single can() helper plus a sweep of every mutating route stops permission logic from scattering and leaking.
16. Add input validation and rate limiting
Harden the API against bad and abusive input in Write mode.
Validation:
- @codebase find every route that reads a request body or query and ensure it validates with a [Zod] schema; reject invalid input with 400 and field-level errors. Add schemas where missing.
Rate limiting:
- Add rate limiting to [auth and mutation endpoints] using [Upstash rate limit / a middleware]. Reference @docs for [the library]. Key by [user id, falling back to IP]. Return 429 with a Retry-After header when exceeded.
- Put the limits in config, not scattered constants.
Add a test that a route rejects malformed input and that the limiter returns 429 after [N] rapid calls. Run the tests.Best for: closing the validation gaps and abuse vectors that a fast feature build tends to skip.
17. Handle secrets and environment variables
Audit and fix how this app handles secrets and env vars in Write mode.
- @codebase find every hardcoded key, token, URL, or connection string and move it to an environment variable. Never leave a real secret in the repo.
- Create a typed env module (lib/env) that validates required vars at startup with [Zod] and fails loudly if one is missing.
- Ensure client code only ever reads variables prefixed for the client ([NEXT_PUBLIC_*]); server-only secrets must never reach the bundle.
- Update .env.example with every variable (names only, no values) and confirm .env is gitignored.
Report anything that was exposed. Run the build to confirm the typed env passes.Why it works: a validated env module turns a missing or leaked secret into a loud startup failure instead of a runtime surprise in production.
5. Integrations
Integrations are where guessed API shapes cause the most damage, so always anchor Cascade to @docs for the exact provider. The prompts below cover payments, email, file storage, a generic third-party API, and adding your own MCP tool.
18. Add Stripe checkout with @docs
Add [Stripe] payments to this app. Reference @docs for Stripe so you use the current API, not from memory.
In Write mode:
- Add a checkout flow for [a Pro subscription]: a "Upgrade" button that creates a Checkout Session server-side and redirects.
- Implement the webhook at [/api/webhooks/stripe]: verify the signature, and on [checkout.session.completed] / [subscription updated] mark the user's plan in the database.
- Store price/product IDs and keys in env (test keys in .env.example, names only).
- Handle the failure paths: canceled checkout, failed payment, expired session.
Add a test for the webhook handler with a mocked event. Run it. Tell me exactly which Stripe dashboard settings and env vars I must set to test locally.Best for: a checkout plus webhook where the API surface is read from @docs, which is the top cause of broken payment code.
19. Add transactional email
Add transactional email using [Resend / Postmark]. Reference @docs for [the provider].
In Write mode:
- Create lib/email with a typed send() and templated emails for [welcome, password reset, invite].
- Build templates with [React Email / provider templates]; keep copy in one place.
- Trigger the welcome email on signup and the invite email from the [invite teammate] action.
- Read the API key from env; add it to .env.example (name only). Fail gracefully and log if sending fails — never block the request on email.
Add a test that send() is called with the right template and recipient on signup (mock the provider). Run it.Why it works: it puts every email behind one typed helper and makes delivery non-blocking, so a mail outage never takes down signup.
20. Add file uploads and storage
Add file uploads for [project attachments / avatars] using [S3 / Cloudflare R2 / UploadThing]. Reference @docs for [the provider].
In Write mode:
- Server: issue presigned upload URLs so files go straight to storage, not through our server. Validate [content type in an allowlist] and [max size]. Store the file metadata (key, size, type, owner) in the database.
- Client: an upload control with progress, preview, and remove; wire it to [the profile / project] form.
- Serve files via [signed download URLs] scoped to the owner — no public bucket for private files.
Add a test for the metadata + validation logic. Run it and confirm an upload round-trips in the dev server. List the storage/bucket settings and env vars I need to set.Best for: presigned, validated, owner-scoped uploads instead of a leaky public bucket.
21. Connect a third-party API via @docs
Integrate the [Provider] API into this app. Reference @docs for [Provider]; use @web only if the docs are missing a detail.
In Write mode:
- Build a typed client in lib/[provider] with the endpoints we need: [describe the 2-3 calls].
- Handle auth ([API key / OAuth]) from env, retries with backoff on 429/5xx, and a typed error for failures.
- Add a thin service layer our routes call — components and routes never call the provider SDK directly.
- Cache [read responses] for [N minutes] where it's safe to.
Add a test with the provider client mocked. Run it. Note any env vars and account setup I need. Don't touch unrelated files.Why it works: @docs grounds the client in the real endpoints, and the service-layer boundary keeps the SDK from leaking across the app.
22. Add an MCP tool to Cascade
I want Cascade to reach [our internal API / a database / a third-party service] through an MCP server while we build.
In Write mode:
- Add an MCP server config for [the server] to this workspace's Windsurf MCP settings, reading credentials from env (never commit them).
- If we need a custom one, scaffold a small MCP server in [mcp/] exposing the tools: [list the 1-3 tools and their inputs/outputs].
- Document in mcp/README how to run it and which env vars it needs.
Then confirm the tools are available to Cascade and show me one example call. Don't expose any write/destructive tool without an explicit confirmation step.Best for: giving Cascade first-class access to your own systems so it can build against live data safely.
6. Ship it
Shipping means tests that run, CI that gates merges, a container or deploy config, and a repeatable release path. Wrap the whole sequence in a /workflow so a release is one slash command, not a checklist you retype.
23. Write tests and run them, add CI
Add a test suite and CI in Write mode.
Tests:
- Set up [Vitest] for unit/integration and [Playwright] for one end-to-end flow: [sign in -> create project -> see it in the list].
- @codebase cover the critical paths: auth guard, CRUD data layer, validation, and the [Stripe webhook]. Aim for meaningful coverage, not a number.
- Run the whole suite via @terminal and fix every failure before finishing.
CI:
- Add a [GitHub Actions] workflow that installs deps, runs lint, typecheck, the test suite, and the build on every PR. Cache dependencies. Fail the job on any error.
Done when the suite passes locally and the CI file is valid. Show me the final test run output.Why it works: "run the whole suite and fix every failure" turns test-writing into a self-correcting loop that ends green.
24. Dockerfile, deploy config, and a /ship workflow
Make this app deployable, then save the release steps as a reusable workflow.
In Write mode:
- Write a multi-stage Dockerfile that builds and runs the app on [Node LTS], with a small final image and a non-root user. Add a .dockerignore.
- Add deploy config for [Vercel / Fly.io / Railway / Docker + a VPS]: build command, start command, health check at /api/health, and the full env-var list the platform must set.
- Document a production migration step so the database is migrated before or during deploy.
Then create .windsurf/workflows/ship.md, invoked as /ship, that runs in order: install deps, run migrations, lint, typecheck, run the test suite, build, and (if all pass) produce/refresh the deploy artifact. Have each step read @terminal and stop on the first failure.
Build the Docker image locally to confirm it succeeds, then show me the /ship workflow file.Best for: collapsing a whole release checklist into one /ship command you can run every deploy.
Work through these in order and you have a scaffolded, styled, data-backed, authenticated, integrated, tested, and deployable app. When a step fights back, reach for the best-prompts roundup or, for prompt-writing mechanics, the how to prompt Windsurf guide. Building on a different agentic tool this week? The best Lovable prompts cover similar ground for that stack.
Frequently Asked Questions
Can Windsurf build a full-stack app from a single prompt?
Cascade can scaffold a working full-stack app from one detailed prompt, but you get better results by breaking the build into stages — scaffold, frontend, data layer, auth, integrations, then deploy — and using @docs and acceptance criteria at each step. Treat the one-shot prompt as a starting skeleton you then refine feature by feature.
Should I use Cascade Write mode or Chat mode for building features?
Use Write mode to build — it edits files across the project and runs terminal commands like installs, migrations, and tests. Use Chat/Ask mode to plan an architecture, review a schema, or explore the codebase read-only before you let it touch anything.
How do I keep Cascade consistent with my stack and conventions?
Put durable conventions in .windsurf/rules — a short always-on rule plus scoped glob rules for your framework, database layer, and UI kit. Rules load every session, so you stop re-explaining your stack in every prompt, and Cascade generates code that matches your patterns by default.
How does Cascade get third-party library APIs right?
Point it at @docs for the exact library — Stripe, an email provider, your ORM — so it reads the indexed documentation instead of guessing. This is the biggest single win for integrations, where hallucinated method names and outdated APIs are the most common failure.
Can Windsurf write and run the tests for my app?
Yes. In Write mode you can tell Cascade to write tests for a feature and then run them, read the output from @terminal, and fix failures until they pass. Defining acceptance criteria like "done when the test suite passes" makes the loop self-correcting.
How do I use a workflow to ship an app end to end?
Save a reusable recipe as Markdown in .windsurf/workflows/ and invoke it with a /slash command in Cascade. A ship workflow can install dependencies, run migrations, run the build, run the test suite, and produce a deploy config in one command instead of retyping the sequence each release.