-
-
Notifications
You must be signed in to change notification settings - Fork 413
Examples
This page provides comprehensive examples for documenting your Laravel API using OpenAPI 3 with PHP 8 Attributes.
Note: All examples use PHP 8 Attributes syntax. If you're using L5-Swagger v9.x or earlier with DocBlock annotations, see the Migration Guide.
- Basic Setup
- API Information
- Routes/Endpoints
- Parameters
- Request Bodies
- Responses
- Schemas/Models
- Security
- Advanced Examples
First, ensure you have the OpenAPI Attributes namespace imported at the top of your files:
<?php
namespace App\Http\Controllers;
use OpenApi\Attributes as OA;Define your API's basic information. This typically goes in your base Controller.php or a dedicated info class:
<?php
namespace App\Http\Controllers;
use OpenApi\Attributes as OA;
#[OA\Info(
version: "1.0.0",
title: "My Application API",
description: "API documentation for My Application",
contact: new OA\Contact(
name: "API Support",
email: "[email protected]",
url: "https://example.com/support"
),
license: new OA\License(
name: "Apache 2.0",
url: "https://www.apache.org/licenses/LICENSE-2.0.html"
)
)]
#[OA\Server(
url: "http://localhost:8000",
description: "Local development server"
)]
#[OA\Server(
url: "https://api.example.com",
description: "Production server"
)]
class Controller
{
// Your controller code
}Simple GET endpoint returning a list:
#[OA\Get(
path: "/api/users",
summary: "Get list of users",
description: "Returns a paginated list of all users",
tags: ["Users"],
responses: [
new OA\Response(
response: 200,
description: "Successful operation",
content: new OA\JsonContent(
type: "array",
items: new OA\Items(ref: "#/components/schemas/User")
)
),
new OA\Response(
response: 401,
description: "Unauthenticated"
)
]
)]
public function index()
{
// Your code
}GET endpoint with path parameter:
#[OA\Get(
path: "/api/users/{id}",
summary: "Get user by ID",
description: "Returns a single user",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
description: "User ID",
in: "path",
required: true,
schema: new OA\Schema(type: "integer")
)
],
responses: [
new OA\Response(
response: 200,
description: "Successful operation",
content: new OA\JsonContent(ref: "#/components/schemas/User")
),
new OA\Response(
response: 404,
description: "User not found"
)
]
)]
public function show($id)
{
// Your code
}Create a new resource:
#[OA\Post(
path: "/api/users",
summary: "Create a new user",
description: "Creates a new user and returns the created user object",
tags: ["Users"],
requestBody: new OA\RequestBody(
required: true,
description: "User object that needs to be created",
content: new OA\JsonContent(
required: ["name", "email", "password"],
properties: [
new OA\Property(property: "name", type: "string", example: "John Doe"),
new OA\Property(property: "email", type: "string", format: "email", example: "[email protected]"),
new OA\Property(property: "password", type: "string", format: "password", example: "secret123")
]
)
),
responses: [
new OA\Response(
response: 201,
description: "User created successfully",
content: new OA\JsonContent(ref: "#/components/schemas/User")
),
new OA\Response(
response: 422,
description: "Validation error"
)
]
)]
public function store(Request $request)
{
// Your code
}Update an existing resource:
#[OA\Put(
path: "/api/users/{id}",
summary: "Update an existing user",
description: "Updates user data",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
description: "User ID",
in: "path",
required: true,
schema: new OA\Schema(type: "integer")
)
],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(property: "name", type: "string", example: "Jane Doe"),
new OA\Property(property: "email", type: "string", format: "email", example: "[email protected]")
]
)
),
responses: [
new OA\Response(
response: 200,
description: "User updated successfully",
content: new OA\JsonContent(ref: "#/components/schemas/User")
),
new OA\Response(
response: 404,
description: "User not found"
),
new OA\Response(
response: 422,
description: "Validation error"
)
]
)]
public function update(Request $request, $id)
{
// Your code
}Delete a resource:
#[OA\Delete(
path: "/api/users/{id}",
summary: "Delete a user",
description: "Deletes a user by ID",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
description: "User ID",
in: "path",
required: true,
schema: new OA\Schema(type: "integer")
)
],
responses: [
new OA\Response(
response: 204,
description: "User deleted successfully"
),
new OA\Response(
response: 404,
description: "User not found"
)
]
)]
public function destroy($id)
{
// Your code
}Parameters that are part of the URL path:
#[OA\Get(
path: "/api/posts/{postId}/comments/{commentId}",
summary: "Get a specific comment on a post",
tags: ["Comments"],
parameters: [
new OA\Parameter(
name: "postId",
description: "ID of the post",
in: "path",
required: true,
schema: new OA\Schema(type: "integer", format: "int64")
),
new OA\Parameter(
name: "commentId",
description: "ID of the comment",
in: "path",
required: true,
schema: new OA\Schema(type: "integer", format: "int64")
)
],
responses: [
new OA\Response(response: 200, description: "Success")
]
)]
public function getComment($postId, $commentId)
{
// Your code
}Parameters passed in the query string:
#[OA\Get(
path: "/api/users",
summary: "Get users with filtering and pagination",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "page",
description: "Page number",
in: "query",
required: false,
schema: new OA\Schema(type: "integer", default: 1)
),
new OA\Parameter(
name: "per_page",
description: "Items per page",
in: "query",
required: false,
schema: new OA\Schema(type: "integer", default: 15, maximum: 100)
),
new OA\Parameter(
name: "status",
description: "Filter by status",
in: "query",
required: false,
schema: new OA\Schema(type: "string", enum: ["active", "inactive", "pending"])
),
new OA\Parameter(
name: "search",
description: "Search term",
in: "query",
required: false,
schema: new OA\Schema(type: "string")
)
],
responses: [
new OA\Response(response: 200, description: "Success")
]
)]
public function index(Request $request)
{
// Your code
}Custom headers:
#[OA\Get(
path: "/api/users",
summary: "Get users",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "X-Api-Version",
description: "API version",
in: "header",
required: false,
schema: new OA\Schema(type: "string", default: "v1")
),
new OA\Parameter(
name: "Accept-Language",
description: "Preferred language",
in: "header",
required: false,
schema: new OA\Schema(type: "string", enum: ["en", "es", "fr"], default: "en")
)
],
responses: [
new OA\Response(response: 200, description: "Success")
]
)]
public function index(Request $request)
{
// Your code
}#[OA\Post(
path: "/api/posts",
summary: "Create a new post",
tags: ["Posts"],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ["title", "content"],
properties: [
new OA\Property(property: "title", type: "string", example: "My First Post"),
new OA\Property(property: "content", type: "string", example: "This is the post content"),
new OA\Property(property: "published", type: "boolean", example: false)
]
)
),
responses: [
new OA\Response(response: 201, description: "Post created")
]
)]
public function store(Request $request)
{
// Your code
}#[OA\Post(
path: "/api/posts",
summary: "Create a new post",
tags: ["Posts"],
requestBody: new OA\RequestBody(
required: true,
description: "Post object",
content: new OA\JsonContent(ref: "#/components/schemas/PostRequest")
),
responses: [
new OA\Response(response: 201, description: "Post created")
]
)]
public function store(Request $request)
{
// Your code
}#[OA\Post(
path: "/api/users/{id}/avatar",
summary: "Upload user avatar",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
in: "path",
required: true,
schema: new OA\Schema(type: "integer")
)
],
requestBody: new OA\RequestBody(
required: true,
content: new OA\MediaType(
mediaType: "multipart/form-data",
schema: new OA\Schema(
required: ["avatar"],
properties: [
new OA\Property(
property: "avatar",
type: "string",
format: "binary",
description: "Avatar image file"
)
]
)
)
),
responses: [
new OA\Response(response: 200, description: "Avatar uploaded successfully")
]
)]
public function uploadAvatar(Request $request, $id)
{
// Your code
}#[OA\Get(
path: "/api/users/{id}",
summary: "Get user by ID",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
in: "path",
required: true,
examples: [
new OA\Examples(
example: "int",
summary: "Integer ID",
value: 1
),
new OA\Examples(
example: "uuid",
summary: "UUID",
value: "0006faf6-7a61-426c-9034-579f2cfcfa83"
)
],
schema: new OA\Schema(
oneOf: [
new OA\Schema(type: "integer"),
new OA\Schema(type: "string", format: "uuid")
]
)
)
],
responses: [
new OA\Response(
response: 200,
description: "Success",
content: new OA\JsonContent(
examples: [
new OA\Examples(
example: "user",
summary: "User object",
value: [
"id" => 1,
"name" => "John Doe",
"email" => "[email protected]"
]
)
]
)
),
new OA\Response(response: 404, description: "User not found")
]
)]
public function show($id)
{
// Your code
}#[OA\Get(
path: "/api/users/{id}",
summary: "Get user",
tags: ["Users"],
parameters: [
new OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer"))
],
responses: [
new OA\Response(
response: 200,
description: "Success",
content: [
new OA\JsonContent(ref: "#/components/schemas/User"),
new OA\XmlContent(ref: "#/components/schemas/User")
]
)
]
)]
public function show($id)
{
// Your code
}#[OA\Post(
path: "/api/process",
summary: "Process data",
tags: ["Processing"],
requestBody: new OA\RequestBody(
content: new OA\JsonContent(
properties: [
new OA\Property(
property: "phone",
oneOf: [
new OA\Schema(type: "string"),
new OA\Schema(type: "integer")
],
example: "12345678"
)
]
)
),
responses: [
new OA\Response(
response: 200,
description: "Success",
content: new OA\JsonContent(
oneOf: [
new OA\Schema(ref: "#/components/schemas/SuccessResult"),
new OA\Schema(type: "boolean")
],
examples: [
new OA\Examples(
example: "result",
summary: "Result object",
value: ["success" => true]
),
new OA\Examples(
example: "boolean",
summary: "Boolean value",
value: false
)
]
)
)
]
)]
public function process(Request $request)
{
// Your code
}Define reusable schemas for your data models. These are typically defined on your Model classes or dedicated DTO classes:
<?php
namespace App\Models;
use OpenApi\Attributes as OA;
#[OA\Schema(
schema: "User",
title: "User",
description: "User model",
required: ["id", "name", "email"],
properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "name", type: "string", example: "John Doe"),
new OA\Property(property: "email", type: "string", format: "email", example: "[email protected]"),
new OA\Property(property: "created_at", type: "string", format: "date-time", example: "2024-01-01T00:00:00Z"),
new OA\Property(property: "updated_at", type: "string", format: "date-time", example: "2024-01-01T00:00:00Z")
]
)]
class User
{
// Your model code
}#[OA\Schema(
schema: "Post",
required: ["id", "title", "author"],
properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "title", type: "string", example: "My Post"),
new OA\Property(property: "content", type: "string", example: "Post content here"),
new OA\Property(
property: "author",
ref: "#/components/schemas/User",
description: "Post author"
),
new OA\Property(
property: "tags",
type: "array",
items: new OA\Items(type: "string"),
example: ["laravel", "php"]
),
new OA\Property(
property: "comments",
type: "array",
items: new OA\Items(ref: "#/components/schemas/Comment")
)
]
)]
class Post
{
// Your model code
}#[OA\Schema(
schema: "Order",
required: ["id", "status"],
properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(
property: "status",
type: "string",
enum: ["pending", "processing", "completed", "cancelled"],
example: "pending"
),
new OA\Property(property: "total", type: "number", format: "float", example: 99.99)
]
)]
class Order
{
// Your model code
}#[OA\Schema(
schema: "PostRequest",
required: ["title", "content"],
properties: [
new OA\Property(property: "title", type: "string", maxLength: 255, example: "My Post Title"),
new OA\Property(property: "content", type: "string", example: "Post content"),
new OA\Property(property: "published", type: "boolean", default: false),
new OA\Property(property: "category_id", type: "integer", nullable: true, example: 1)
]
)]
class PostRequest
{
// Your DTO/Request class
}#[OA\Schema(
schema: "Pet",
required: ["name", "type"],
properties: [
new OA\Property(property: "name", type: "string", example: "Buddy"),
new OA\Property(property: "type", type: "string", enum: ["dog", "cat"])
],
discriminator: new OA\Discriminator(propertyName: "type")
)]
class Pet {}
#[OA\Schema(
schema: "Dog",
allOf: [
new OA\Schema(ref: "#/components/schemas/Pet"),
new OA\Schema(
properties: [
new OA\Property(property: "breed", type: "string", example: "Golden Retriever")
]
)
]
)]
class Dog extends Pet {}
#[OA\Schema(
schema: "Cat",
allOf: [
new OA\Schema(ref: "#/components/schemas/Pet"),
new OA\Schema(
properties: [
new OA\Property(property: "indoor", type: "boolean", example: true)
]
)
]
)]
class Cat extends Pet {}Define the security scheme in your base controller:
#[OA\SecurityScheme(
securityScheme: "apiKey",
type: "apiKey",
description: "API Key Authentication",
name: "X-API-Key",
in: "header"
)]
class Controller
{
// Your controller code
}Apply it to endpoints:
#[OA\Get(
path: "/api/users",
summary: "Get users",
tags: ["Users"],
security: [["apiKey" => []]],
responses: [
new OA\Response(response: 200, description: "Success")
]
)]
public function index()
{
// Your code
}#[OA\SecurityScheme(
securityScheme: "bearerAuth",
type: "http",
description: "Enter JWT Bearer token",
scheme: "bearer",
bearerFormat: "JWT"
)]
class Controller {}Usage:
#[OA\Get(
path: "/api/profile",
summary: "Get user profile",
tags: ["Profile"],
security: [["bearerAuth" => []]],
responses: [
new OA\Response(response: 200, description: "Success"),
new OA\Response(response: 401, description: "Unauthenticated")
]
)]
public function profile()
{
// Your code
}#[OA\SecurityScheme(
securityScheme: "oauth2",
type: "oauth2",
description: "OAuth2 password grant",
flows: [
new OA\Flow(
flow: "password",
tokenUrl: "/oauth/token",
refreshUrl: "/oauth/token/refresh",
scopes: [
"read:users" => "Read user information",
"write:users" => "Modify user information",
"admin" => "Administrative access"
]
)
]
)]
class Controller {}Usage with specific scopes:
#[OA\Put(
path: "/api/users/{id}",
summary: "Update user",
tags: ["Users"],
security: [["oauth2" => ["write:users"]]],
parameters: [
new OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer"))
],
responses: [
new OA\Response(response: 200, description: "Success"),
new OA\Response(response: 403, description: "Forbidden")
]
)]
public function update($id)
{
// Your code
}#[OA\SecurityScheme(
securityScheme: "sanctum",
type: "apiKey",
description: "Enter token in format (Bearer <token>)",
name: "Authorization",
in: "header"
)]
class Controller {}#[OA\Schema(
schema: "PaginatedUsers",
properties: [
new OA\Property(
property: "data",
type: "array",
items: new OA\Items(ref: "#/components/schemas/User")
),
new OA\Property(
property: "meta",
properties: [
new OA\Property(property: "current_page", type: "integer", example: 1),
new OA\Property(property: "last_page", type: "integer", example: 10),
new OA\Property(property: "per_page", type: "integer", example: 15),
new OA\Property(property: "total", type: "integer", example: 150)
],
type: "object"
),
new OA\Property(
property: "links",
properties: [
new OA\Property(property: "first", type: "string", example: "http://api.example.com/users?page=1"),
new OA\Property(property: "last", type: "string", example: "http://api.example.com/users?page=10"),
new OA\Property(property: "prev", type: "string", nullable: true, example: null),
new OA\Property(property: "next", type: "string", example: "http://api.example.com/users?page=2")
],
type: "object"
)
]
)]
class PaginatedUsers {}#[OA\Schema(
schema: "Error",
properties: [
new OA\Property(property: "message", type: "string", example: "The given data was invalid."),
new OA\Property(
property: "errors",
type: "object",
example: [
"email" => ["The email field is required."],
"password" => ["The password must be at least 8 characters."]
]
)
]
)]
class Error {}Group your endpoints with tags:
#[OA\Tag(
name: "Users",
description: "User management endpoints"
)]
#[OA\Tag(
name: "Posts",
description: "Blog post management",
externalDocs: new OA\ExternalDocumentation(
description: "Find out more",
url: "https://example.com/docs/posts"
)
)]
class Controller {}#[OA\Get(
path: "/api/download/{fileId}",
summary: "Download file",
tags: ["Files"],
parameters: [
new OA\Parameter(name: "fileId", in: "path", required: true, schema: new OA\Schema(type: "string"))
],
responses: [
new OA\Response(
response: 200,
description: "File content",
headers: [
new OA\Header(
header: "Content-Disposition",
description: "Attachment filename",
schema: new OA\Schema(type: "string"),
example: "attachment; filename=document.pdf"
),
new OA\Header(
header: "Content-Type",
schema: new OA\Schema(type: "string"),
example: "application/pdf"
)
],
content: new OA\MediaType(
mediaType: "application/octet-stream",
schema: new OA\Schema(type: "string", format: "binary")
)
)
]
)]
public function download($fileId)
{
// Your code
}- OpenAPI 3.0 Specification
- swagger-php Documentation
- swagger-php Attributes Guide
- Swagger Editor - Test your OpenAPI definitions
-
Use Schema References: Define schemas once and reference them with
ref: "#/components/schemas/SchemaName"to avoid repetition. -
Organize by Tags: Use tags to group related endpoints for better documentation structure.
-
Be Specific with Types: Always specify property types, formats, and examples for better API documentation.
-
Document All Responses: Include all possible response codes (200, 400, 401, 404, 422, 500, etc.).
-
Use Examples: Provide realistic examples for all parameters, request bodies, and responses.
-
Security: Document all security requirements at both the global and endpoint levels.
-
Validation Rules: Mirror your Laravel validation rules in the OpenAPI schema (required, maxLength, pattern, etc.).
-
Keep It DRY: Use schema references and avoid duplicating definitions.