Writing Documentation Developers Actually Read

Reverend Philip Nov 27, 2025 9 min read

Create documentation that helps rather than hinders. Learn structure, writing style, and tools that make docs maintainable and useful.

Good documentation is force multiplier. It reduces onboarding time, decreases support burden, and enables developers to be productive without constant hand-holding. Yet most documentation fails to achieve these goals. This guide covers how to write documentation that developers actually read and find useful.

Why Documentation Fails

Common failure modes:

  • Out of date: Docs written once, never maintained
  • Too abstract: Concepts without concrete examples
  • Missing context: Assumes too much prior knowledge
  • Poor discoverability: Can't find what you need
  • Wrong audience: Written for experts, used by beginners

Documentation Types

Different purposes need different documentation:

Tutorials

Learning-oriented, lessons that take the reader through a series of steps to complete a project.

Characteristics:

  • Learning by doing
  • Gets the user started
  • Shows concrete steps
  • Inspires confidence

Tutorials should set clear expectations upfront - what the reader will build, what they need to know beforehand, and how long it will take. Here's how to structure the opening of a tutorial.

# Build Your First API

By the end of this tutorial, you'll have a working REST API
that creates, reads, updates, and deletes todo items.

## Prerequisites
- Node.js 18+
- Basic JavaScript knowledge

## Step 1: Initialize the Project
...

Notice how this immediately tells readers what they'll accomplish and what they need. This helps them decide if the tutorial is right for them before investing time.

How-To Guides

Goal-oriented, solve a specific problem.

Characteristics:

  • Assumes basic knowledge
  • Focuses on completing a task
  • Provides practical steps
  • Addresses a specific need

Unlike tutorials, how-to guides assume the reader already has a working project and wants to add a specific capability. The structure reflects this - jump straight into requirements and steps.

# How to Add Authentication

This guide shows how to add JWT authentication to your existing API.

## Requirements
- An existing Express API
- A user database table

## Steps

### 1. Install Dependencies
```bash
npm install jsonwebtoken bcrypt

2. Create Auth Middleware

...


The key difference from tutorials: you're solving a specific problem, not teaching from scratch. Readers arrive with context and want to get straight to the solution.

### Reference

Information-oriented, accurate and complete.

**Characteristics:**
- Technical descriptions
- Structured consistently
- Comprehensive coverage
- Dry but precise

Reference documentation should follow a consistent format for every entry. Readers learn the pattern once and can quickly find what they need. Here's a template for configuration options.

```markdown
# Configuration Options

## `database.connection`

Type: `string`
Default: `'mysql'`
Environment variable: `DB_CONNECTION`

The database driver to use. Supported values:
- `mysql` - MySQL/MariaDB
- `pgsql` - PostgreSQL
- `sqlite` - SQLite

Consistency matters more than style in reference docs. Pick a format and stick to it across every page.

Explanation

Understanding-oriented, provides context and background.

Characteristics:

  • Clarifies concepts
  • Provides background
  • Discusses alternatives
  • Explains reasoning

Explanation documents help readers understand "why" rather than "how." They're the right place for architecture decisions, trade-off discussions, and conceptual foundations.

# Why We Use Event Sourcing

Event sourcing stores state changes as a sequence of events
rather than current state...

## Traditional vs Event Sourcing

In a traditional system, when a user updates their profile...

## When Event Sourcing Makes Sense

Consider event sourcing when you need:
- Complete audit trail
- Temporal queries
- Complex domain logic

These documents age well because they explain reasoning that remains valid even as implementation details change.

Writing Principles

Start With the User's Goal

Don't describe what something is;describe what it helps the user do.

The first example below describes the class; the second describes what the reader can accomplish. Always lead with the benefit to the user.

# Bad
## The Cache Class
The Cache class provides methods for storing and retrieving
cached data using various drivers.

# Good
## Caching
Speed up your application by caching expensive operations
and frequently accessed data.

Show, Don't Just Tell

Every concept needs a concrete example:

Telling readers they "can" do something isn't enough - show them exactly how. Code examples transform abstract capabilities into actionable knowledge.

# Bad
Use the `retry` option to automatically retry failed requests.

# Good
Use the `retry` option to automatically retry failed requests:

```php
$response = Http::retry(3, 100)->get($url);
// Retries up to 3 times with 100ms delay between attempts

### Use Progressive Disclosure

Start simple, add complexity gradually:

Most readers need the basic case. Put that first. Advanced users will scroll down; beginners won't be overwhelmed by complexity they don't need yet.

```markdown
## Quick Start

```php
Cache::put('key', 'value', 60);
$value = Cache::get('key');

Configuration

For most applications, the default configuration works well. When you need to customize caching behavior...

Advanced: Custom Cache Drivers

When the built-in drivers don't meet your needs...


This structure serves both beginners (who can stop at Quick Start) and experts (who can jump to Advanced).

### Write for Scanning

Developers scan before they read. Help them:

- **Clear headings**: Make structure obvious
- **Code first**: Put examples before explanations
- **Bullet points**: For lists and options
- **Bold key terms**: Highlight important concepts

### Include Error Messages

Document what goes wrong and how to fix it:

Error documentation is often more valuable than feature documentation. When someone hits an error, they're blocked and searching for answers. Be there with the solution.

```markdown
## Troubleshooting

### "Connection refused" error

This usually means Redis isn't running.

