How to Build a Claude-Powered Code Documentation Generator
Building a Claude-powered code documentation generator means feeding your source files to Claude through its API with a prompt that extracts function signatures, explains behavior, and outputs structured docs (Markdown, JSDoc, docstrings, or OpenAPI specs). The core pipeline is: read files → chunk them sensibly → send each chunk to Claude with a documentation-focused prompt → parse the response → write it back into your repo or a docs site.
This is a practical, high-leverage use case because documentation is tedious to write by hand and goes stale fast, but it's also mechanical enough that an LLM with good context handling does it reliably. Below is a full walkthrough: architecture, prompt design, code, and how to run it at scale without burning through rate limits.
Why this works well with Claude
Claude is strong at this task because it:
- Handles long context windows, so you can pass entire files or modules instead of isolated functions.
- Follows structured output instructions closely (Markdown tables, JSON, specific docstring formats).
- Reasons about code behavior, not just syntax, so it can describe what a function does, not just its signature.
The main engineering work isn't prompting Claude — it's building the pipeline around it: file discovery, chunking, rate limiting, and output formatting.
Step 1: Decide your output format first
Before writing any code, pick the target format because it changes your prompt:
- Markdown docs for a
/docsfolder or static site (Docusaurus, MkDocs) - Inline docstrings/JSDoc written back into source files
- OpenAPI/JSON Schema for API endpoints
- README summaries per module or package
Mixing formats in one pass leads to inconsistent output. Run separate passes if you need both inline comments and a standalone docs site.
Step 2: Walk the repo and chunk files
Don't send your entire codebase in one request. Chunk by file or by logical unit (class, module) and keep chunks under a size that leaves room for a detailed response.
import { readFileSync, readdirSync, statSync } from "fs";
import path from "path";
function collectFiles(dir, exts = [".js", ".ts", ".py"]) {
let files = [];
for (const entry of readdirSync(dir)) {
const full = path.join(dir, entry);
if (statSync(full).isDirectory()) {
if (!entry.startsWith("node_modules") && !entry.startsWith(".git")) {
files = files.concat(collectFiles(full, exts));
}
} else if (exts.includes(path.extname(entry))) {
files.push(full);
}
}
return files;
}
For large files, split by top-level function or class boundaries rather than arbitrary line counts — this keeps each chunk semantically complete, which matters a lot for accurate documentation.
Step 3: Design the prompt
Be explicit about format, tone, and what to skip. A vague prompt like "document this code" produces inconsistent results across files.
function buildPrompt(filename, code) {
return `You are generating documentation for a source file.
File: ${filename}
Rules:
- Output valid Markdown only.
- For each exported function/class, include: name, one-line summary, parameters with types, return value, and a short usage example.
- Skip private/internal helpers unless they're exported.
- Do not restate obvious code; explain intent and edge cases.
- If the file has no exports, say "No public API in this file."
Source code:
\`\`\`
${code}
\`\`\`
`;
}
Locking down the output structure like this makes downstream parsing trivial and keeps results consistent whether the file is 20 lines or 400.
Step 4: Call the API
Here's a working example using SubToAPI, which exposes Claude through a standard HTTPS API with an application key (sub_live_...) instead of requiring a direct Anthropic subscription per environment:
async function generateDocs(filename, code) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1500,
messages: [
{ role: "user", content: buildPrompt(filename, code) }
]
})
});
const data = await res.json();
return data.content[0].text;
}
Because SubToAPI returns standard usage metadata with each response, you can log token counts per file and track generation cost across a full repo run without instrumenting that yourself. See /docs/messages for the full request/response shape.
Step 5: Run it across the repo and write output
import { writeFileSync, mkdirSync } from "fs";
async function run() {
const files = collectFiles("./src");
mkdirSync("./docs/generated", { recursive: true });
for (const file of files) {
const code = readFileSync(file, "utf8");
if (code.trim().length === 0) continue;
const markdown = await generateDocs(file, code);
const outPath = `./docs/generated/${path.basename(file)}.md`;
writeFileSync(outPath, markdown);
console.log(`Documented: ${file}`);
await new Promise(r => setTimeout(r, 300)); // simple pacing
}
}
run();
For large codebases, add concurrency control (a small worker pool of 3–5 parallel requests) rather than one request at a time, and add retry logic for transient failures. If you expect heavy, repeated runs — say, regenerating docs on every merge to main — batching requests and streaming responses for large files will keep latency manageable; see /docs/streaming for details.
Step 6: Wire it into CI
Once the script works locally, run it as a CI step on pull requests that touch /src, and fail the build (or just comment) if generated docs drift significantly from committed ones. This keeps documentation from silently going stale, which is the whole point of automating it in the first place.
- name: Generate docs
run: node scripts/generate-docs.js
env:
SUBTOAPI_KEY: ${{ secrets.SUBTOAPI_KEY }}
Handling inline docstrings instead of standalone files
If you want Claude to write JSDoc/docstrings directly into the source rather than separate Markdown files, change the prompt to request the full file back with comments inserted, and instruct Claude not to alter any logic — only add comments. Diff the output against the original before committing, since even small instruction drift can cause unwanted code changes.
Getting started
If you're prototyping this, you can get a key and test the pipeline against a few files in minutes — check /docs/quickstart for setup, and /pricing if you want to compare the Solo plan (good for personal projects or small repos) against Team/Scale plans for CI pipelines running across a whole engineering org.
questions
Does Claude need the whole repo in context to write good docs? No. Most documentation generators work file-by-file or module-by-module. Full-repo context only helps for cross-file relationship docs (like architecture overviews), which is a separate, smaller pass.
How do I keep generated docs from going stale? Run the generator in CI on every PR that touches source files, and diff the output against committed docs so stale documentation shows up as a failing check rather than silently drifting.
Can this generate OpenAPI specs instead of Markdown? Yes — change the prompt to request valid OpenAPI JSON/YAML describing routes, parameters, and responses instead of Markdown, and validate the output with a schema validator before committing it, since strict spec compliance matters more there than in freeform docs.