What is code scaffolding?
1,901 words · 10 min read

What is code scaffolding?
Code scaffolding is the technique of generating a software project's baseline structure — directories, config files, build setup, and starter code — so that you can skip the boilerplate and start writing the parts that are actually unique to your application.
When you run
rails new myapp, cargo new myapp, or npx create-next-app, you're scaffolding. The tool reads your choices (or its own defaults), creates a folder, and fills it with everything a project of that type usually needs: a build file, a source directory, a .gitignore, sometimes a starter "hello world" route, sometimes a test setup, often a lockfile.Clone it, run it, build on it.
Where the metaphor comes from
Real scaffolding on a building site is temporary structure that lets workers reach parts of the building they couldn't otherwise. It supports the work. It isn't the work. Code scaffolding works the same way.
The generated files are the platform your application sits on while you build it. Files you'll never touch (
.gitignore, basic Webpack config). Files you'll heavily modify (your routing layer, your data models). Files you'll delete the day you outgrow the defaults. The scaffolding's job is to get you to the point where you can start writing the interesting code without making 47 setup decisions first.What scaffolding does for you
Mostly these 3 things:
- Picks the boring decisions. What's the directory layout? Which test framework? How does the build run? A scaffolder picks defaults so you don't have to.
- Wires things together. A blank
package.jsondoesn't help much. A scaffolder gives youpackage.jsonplus asrc/, plus a workingnpm run dev, plus the dependencies installed. - Encodes a community's idea of "right." When you scaffold a Rails app, you're inheriting a decade of opinions about how Ruby web apps should be organized. That's a lot of free signal.
The phrase "convention over configuration," coined by Rails, is an argument for scaffolding. The convention is what the scaffolder generates.
Is "AI scaffolding" the same thing?
The term has split into two meanings.
AI-assisted code scaffolding is the original meaning applied to AI tools. You ask Cursor, Claude, or Kiro to "set up a Next.js project with TypeScript and Tailwind" and it runs the scaffolding command, or generates the equivalent files directly. Same scaffolding, with an LLM at the steering wheel.
LLM scaffolding (sometimes called "agent scaffolding") refers to the code, prompts, and tools wrapped around a language model to make it more capable. A web search tool, a code execution sandbox, memory between calls — scaffolding for the LLM. Same construction metaphor, applied to a different builder.
One kind scaffolds your project with AI. The other scaffolds AI with code.
Why scaffolding looks different in every stack
I've spent my career straddling different platforms. If you work on the web, this is pretty normal. Though, most stacks set out to focus on a particular problem that drives the idioms the creators adopt. Some are more or less opinionated about scaffolding structure.
Rails ships you the kitchen sink and a generator for every noun. Rust hands you a
Cargo.toml and wishes you well. Next.js asks eight questions before writing a single line. SvelteKit just shipped a brand new CLI that renamed the entry point to sv create. Four frameworks. Four philosophies. Four six-month futures, none alike.Aside: this is one of those things that's invisible until you do it across enough stacks that the patterns start showing through.
So I made a matrix. Same workflow as the polyglot build tools comparison: draft the structure, fill the first row by hand, hand the AI a research guide and a task list, verify every command against current docs.
Scaffolding by stack
| Stack | Scaffold command | Project variants | Included out of the box | Post-init generators | Config style | Scaffolding philosophy |
|---|---|---|---|---|---|---|
| Ruby on Rails | rails new myapp | --api, --minimal, --database=postgresql, --javascript=esbuild, etc. | DB, ORM, router, views, tests (Minitest), asset pipeline, dev server | ✅ Many: scaffold, model, controller, migration, mailer | config/*.rb files | Convention over configuration; one canonical layout |
| Django | django-admin startproject myproject then python manage.py startapp myapp | None at project level; apps are the unit | Settings, URL conf, WSGI/ASGI entry, admin, ORM (no DB chosen yet) | ⚠️ A few: startapp, makemigrations, no view/model generators | settings.py Python module | Explicit over implicit; you wire it up |
| Spring Boot | spring init --dependencies=web,data-jpa myapp or Spring Initializr web UI | Driven by dependency selection (web, data-jpa, security, kafka, etc.); over 30 starters | Build file (Maven/Gradle), main class, application.properties, picked starters wired in | ❌ None bundled (IDE templates fill the gap) | application.properties / application.yml | Pick your dimensions up front; starters wire the rest |
| Phoenix (Elixir) | mix phx.new myapp | --no-ecto, --live, --no-html, --umbrella, --database | Router, contexts, LiveView, Ecto, ExUnit, asset pipeline, releases | ✅ Many: phx.gen.html, phx.gen.json, phx.gen.live, phx.gen.auth, phx.gen.context | config/*.exs per environment | Full-stack defaults; LiveView assumed unless opted out |
| ASP.NET Core | dotnet new web (or webapi, mvc, blazorserver, worker) | Dozens of templates; each is its own variant | Program.cs, csproj, launch settings, picked template's structure | ⚠️ dotnet aspnet-codegenerator (separate tool, MVC scaffolds) | csproj XML + appsettings.json | Template-per-shape; one CLI, many starting points |
| Next.js | npx create-next-app@latest | Interactive prompts: TS/JS, ESLint, Tailwind, src/, App Router, Turbopack, import alias | App Router scaffold, page, layout, picked tooling pre-wired | ❌ No first-party generators | next.config.js + package.json | Interactive prompts decide the stack |
| SvelteKit | npx sv create myapp | Template prompt + opt-in add-ons: TypeScript, ESLint, Prettier, Vitest, Playwright, Tailwind, Storybook, etc. | Vite + SvelteKit core; everything else is opt-in via add-ons | ✅ sv add (add-ons after init) | svelte.config.js + vite.config.ts | Minimal core; add-ons compose what you need |
| Nuxt | npx nuxi@latest init myapp | Single template; package manager prompt | Nuxt app, pages dir, server routes, auto-imports | ⚠️ nuxi add for components, pages, middleware, layouts | nuxt.config.ts | Auto-imports + module ecosystem do the heavy lifting |
| NestJS | npm i -g @nestjs/cli && nest new myproject | Package manager prompt; otherwise single template | TypeScript, modules, controllers, services, Jest, decorator-driven structure | ✅ Many: nest g module, nest g controller, nest g service, nest g resource | nest-cli.json + per-app tsconfig | Angular-style monolith; generators enforce structure |
| Expo (React Native) | npx create-expo-app@latest myapp | Templates via --template: blank, blank-typescript, tabs, default | Expo Router, TypeScript (in default), example screens | ❌ No generators (use Expo Router file conventions) | app.json / app.config.ts | Managed workflow first; native code stays hidden |
| Flutter | flutter create myapp | --template=app, --template=plugin, --template=package, --template=module | Dart entry, widget tree, platform folders (iOS/Android/web/desktop), tests | ❌ No generators bundled | pubspec.yaml | Single template, multi-platform output |
| Rust + Axum | cargo new myapp then cargo add axum tokio; or cargo loco new for a Rails-style full stack | None canonical via Cargo; Loco ships SaaS / REST / lightweight starters; cargo-generate for arbitrary templates | Plain Cargo: Cargo.toml, src/main.rs, .gitignore. Loco: routing, ORM (SeaORM), auth, jobs, mailers, tests | ⚠️ None in Cargo; ✅ Loco has cargo loco generate model/controller/scaffold (Rails-style) | Cargo.toml + module structure | Cargo gives you a blank canvas; Loco picks Rails conventions for you |
| Go web (Gin/Echo/Chi) | go mod init github.com/me/myapp then go get github.com/gin-gonic/gin; or gonew (Go-team experimental) for module templates; or go-blueprint create for an interactive web-stack scaffolder | Plain Go: nothing canonical. go-blueprint: framework + DB + features (htmx, Docker, GitHub Actions, React frontend). gonew: any module published as a template (e.g. Google Cloud's templates) | Plain: go.mod, go.sum. go-blueprint: framework wired in, DB driver, optional Docker / CI / frontend. gonew: whatever the source module ships | ❌ No generators in any of them | Go files, no central config | The core team ships only templates (gonew); the project layout debate gave the community room to fill the gap |
| Python with uv | uv init myapp | --app, --lib, --package, --bare | pyproject.toml, virtualenv on first run, lockfile, hello.py (or library structure) | ❌ No app generators (uv is package-focused) | pyproject.toml | Variant-driven init; minimal opinions beyond packaging |
| Projen (meta-scaffolder, any stack) | npx projen new <template> (e.g. awscdk-app-ts, python, nextjs-ts) or define your own in .projenrc.ts | Built-in templates for TS, Python, Java, Go, AWS CDK, Lambdas; or author your own blueprints for any stack | Whatever you put in .projenrc.ts — package config, build, lint, CI, docs, monorepo wiring, all regenerated from one source | ✅ Regeneration is the generator: edit .projenrc.ts, run pnpm projen, files update across every package | .projenrc.ts (TypeScript) | Scaffolding as code — transcends individual stacks, encodes your patterns instead of a framework's |
The exception in the matrix: Projen
Projen sits above the stacks. You write a
.projenrc.ts file describing what your project should look like, and Projen regenerates the surface — package.json, tsconfig, ESLint config, GitHub Actions — every time you run pnpm projen.Projen is to scaffolding what Infrastructure as Code was to provisioning servers. Before IaC, you stood up servers by clicking through a console or running ad-hoc shell scripts. After IaC, your infrastructure was a versioned file you could review, diff, and regenerate. Before Projen, your scaffolding was whatever
rails new left behind plus whatever you hand-edited on top. After Projen, your scaffolding is a versioned TypeScript file you can review, diff, and regenerate. Same shift, different problem.Two consequences:
Projen blueprints transcend stacks. A blueprint can stand up a TypeScript Lambda, a Python data pipeline, a CDK app, and a Next.js frontend in one repo, all configured consistently, all regenerable from the same source. The matrix shows 14 framework-specific scaffolders. Projen lets you build the 15th: the one that encodes your patterns.
It's the natural next step if you're scaffolding with AI tools. When the AI sees
.projenrc.ts, it reads an explicit description of what the project should look like. The more your scaffolding is itself code, the more determinism you get from AI tools — which is the argument behind coding your own scaffolding first.For the technical detail on Projen with PDK and Nx for polyglot monorepos, the Polyglot monorepo build and maintenance automation deep dive is the level-300 version of this section.
Some Patterns
Use flag-form scaffolders with AI assistants, not interactive prompts. Whether a scaffolder defaults to prompts (
create-next-app, sv create) or skips them (rails new, mix phx.new), nearly all of them accept a flag form. Point your AI at it:npx create-next-app@latest myapp --ts \
--app --tailwind --src-dir \
--import-alias "@/*" --use-pnpm --yes
One deterministic command. The disk now shows the assistant exactly what you wanted, which is a kind of chain-of-thought prompting — the structure on disk feeds every prompt that follows.
Rust and Go take a "kit, not framework" approach to scaffolding. The Rust core team ships
cargo new. The Go team ships go mod init and gonew, an officially-published template copier under golang.org/x/tools. Both core teams stop at the project skeleton and trust the community to fill in framework-shaped scaffolders on top. That's where Loco lives in Rust — Rails-style cargo loco generate model, full ORM, jobs, mailers — and where go-blueprint lives in Go, with Gin-or-Fiber-or-Chi plus DB driver plus Docker plus optional React frontend. Composition over framework-imposed structure, with opt-in scaffolders for the people who want them.Scaffold first, then prompt
If you came for the right command, the second column is what you wanted. Bookmark it.
The reason any of this matters more than it used to is AI assistants. An assistant in an empty directory has to invent a project structure. An assistant in a scaffolded directory reads every config file, every directory name, every wired dependency off the disk. The scaffold is 80% of your prompt.
So scaffold deterministically before you let AI write features. Pass the flags. Pin the versions. When you find yourself running the same
create-next-app invocation by hand for every new project, code your scaffolding first — capture it, version it, regenerate it as defaults drift.© 2026 Archie Cowan
