# How To Generate a OpenAPI for Laravel
Source: https://speakeasy.com/openapi/frameworks/laravel

import { Callout, YouTube } from "@/mdx/components";



<YouTube videoId="dn5U_pidRw0" />

You're investing in your API, and that means finally creating an OpenAPI document that accurately describes your API. With the rise in popularity of API-first design some APIs might have declared their OpenAPI before writing the code, but for many the code-first workflow is still fundamental for older APIs. If you're working with an existing Laravel application, you can generate a complete OpenAPI document directly from the API's source code.

A few excellent tools have come and gone over the years, but these days [Scribe](https://scribe.knuckles.wtf/laravel) is the go to for generating API documentation form Laravel source code, and it happily exports OpenAPI to be used in a variety of other tools: like Speakeasy.

## What is Scribe all about

Scribe is a robust documentation solution for PHP APIs. It helps you generate comprehensive human-readable documentation from your Laravel/Lumen/Dingo codebase, without needing to add docblocks or annotations for **everything** like other tools have required in the past. 

Scribe introspects the API source code itself, and without AI fudging the results it will accurately turn routing, controllers, Eloquent models, and all sorts of code into the best and most accurate API descriptions possible. Then it can be exported as OpenAPI, or Postman collections (if you're into that sort of thing.)

The first step is to install a package, and explore the options available.

```bash
composer require --dev knuckleswtf/scribe
```

Once installed, publish the package configuration to access the full variety of config options.

```bash
php artisan vendor:publish --tag=scribe-config
```

There are a lot of [config options](https://scribe.knuckles.wtf/laravel/reference/config) available, and we'll look at some good ones later. For now let's see what a basic generation looks like.

```bash
php artisan scribe:generate
```

The command above will generate both HTML documentation and an OpenAPI specification file. By default, the OpenAPI document will be saved in `storage/app/private/scribe/openapi.yaml`, but the command will let you know exactly where it's stored.

```yaml
openapi: 3.0.3
info:
  title: 'Laravel API Documentation'
  description: ''
  version: 1.0.0
servers:
  - url: 'http://localhost'
tags:
  - name: Endpoints
    description: ''
paths:
  /api/health:
    get:
      summary: ''
      operationId: getApiHealth
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  version:
                    type: string
                  timestamp:
                    type: string
      tags:
        - Endpoints
      security: []
  /api/drivers:
    get:
      summary: 'Display a listing of the resource.'
      operationId: displayAListingOfTheResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        code:
                          type: string
                        created_at:
                          type: string
                        updated_at:
                          type: string
                  meta:
                    type: object
                    properties:
                      count:
                        type: integer
      tags:
        - Endpoints
      security: []
  '/api/drivers/{id}':
    get:
      summary: 'Display the specified resource.'
      operationId: displayTheSpecifiedResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      code:
                        type: string
                      created_at:
                        type: string
                      updated_at:
                        type: string
      tags:
        - Endpoints
      security: []
    parameters:
      -
        in: path
        name: id
        description: 'The ID of the driver.'
        required: true
        schema:
          type: integer
  /api/circuits:
    get:
      summary: 'Display a listing of the resource.'
      operationId: displayAListingOfTheResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        location:
                          type: string
                        created_at:
                          type: string
                        updated_at:
                          type: string
                  meta:
                    type: object
                    properties:
                      count:
                        type: integer
      tags:
        - Endpoints
      security: []
    post:
      summary: 'Store a newly created resource in storage.'
      operationId: storeANewlyCreatedResourceInStorage
      description: ''
      responses: {  }
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: ''
                  nullable: false
                location:
                  type: string
                  description: ''
                  nullable: false
              required:
                - name
                - location
      security: []
  '/api/circuits/{id}':
    get:
      summary: 'Display the specified resource.'
      operationId: displayTheSpecifiedResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      location:
                        type: string
                      created_at:
                        type: string
                      updated_at:
                        type: string
      tags:
        - Endpoints
      security: []
    parameters:
      -
        in: path
        name: id
        description: 'The ID of the circuit.'
        required: true
        schema:
          type: integer
  /api/races:
    get:
      summary: 'Display a listing of the resource.'
      operationId: displayAListingOfTheResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        race_date:
                          type: string
                        season:
                          type: string
                        created_at:
                          type: string
                        updated_at:
                          type: string
                        links:
                          type: object
                          properties:
                            self:
                              type: string
                            circuit:
                              type: string
                            drivers:
                              type: string
                  meta:
                    type: object
                    properties:
                      count:
                        type: integer
      tags:
        - Endpoints
      security: []
  '/api/races/{id}':
    get:
      summary: 'Display the specified resource.'
      operationId: displayTheSpecifiedResource
      description: ''
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      race_date:
                        type: string
                      season:
                        type: string
                      created_at:
                        type: string
                      updated_at:
                        type: string
                      links:
                        type: object
                        properties:
                          self:
                            type: string
                          circuit:
                            type: string
                          drivers:
                            type: string
      tags:
        - Endpoints
      security: []
    parameters:
      -
        in: path
        name: id
        description: 'The ID of the race.'
        example: 1
        required: true
        schema:
          type: integer
```

A surprisingly good start for something that's had absolutely no work done on it. Beyond just outputting endpoints and models, Scribe was able to look through the [API resources](https://laravel.com/docs/12.x/eloquent-resources) (also known as serializers) to figure out what the response payloads would look like, and describe them as [OpenAPI Schema objects](https://learn.openapis.org/specification/content.html#the-schema-object).

Examples were also generated based on the data in the database. This is a great touch at providing some realism immediately, but it made the above example too big to share. The examples generated are based on Laravel's [database seeders](https://laravel.com/docs/12.x/seeding), so they should be more realistic than most hand-written examples - unless the seeds are creating bad or outdated data. Here's how the examples were generated.

```yaml
  '/api/drivers/{id}':
    get:
      summary: 'Display the specified resource.'
      operationId: displayTheSpecifiedResource
      description: ''
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  data:
                    id: 1
                    name: 'Max Verstappen'
                    code: VER
                    created_at: '2025-10-29T17:21:39.000000Z'
                    updated_at: '2025-10-29T17:21:39.000000Z'
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                        example: 1
                      name:
                        type: string
                        example: 'Max Verstappen'
                      code:
                        type: string
                        example: VER
                      created_at:
                        type: string
                        example: '2025-10-29T17:21:39.000000Z'
                      updated_at:
                        type: string
                        example: '2025-10-29T17:21:39.000000Z'
```

However, there are some shortcomings to this auto-generated output.

OpenAPI is used for a lot of different purposes, but in order to use it for API documentation it needs to have useful descriptions that provide context to the raw data. Currently the API missing a lot of the "why" and "how" in this sea of "what", and needs more human input.

Additionally, the descriptions and summaries are all the same generic content pulled from some template, and having `summary: 'Display a listing of the resource.'` for each operation (which in turn is giving poor `operationId`) is not only going to be confusing for users, it will produce a bad SDK in Speakeasy.

Let's look at some ways we can improve this output with quick config settings, and then by adding some attributes to the controllers to improve things further.

## Configuring Scribe

Open the `config/scribe.php` file that was published earlier, and look for the following options:

```php
  // The HTML <title> for the generated documentation.
  'title' => 'F1 Race API',

  // A short description of your API. Will be included in the docs webpage, Postman collection and OpenAPI spec.
  'description' => '',

  // Text to place in the "Introduction" section, right after the `description`. Markdown and HTML are supported.
  'intro_text' => <<<INTRO
      This documentation aims to provide all the information you need to work with our API.

      <aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
      You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
  INTRO,
```

Updating these options will help give some context to the API consumers, but to get the rest of the API covered Scribe will need some extra context spread around the codebase.

## Creating summaries and descriptions

Scribe scans application routes to identify which endpoints should be described, then extracts metadata from the corresponding routes, such as route names, URI patterns, HTTP methods. It can do this by looking purely at the code, but extra information can be added using annotations and comments in the controller to expand on the "why" and "how" of the API.

In order to provide that context, Scribe looks at "docblock" comments (`/**`) on the controller methods. 

Take a look at the `HealthController` because that has no descriptions or summary so far.

```php focus=10:16