Making Your Documentation AI-Friendly: Serving Markdown to AI Coding Assistants

AI, News, and Tips & Tricks

Making Your Documentation AI-Friendly: Serving Markdown to AI Coding Assistants

If you've been following developer trends recently, you might have seen discussions about serving Markdown directly to AI coding assistants like Claude Code, Cursor, and GitHub Copilot. Companies like Bun, Mintlify, and Fumadocs have already implemented this feature in their documentation platforms.

Here's why it matters for your deployment documentation and how to implement it yourself with Laravel.

The Problem: HTML Bloat

AI agents can fetch the content of web pages to help developers. Most of the time, this content comes back as HTML. But HTML contains markup that consumes tokens without adding meaningful information, degrading performance.

For example, a simple documentation page about deployment commands might look like this in HTML:

<div class="documentation-wrapper">
  <header class="docs-header">...</header>
  <nav class="sidebar">...</nav>
  <main class="content">
    <h1 class="heading-primary">Deployment Commands</h1>
    <p class="text-body">To deploy your application, use the following command:</p>
    <pre class="code-block"><code>deployhq deploy production</code></pre>
  </main>
  <footer class="docs-footer">...</footer>
</div>

The same content in Markdown is much cleaner:

# Deployment Commands

To deploy your application, use the following command:

deployhq deploy production

The Solution: Content Negotiation

The elegant solution is content negotiation. This is a standard HTTP mechanism where clients tell servers what format they prefer using the Accept header.

When an AI agent requests your documentation with Accept: text/markdown or Accept: text/plain, your server can respond with Markdown instead of HTML.

This means:

  • Fewer tokens for the same information
  • Easier parsing and comprehension
  • More documentation fits in the context window

Implementation Guide for Laravel

Let's walk through implementing this feature in a Laravel application. The exact details depend on your setup, but the general approach remains the same.

1. Detect the Accept Header

When a request arrives, check if the Accept header contains text/markdown or text/plain:

// routes/web.php
Route::get('/docs/{path?}', function (Request $request, $path = 'index') {
    $acceptHeader = $request->header('Accept', '');

    if (str_contains($acceptHeader, 'text/markdown')) {
        // Serve Markdown
    } elseif (str_contains($acceptHeader, 'text/plain')) {
        // Serve plain text
    } else {
        // Serve HTML (default behavior)
    }
})->where('path', '.*');

2. Serve the Raw Markdown

Load and return the raw Markdown content for the requested page:

Route::get('/docs/{path?}', function (Request $request, $path = 'index') {
    $acceptHeader = $request->header('Accept', '');

    if (str_contains($acceptHeader, 'text/markdown')) {
        $markdownContent = loadMarkdownForPage($path);

        return response($markdownContent)
            ->header('Content-Type', 'text/markdown; charset=utf-8');
    }

    // ... rest of implementation
})->where('path', '.*');

3. Create a Controller for Better Organization

For a cleaner approach, create a dedicated controller:

// app/Http/Controllers/DocsController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;

class DocsController extends Controller
{
    public function show(Request $request, $path = 'index')
    {
        $acceptHeader = $request->header('Accept', '');
        $markdownContent = $this->loadMarkdown($path);

        if (!$markdownContent) {
            abort(404, 'Documentation not found');
        }

        if (str_contains($acceptHeader, 'text/markdown')) {
            return response($markdownContent)
                ->header('Content-Type', 'text/markdown; charset=utf-8');
        }

        if (str_contains($acceptHeader, 'text/plain')) {
            return response($markdownContent)
                ->header('Content-Type', 'text/plain; charset=utf-8');
        }

        // Default: Serve HTML
        return view('docs.show', [
            'content' => Str::markdown($markdownContent)
        ]);
    }

    private function loadMarkdown($path)
    {
        // Sanitize path to prevent directory traversal
        $cleanPath = str_replace(['../', './'], '', $path);
        $filePath = resource_path("docs/{$cleanPath}.md");

        if (!File::exists($filePath)) {
            return null;
        }

        return File::get($filePath);
    }
}

4. Configure Your Routes

// routes/web.php
use App\Http\Controllers\DocsController;

Route::get('/docs/{path?}', [DocsController::class, 'show'])
    ->where('path', '.*')
    ->name('docs.show');

Testing Your Implementation

Use curl to verify your implementation works correctly:

# Request Markdown
curl -H 'Accept: text/markdown' https://deployhq.com/guides/laravel

# Request plain text
curl -H 'Accept: text/plain' https://deployhq.com/guides/laravel

# Request HTML (default)
curl -H 'Accept: text/html' https://deployhq.com/guides/laravel

For Markdown requests, you should see:

  • Response header: Content-Type: text/markdown; charset=utf-8
  • Body: Raw Markdown content without HTML tags

For HTML requests, you should see your normal rendered page.

Adding Caching for Better Performance

To improve performance, cache both Markdown and HTML versions:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;

class DocsController extends Controller
{
    public function show(Request $request, $path = 'index')
    {
        $acceptHeader = $request->header('Accept', '');
        $contentType = $this->getContentType($acceptHeader);

        $cacheKey = "docs.{$path}.{$contentType}";

        $content = Cache::remember($cacheKey, 3600, function () use ($path, $contentType) {
            $markdown = $this->loadMarkdown($path);

            if (!$markdown) {
                return null;
            }

            return $contentType === 'html' 
                ? Str::markdown($markdown) 
                : $markdown;
        });

        if (!$content) {
            abort(404, 'Documentation not found');
        }

        if ($contentType === 'markdown') {
            return response($content)
                ->header('Content-Type', 'text/markdown; charset=utf-8');
        }

        if ($contentType === 'text') {
            return response($content)
                ->header('Content-Type', 'text/plain; charset=utf-8');
        }

        return view('docs.show', ['content' => $content]);
    }

    private function getContentType($acceptHeader)
    {
        if (str_contains($acceptHeader, 'text/markdown')) {
            return 'markdown';
        }

        if (str_contains($acceptHeader, 'text/plain')) {
            return 'text';
        }

        return 'html';
    }

    private function loadMarkdown($path)
    {
        $cleanPath = str_replace(['../', './'], '', $path);
        $filePath = resource_path("docs/{$cleanPath}.md");

        if (!File::exists($filePath)) {
            return null;
        }

        return File::get($filePath);
    }
}

Why This Matters for DeployHQ Users

When developers use AI coding assistants like Claude Code or Cursor to help with deployment automation, the AI can now:

  • Fetch your deployment documentation more efficiently
  • Access more comprehensive guides without hitting token limits
  • Better understand complex deployment pipelines
  • Provide more accurate assistance with DeployHQ integrations

Whether you're documenting deployment workflows, API endpoints, or configuration options, this simple optimization makes your documentation more accessible to the AI tools developers use every day.

Further Reading and Resources

To learn more about implementing this feature:


Want to automate your deployments? Learn more about DeployHQ and how we help teams deploy faster and more reliably.

A little bit about the author

Facundo | CTO | DeployHQ | Continuous Delivery & Software Engineering Leadership - As CTO at DeployHQ, Facundo leads the software engineering team, driving innovation in continuous delivery. Outside of work, he enjoys cycling and nature, accompanied by Bono 🐶.