**Solution:**
```bash
redis-server --daemonize yes

"WRONGTYPE Operation" error

You're using a string command on a hash key.

Solution: Check your key names. You may have a collision.


Include the exact error message text - this is what developers will paste into their search engine. Matching that text makes your docs discoverable.

## Structure and Organization

### Consistent Navigation

A predictable folder structure helps readers build a mental model of where to find information. Group related content and use descriptive folder names.

docs/ ├── getting-started/ │ ├── installation.md │ ├── configuration.md │ └── quickstart.md ├── guides/ │ ├── authentication.md │ ├── caching.md │ └── testing.md ├── api-reference/ │ ├── cache.md │ ├── database.md │ └── http.md └── concepts/ ├── architecture.md └── security.md


### Landing Pages

Help users navigate to what they need:

Landing pages act as signposts. They don't contain detailed content themselves - they help readers find the right document for their current need.

```markdown
# Documentation

## Getting Started
New here? Start with these guides.
- [Installation](./installation.md) - Get up and running
- [Quick Start](./quickstart.md) - Build something in 5 minutes

## Guides
Step-by-step guides for common tasks.
- [Authentication](./guides/auth.md)
- [Deploying to Production](./guides/deploy.md)

## API Reference
Complete reference for all methods and options.
- [Cache API](./api/cache.md)
- [Database API](./api/database.md)

Brief descriptions after each link help readers choose without clicking through multiple pages.

Version Awareness

When you maintain multiple versions, make it obvious which version the reader is viewing. Call out breaking changes prominently.

> **Note**: This documentation is for version 3.x.
> For version 2.x, see [legacy docs](./v2/).

## Breaking Changes in 3.0

The `config()` helper now returns `null` instead of throwing...

Code Examples

Make Examples Complete

Incomplete examples frustrate readers. They have to guess at imports, variable setup, and context. Provide everything needed to actually run the code.

# Bad - Missing context
```php
$user->notify(new InvoicePaid($invoice));

Good - Complete, runnable example

use App\Models\User;
use App\Notifications\InvoicePaid;
use App\Models\Invoice;

$user = User::find(1);
$invoice = Invoice::find(42);

$user->notify(new InvoicePaid($invoice));

### Show Input and Output

When demonstrating functions or methods, show both what goes in and what comes out. This helps readers verify they understand correctly.

```markdown
```php
$collection = collect([1, 2, 3, 4, 5]);
$filtered = $collection->filter(fn($value) => $value > 2);

$filtered->all();
// [3, 4, 5]

### Use Realistic Data

Abstract variable names like `$foo` and `$bar` force readers to do mental translation. Realistic examples communicate intent alongside mechanics.

```markdown
# Bad - Meaningless
$result = process($foo, $bar);

# Good - Realistic
$order = Order::create([
    'customer_id' => $customer->id,
    'total' => 99.99,
    'status' => 'pending',
]);

Domain-specific examples also help readers understand how the code fits into real applications.

Maintaining Documentation

Docs as Code

Treat documentation like code:

  • Store in version control
  • Review changes in PRs
  • Run linting and link checking
  • Deploy automatically

Automating your documentation workflow catches broken links, validates formatting, and ensures docs deploy alongside code changes.

# .github/workflows/docs.yml
name: Documentation
on:
  push:
    branches: [main]
    paths: ['docs/**']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run docs:build
      - run: npm run docs:check-links

The paths filter ensures the workflow only runs when documentation actually changes, saving CI minutes.

Keep Docs Close to Code

Co-locating documentation with the code it describes increases the chance it gets updated when the code changes. Developers are more likely to update a README in the same directory than navigate to a separate docs folder.

src/
├── auth/
│   ├── AuthManager.php
│   └── README.md        # Auth module docs
├── cache/
│   ├── CacheManager.php
│   └── README.md        # Cache module docs

Automate What You Can

  • Generate API reference from code comments
  • Extract examples from tests
  • Validate code examples actually run

PHPDoc comments can generate reference documentation automatically. The example below shows how to document a method so tooling can extract both the API signature and usage examples.

/**
 * Store an item in the cache.
 *
 * @param string $key
 * @param mixed $value
 * @param int $ttl Time to live in seconds
 * @return bool
 *
 * @example
 * Cache::put('user:1', $user, 3600);
 */
public function put(string $key, mixed $value, int $ttl): bool

When documentation is generated from code, it stays in sync automatically.

Measuring Documentation Quality

Analytics

Track what people search for and don't find:

  • Search queries with no results
  • Pages with high bounce rates
  • Time on page

Feedback Loops

Give readers an easy way to signal when documentation helped or fell short. Even simple binary feedback reveals problem areas.

---
Was this page helpful?
[Yes](#) | [No](#) | [Report an issue](#)
---

Documentation Tests

You can write tests that verify your documentation examples actually work. This catches staleness when APIs change and examples break.

// Test that documentation examples work
public function test_cache_example_from_docs()
{
    Cache::put('key', 'value', 60);
    $this->assertEquals('value', Cache::get('key'));
}

These tests serve double duty - they validate docs and serve as additional integration tests for your codebase.

Conclusion

Good documentation is written for a specific audience with a specific goal. Choose the right type (tutorial, how-to, reference, explanation), show concrete examples, structure for scanning, and maintain docs like code. The investment pays off in reduced support burden and faster onboarding.

Share this article

Related Articles

Distributed Locking Patterns

Coordinate access to shared resources across services. Implement distributed locks with Redis, ZooKeeper, and databases.

Jan 16, 2026

API Design First Development

Design APIs before implementing them. Use OpenAPI specifications, mock servers, and contract-first workflows.

Jan 15, 2026

Need help with your project?

Let's discuss how we can help you build reliable software.