The Distribution: Create Powerful APIs with Ease
Core
Schema Generator
Admin
Create Client
Deployment
General Information
API Platform natively support the OpenAPI API specification format.
The specification of the API is available at the /docs.json
path.
By default, OpenAPI v3 is used.
You can also get an OpenAPI v3-compliant version thanks to the spec_version
query parameter: /docs.json?spec_version=3
It also integrates a customized version of Swagger UI and ReDoc, some nice tools to display the API documentation in a user friendly way.
You can also dump an OpenAPI specification for your API.
OpenAPI, JSON format:
docker compose exec php \
bin/console api:openapi:export
docker compose exec php \
bin/console api:openapi:export
OpenAPI, YAML format:
docker compose exec php \
bin/console api:openapi:export --yaml
docker compose exec php \
bin/console api:openapi:export --yaml
Create a file containing the specification:
docker compose exec php \
bin/console api:openapi:export --output=swagger_docs.json
docker compose exec php \
bin/console api:openapi:export --output=swagger_docs.json
If you want to use the old OpenAPI v2 (Swagger) JSON format, use:
docker compose exec php \
bin/console api:swagger:export
docker compose exec php \
bin/console api:swagger:export
Symfony allows to decorate services, here we
need to decorate api_platform.openapi.factory
.
In the following example, we will see how to override the title and the base path URL of the Swagger documentation and add a custom filter for
the GET
operation of /foos
path.
# api/config/services.yaml
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments: [ '@App\OpenApi\OpenApiFactory.inner' ]
autoconfigure: false
# api/config/services.yaml
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments: [ '@App\OpenApi\OpenApiFactory.inner' ]
autoconfigure: false
<?php
namespace App\OpenApi;
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Model;
class OpenApiFactory implements OpenApiFactoryInterface
{
public function __construct(private OpenApiFactoryInterface $decorated)
{
}
public function __invoke(array $context = []): OpenApi
{
$openApi = $this->decorated->__invoke($context);
$pathItem = $openApi->getPaths()->getPath('/api/grumpy_pizzas/{id}');
$operation = $pathItem->getGet();
$openApi->getPaths()->addPath('/api/grumpy_pizzas/{id}', $pathItem->withGet(
$operation->withParameters(array_merge(
$operation->getParameters(),
[new Model\Parameter('fields', 'query', 'Fields to remove of the output')]
))
));
$openApi = $openApi->withInfo((new Model\Info('New Title', 'v2', 'Description of my custom API'))->withExtensionProperty('info-key', 'Info value'));
$openApi = $openApi->withExtensionProperty('key', 'Custom x-key value');
$openApi = $openApi->withExtensionProperty('x-value', 'Custom x-value value');
// to define base path URL
$openApi = $openApi->withServers([new Model\Server('https://foo.bar')]);
return $openApi;
}
}
<?php
namespace App\OpenApi;
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Model;
class OpenApiFactory implements OpenApiFactoryInterface
{
public function __construct(private OpenApiFactoryInterface $decorated)
{
}
public function __invoke(array $context = []): OpenApi
{
$openApi = $this->decorated->__invoke($context);
$pathItem = $openApi->getPaths()->getPath('/api/grumpy_pizzas/{id}');
$operation = $pathItem->getGet();
$openApi->getPaths()->addPath('/api/grumpy_pizzas/{id}', $pathItem->withGet(
$operation->withParameters(array_merge(
$operation->getParameters(),
[new Model\Parameter('fields', 'query', 'Fields to remove of the output')]
))
));
$openApi = $openApi->withInfo((new Model\Info('New Title', 'v2', 'Description of my custom API'))->withExtensionProperty('info-key', 'Info value'));
$openApi = $openApi->withExtensionProperty('key', 'Custom x-key value');
$openApi = $openApi->withExtensionProperty('x-value', 'Custom x-value value');
// to define base path URL
$openApi = $openApi->withServers([new Model\Server('https://foo.bar')]);
return $openApi;
}
}
The impact on the swagger-ui is the following:
Sometimes you may want to change the information included in your OpenAPI documentation. The following configuration will give you total control over your OpenAPI definitions:
<?php
// api/src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource]
class Product // The class name will be used to name exposed resources
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
/**
* @param string $name A name property - this description will be available in the API documentation too.
*
*/
#[ORM\Column]
#[Assert\NotBlank]
#[ApiProperty(
openapiContext: [
'type' => 'string',
'enum' => ['one', 'two'],
'example' => 'one'
]
)]
public string $name;
#[ORM\Column(type: "datetime")]
#[Assert\DateTime]
#[ApiProperty(
openapiContext: [
'type' => 'string',
'format' => 'date-time'
]
)]
public $timestamp;
// ...
}
<?php
// api/src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource]
class Product // The class name will be used to name exposed resources
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
/**
* @param string $name A name property - this description will be available in the API documentation too.
*
*/
#[ORM\Column]
#[Assert\NotBlank]
#[ApiProperty(
openapiContext: [
'type' => 'string',
'enum' => ['one', 'two'],
'example' => 'one'
]
)]
public string $name;
#[ORM\Column(type: "datetime")]
#[Assert\DateTime]
#[ApiProperty(
openapiContext: [
'type' => 'string',
'format' => 'date-time'
]
)]
public $timestamp;
// ...
}
This will produce the following Swagger documentation:
"components": {
"schemas": {
"GrumpyPizza:jsonld": {
"type": "object",
"description": "",
"properties": {
"@context": {
"readOnly": true,
"type": "string"
},
"@id": {
"readOnly": true,
"type": "string"
},
"@type": {
"readOnly": true,
"type": "string"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"id": {
"readOnly": true,
"type": "integer"
},
"name": {
"type": "string",
"enum": [
"one",
"two"
],
"example": "one"
}
}
}
}
}
"components": {
"schemas": {
"GrumpyPizza:jsonld": {
"type": "object",
"description": "",
"properties": {
"@context": {
"readOnly": true,
"type": "string"
},
"@id": {
"readOnly": true,
"type": "string"
},
"@type": {
"readOnly": true,
"type": "string"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"id": {
"readOnly": true,
"type": "integer"
},
"name": {
"type": "string",
"enum": [
"one",
"two"
],
"example": "one"
}
}
}
}
}
To pass a context to the OpenAPI v2 generator, use the swaggerContext
attribute (notice the prefix: swagger
instead of openapi
).
Sometimes you may want to disable an operation from the OpenAPI documentation, for example to not exposing it.
Using the openapi
boolean option disables this operation from the OpenAPI documentation:
<?php
// api/src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
#[ApiResource(
operations: [
new GetCollection(openapi: false)
]
)]
class Product
{
// ...
}
<?php
// api/src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
#[ApiResource(
operations: [
new GetCollection(openapi: false)
]
)]
class Product
{
// ...
}
Note: as your route is not exposed, you may want to return a HTTP 404 if it’s called. Prefer using the NotExposedAction
controller instead.
API Platform generates a definition name based on the serializer groups
defined
in the (de
)normalizationContext
. It’s possible to override the name
thanks to the openapi_definition_name
option:
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
#[ApiResource]
#[Post(denormalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'])]
class User
{
// ...
}
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
#[ApiResource]
#[Post(denormalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'])]
class User
{
// ...
}
It’s also possible to re-use the (de
)normalizationContext
:
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
#[ApiResource]
#[Post(denormalizationContext: [User::API_WRITE])]
class User
{
const API_WRITE = [
'groups' => ['user:read'],
'openapi_definition_name' => 'Read',
];
}
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
#[ApiResource]
#[Post(denormalizationContext: [User::API_WRITE])]
class User
{
const API_WRITE = [
'groups' => ['user:read'],
'openapi_definition_name' => 'Read',
];
}
You also have full control over both built-in and custom operations documentation.
<?php
// api/src/Entity/Rabbit.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model;
use App\Controller\RandomRabbit;
#[ApiResource]
#[Post(
name: 'create_rabbit',
uriTemplate: '/rabbit/create',
controller: RandomRabbit::class,
openapi: new Model\Operation(
summary: 'Create a rabbit picture',
description: '# Pop a great rabbit picture by color!\n\n',
requestBody: new Model\RequestBody(
content: new \ArrayObject([
'application/json' => [
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'description' => ['type' => 'string']
]
],
'example' => [
'name' => 'Mr. Rabbit',
'description' => 'Pink Rabbit'
]
]
])
)
)
)]
class Rabbit
{
// ...
}
<?php
// api/src/Entity/Rabbit.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model;
use App\Controller\RandomRabbit;
#[ApiResource]
#[Post(
name: 'create_rabbit',
uriTemplate: '/rabbit/create',
controller: RandomRabbit::class,
openapi: new Model\Operation(
summary: 'Create a rabbit picture',
description: '# Pop a great rabbit picture by color!\n\n',
requestBody: new Model\RequestBody(
content: new \ArrayObject([
'application/json' => [
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'description' => ['type' => 'string']
]
],
'example' => [
'name' => 'Mr. Rabbit',
'description' => 'Pink Rabbit'
]
]
])
)
)
)]
class Rabbit
{
// ...
}
To disable Swagger UI (ReDoc will be shown by default):
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_swagger_ui: false
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_swagger_ui: false
To disable ReDoc:
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_re_doc: false
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_re_doc: false
By default, the Swagger UI is available at the API location (when the HTML format is asked) and at the route /docs
.
You may want to change its route and/or disable it at the API location.
Manually register the Swagger UI controller:
# app/config/routes.yaml
api_doc:
path: /api_documentation
controller: api_platform.swagger_ui.action
# app/config/routes.yaml
api_doc:
path: /api_documentation
controller: api_platform.swagger_ui.action
Change /api_documentation
to the URI you wish Swagger UI to be accessible on.
To disable the Swagger UI at the API location, disable both Swagger UI and ReDoc:
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_swagger_ui: false
enable_re_doc: false
# api/config/packages/api_platform.yaml
api_platform:
# ...
enable_swagger_ui: false
enable_re_doc: false
If you have manually registered the Swagger UI controller, the Swagger UI will still be accessible at the route you have chosen.
Sometimes you may want to use a different Asset Package for the Swagger UI. In this way you’ll have more fine-grained control over the asset URL generations. This is useful i.e. if you want to use different base path, base URL or asset versioning strategy.
Specify a custom asset package name:
# config/packages/api_platform.yaml
api_platform:
asset_package: 'api_platform'
# config/packages/api_platform.yaml
api_platform:
asset_package: 'api_platform'
Set or override asset properties per package:
# config/packages/framework.yaml
framework:
# ...
assets:
base_path: '/custom_base_path' # the default
packages:
api_platform:
base_path: '/'
# config/packages/framework.yaml
framework:
# ...
assets:
base_path: '/custom_base_path' # the default
packages:
api_platform:
base_path: '/'
As described in the Symfony documentation, it’s possible to override the Twig template that loads Swagger UI and renders the documentation:
{# templates/bundles/ApiPlatformBundle/SwaggerUi/index.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% if title %}{{ title }} {% endif %}My custom template</title>
{# ... #}
</html>
{# templates/bundles/ApiPlatformBundle/SwaggerUi/index.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% if title %}{{ title }} {% endif %}My custom template</title>
{# ... #}
</html>
You may want to copy the one shipped with API Platform and customize it.
AWS API Gateway supports OpenAPI partially, but it requires some changes. API Platform provides a way to be compatible with Amazon API Gateway.
To enable API Gateway compatibility on your OpenAPI docs, add api_gateway=true
as query parameter: http://www.example.com/docs.json?api_gateway=true
.
The flag --api-gateway
is also available through the command-line.
If you implemented OAuth on your API, you should configure OpenApi’s authorization using API Platform’s configuration:
api_platform:
oauth:
# To enable or disable OAuth.
enabled: false
# The OAuth client ID.
clientId: ''
# The OAuth client secret.
clientSecret: ''
# The OAuth type.
type: 'oauth2'
# The OAuth flow grant type.
flow: 'application'
# The OAuth token url.
tokenUrl: '/oauth/v2/token'
# The OAuth authentication url.
authorizationUrl: '/oauth/v2/auth'
# The OAuth scopes.
scopes: []
api_platform:
oauth:
# To enable or disable OAuth.
enabled: false
# The OAuth client ID.
clientId: ''
# The OAuth client secret.
clientSecret: ''
# The OAuth type.
type: 'oauth2'
# The OAuth flow grant type.
flow: 'application'
# The OAuth token url.
tokenUrl: '/oauth/v2/token'
# The OAuth authentication url.
authorizationUrl: '/oauth/v2/auth'
# The OAuth scopes.
scopes: []
Note that clientId
and clientSecret
are being used by the SwaggerUI if enabled.
The api_platform.oauth.scopes
option requires an array value with the scopes name and description. For example:
api_platform:
oauth:
scopes:
profile: "This scope value requests access to the End-User's default profile Claims, which are: name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at."
email: "This scope value requests access to the email and email_verified Claims."
address: "This scope value requests access to the address Claim."
phone: "This scope value requests access to the phone_number and phone_number_verified Claims."
api_platform:
oauth:
scopes:
profile: "This scope value requests access to the End-User's default profile Claims, which are: name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at."
email: "This scope value requests access to the email and email_verified Claims."
address: "This scope value requests access to the address Claim."
phone: "This scope value requests access to the phone_number and phone_number_verified Claims."
Note: if you’re using an OpenID Connect server (such as Keycloak or Auth0), the openid
scope must be set according
to the OpenID Connect specification.
The info object provides metadata about the API like licensing information or a contact. You can specify this information using API Platform’s configuration:
api_platform:
# The title of the API.
title: 'API title'
# The description of the API.
description: 'API description'
# The version of the API.
version: '0.0.0'
openapi:
# The contact information for the exposed API.
contact:
# The identifying name of the contact person/organization.
name:
# The URL pointing to the contact information. MUST be in the format of a URL.
url:
# The email address of the contact person/organization. MUST be in the format of an email address.
email:
# A URL to the Terms of Service for the API. MUST be in the format of a URL.
termsOfService:
# The license information for the exposed API.
license:
# The license name used for the API.
name:
# URL to the license used for the API. MUST be in the format of a URL.
url:
api_platform:
# The title of the API.
title: 'API title'
# The description of the API.
description: 'API description'
# The version of the API.
version: '0.0.0'
openapi:
# The contact information for the exposed API.
contact:
# The identifying name of the contact person/organization.
name:
# The URL pointing to the contact information. MUST be in the format of a URL.
url:
# The email address of the contact person/organization. MUST be in the format of an email address.
email:
# A URL to the Terms of Service for the API. MUST be in the format of a URL.
termsOfService:
# The license information for the exposed API.
license:
# The license name used for the API.
name:
# URL to the license used for the API. MUST be in the format of a URL.
url:
You can also help us improve the documentation of this page.
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more