5.0 removes long-deprecated APIs. Components ship with a @beta stability flag (for example
"api-platform/state": "^5.0@beta") instead of @alpha. If your 4.4 install runs without
deprecation notices, most of this upgrade is a no-op; the sections below cover the changes that are
not announced by a 4.4 deprecation.
use_iri_as_id Now Defaults to falseThe default announced by the 4.4 deprecation now applies: not setting
api_platform.jsonapi.use_iri_as_id explicitly resolves to false instead of true. The JSON:API
data.id member carries the resource identifier and the IRI moves to data.links.self.
Before (4.x default, use_iri_as_id: true):
{
"data": {
"id": "/dummies/10",
"type": "Dummy",
"attributes": {
"name": "Dummy #10"
}
}
}After (5.0 default, use_iri_as_id: false):
{
"data": {
"id": "10",
"type": "Dummy",
"links": {
"self": "/dummies/10"
},
"attributes": {
"name": "Dummy #10"
}
}
}To keep the previous payload, set the option explicitly back to true.
Symfony:
# api/config/packages/api_platform.yaml
api_platform:
jsonapi:
use_iri_as_id: trueLaravel:
// config/api-platform.php
return [
'jsonapi' => [
'use_iri_as_id' => true,
],
];See JSON:API for the full behavior, including composite identifiers and resources without a standalone item endpoint.
DeserializeProvider No Longer Accepts a TranslatorApiPlatform\State\Provider\DeserializeProvider drops the
Symfony\Contracts\Translation\TranslatorInterface fourth constructor argument that was deprecated
in 4.4. DenormalizationViolationFactoryInterface, previously the fifth argument, moves to the
fourth position:
public function __construct(
?ProviderInterface $decorated,
SerializerInterface $serializer,
SerializerContextBuilderInterface $serializerContextBuilder,
?DenormalizationViolationFactoryInterface $violationFactory = null,
)Who is affected: anyone constructing DeserializeProvider by hand, or overriding the
api_platform.state_provider.deserialize service definition with a TranslatorInterface argument.
Translation of denormalization violations is entirely handled by
DenormalizationViolationFactoryInterface since 4.4; drop the translator argument and shift any
positional $violationFactory argument one position to the left.
api-platform/state no longer requires symfony/translation-contracts.
The following long-deprecated APIs are removed:
Configuration keys — these Symfony bundle options no longer exist; remove them from
config/packages/api_platform.yaml. The configuration reference lists the
current options:
| Removed key | Replacement |
|---|---|
validator.query_parameter_validation | none — always on |
enable_link_security | none — sub-resource link security is always enabled |
resource_class_directories | none — #[ApiResource] classes are autoconfigured |
graphql.graphql_playground | none — was already ignored |
http_cache.invalidation.varnish_urls | http_cache.invalidation.urls or scoped_clients |
http_cache.invalidation.xkey | a custom purger, see HTTP cache invalidation |
ObjectMapperProcessor — ApiPlatform\State\Processor\ObjectMapperProcessor is removed. Use
ApiPlatform\State\Processor\ObjectMapperInputProcessor and
ApiPlatform\State\Processor\ObjectMapperOutputProcessor instead; see DTOs for the
split responsibilities.
The $distinctFormats constructor argument — it is removed from both
ApiPlatform\JsonSchema\DefinitionNameFactory and ApiPlatform\JsonSchema\SchemaFactory. JSON
Schema definition names for formats other than json and merge-patch+json are now always
suffixed with the format. If you instantiate SchemaFactory positionally, its
$definitionNameFactory argument moves from the seventh to the sixth position.
ValidationException’s string-message constructor — the first constructor argument only
accepts a Symfony\Component\Validator\ConstraintViolationListInterface now; the
string|ConstraintViolationListInterface union and the plain-string code path are removed.
ApiTestCase::$alwaysBootKernel defaulting to null — the property now defaults to false
(kernel not rebooted between requests if already booted) instead of triggering a deprecation and
always booting the kernel. Set it to true in your test class if you relied on the implicit
always-boot behavior.
Automatic short-name deduplication — the
defaults.extra_properties.deduplicate_resource_short_names opt-in flag is removed. Resources
sharing a shortName are now always deduplicated with a numeric suffix (AttributeResource2,
Employee3, …), unconditionally, instead of raising a deprecation when two #[ApiResource]
attributes shared the same shortName without opting in.
Explicit api_assign_object_to_populate context — DeserializeProvider no longer falls back
to computing SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE
(api_assign_object_to_populate) from the HTTP method itself; it only assigns the loaded object
to the denormalization context’s object_to_populate when that flag is already set. Symfony’s
MainController and DeserializeListener, and Laravel’s ApiPlatformController, already set it
for POST, PATCH, and non-standard PUT before calling the provider, so this only matters if
you call DeserializeProvider::provide() directly from a custom controller or pipeline.
Serializer-aware state providers — ApiPlatform\State\SerializerAwareProviderInterface and
ApiPlatform\State\SerializerAwareProviderTrait are removed. Inject
Symfony\Component\Serializer\SerializerInterface through your provider’s constructor instead of
relying on setSerializerLocator(). The internal DataProviderPass that performed setter
injection for these providers is removed as a consequence.
See core#8367 for the full diff.
The legacy symfony/property-info Type system is replaced by symfony/type-info throughout:
ApiProperty::$builtinTypes, ApiProperty::getBuiltinTypes(), and
ApiProperty::withBuiltinTypes() are removed. Use ApiProperty::getNativeType() /
withNativeType(), which return a Symfony\Component\TypeInfo\Type.TypeConverterInterface::convertType() is removed. Implement convertPhpType()
instead; it takes a Symfony\Component\TypeInfo\Type rather than a legacy
Symfony\Component\PropertyInfo\Type.ContextAwareTypeBuilderInterface::isCollection(LegacyType $type) is removed entirely; collection
detection is handled internally from the Type object.Who is affected: custom TypeConverterInterface or ContextAwareTypeBuilderInterface
implementations, and any code reading ApiProperty::getBuiltinTypes().
PropertyAwareFilterInterface::getProperties() Is Now a Real Interface MethodgetProperties(): ?array was previously only documented via an @method docblock annotation on
ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface (with the real method commented
out). It is now declared on the interface. Custom filters implementing
PropertyAwareFilterInterface without a getProperties() method now fail with a fatal “must
implement” error; add the method (or use PropertyAwareFilterTrait).
status Is Now a StringApiPlatform\JsonApi\Serializer\ErrorNormalizer now always casts the status member of a JSON:API
error object to a string, matching the
JSON:API error object spec. Update any client or custom
normalizer that expects status to be an integer.
/contexts/Error and /contexts/ConstraintViolationList Are No Longer Special-CasedApiPlatform\JsonLd\Action\ContextAction::RESERVED_SHORT_NAMES and the hardcoded base-context
fallback for the Error and ConstraintViolationList short names are removed. Since exceptions and
validation errors have been resources since 3.2, their @context is now built through the normal
per-resource context loop like any other resource. The /contexts/ConstraintViolationList route no
longer has a producer: responses reference /contexts/ConstraintViolation (singular) instead.
Who is affected: code or tests hardcoding /contexts/ConstraintViolationList URLs.
SerializerContextBuilder No Longer Injects uri_variablesSerializerContextBuilder::createFromRequest() no longer populates $context['uri_variables'] from
the request attributes. The key is still set — but only later in the state pipeline, by
SerializeProcessor and DeserializeProvider, which already had the correctly parsed values.
Who is affected: custom normalizers reading $context['uri_variables'] in a context built
directly from SerializerContextBuilder::createFromRequest() outside the standard state pipeline
(for example, a custom controller that calls it directly). Normalizers invoked through the regular
provider/processor flow are unaffected — the key is still present by the time normalization runs.
UniqueConstraintViolationException Maps to 422 by DefaultDoctrine\DBAL\Exception\UniqueConstraintViolationException is added to the Symfony bundle’s
default exception_to_status map, resolving to 422 Unprocessable Entity (alongside the
pre-existing OptimisticLockException => 409 Conflict). If you previously mapped this exception
yourself, or relied on it falling through to 500, review your exception_to_status configuration.
See Exception to status.
RangeFilter Deprecated, ComparisonFilter Gains [between]ComparisonFilter now natively supports ?price[between]=10..100, covering the full range syntax
([gt]/[gte]/[lt]/[lte]/[between]). RangeFilter is deprecated in favor of it and is
removed in 6.0; DateFilter and ExistsFilter become standalone classes (they no longer extend
AbstractFilter) with no change to their URL syntax. See
Doctrine Filters for the full migration path.
FilterInterface::getDescription() Stays DeprecatedApiPlatform\Metadata\FilterInterface::getDescription() was deprecated in 4.2 for removal in 6.0.
That removal is deferred to 6.0 and does not happen in 5.0 — the method, #[ApiFilter],
Operation::$filters, and the AbstractFilter base class all still work in 5.0. Only migrate off
them when you are ready to keep pace with the 6.0 timeline; see
Doctrine Filters for the modern filter
interfaces.
The testing utilities have moved from api-platform/symfony to the new api-platform/test package.
Install it as a development dependency:
composer require --dev api-platform/test:^5.0@beta
Update imports to use the new namespace:
use ApiPlatform\Test\ApiTestCase;ApiPlatform\Symfony\Bundle\Test\ApiTestCase remains as a deprecated compatibility shim when
api-platform/test is installed, but it emits a deprecation notice. The other testing utilities
have moved under the same ApiPlatform\Test namespace.
The removal of the internal Request::getContentType() fallbacks and the Symfony 6 value-resolver
compatibility interface requires no application changes.
4.4 is the last 4.x minor. It ships a single backwards-incompatible change (below); everything else is a deprecation that keeps working until it is removed in a later major (5.0 or 6.0). Fixing the deprecations now makes the upgrade to the next major a no-op.
Prior to 4.4, BackedEnum-typed properties received special treatment: any serializer type mismatch
during denormalization was unconditionally promoted to HTTP 422. Starting with 4.4, that implicit
promotion is replaced by a constraint-aware check.
Who is affected: code that relied on enum-typed properties producing 422 without any Symfony Validator constraint (or Laravel rule) on the property.
What to do (Symfony): add an explicit constraint on the enum property:
use Symfony\Component\Validator\Constraints as Assert;
#[Assert\Type(Status::class)]
public Status $status;Alternatively, enable Symfony Validator’s
auto-mapping on the resource class.
Auto-mapping generates an implicit Type constraint from the PHP type declaration, which is
sufficient for the 422 promotion to apply.
What to do (Laravel): add a rule for the property in rules:
#[ApiResource(
rules: ['status' => 'required']
)]Properties that already carry any constraint or rule are unaffected — they continue to produce 422.
For the full rule tables and additional details, see Constraint-Aware 422 for Denormalization Errors (Symfony) and the equivalent section in the Laravel validation guide.
The legacy Doctrine filter API is deprecated in favor of parameter-based filters declared with the
#[QueryParameter] attribute. The #[ApiFilter] attribute, the Operation::$filters property, and
the AbstractFilter base class (Doctrine ORM and MongoDB ODM) are all deprecated and removed in
6.0.
There are two kinds of migration:
Replaced filters — removed in 6.0, swap the class:
| Legacy filter | Replacement |
|---|---|
SearchFilter | ExactFilter / PartialSearchFilter / IriFilter (depending on the strategy) |
BooleanFilter, NumericFilter, BackedEnumFilter | ExactFilter |
OrderFilter | SortFilter |
Kept filters — DateFilter, RangeFilter and ExistsFilter survive. Only the way you
declare them is deprecated: move the declaration from #[ApiFilter] to #[QueryParameter]. The
class name and the URL syntax stay the same (drop-in).
ComparisonFilter and OrFilter are now stable (no longer experimental) and are the recommended
building blocks for comparison and disjunction filtering.
A codemod automates the rewrite of #[ApiFilter] declarations to #[QueryParameter]:
bin/console api:upgrade-filter
See the filter migration guide for the full table and before/after examples.
When instantiating a filter inside a
QueryParameter, always use named arguments(
new DateFilter(nullManagement: ...)rather than positional). Filter constructors are refined across versions; named arguments keep your declarations forward-compatible.
use_iri_as_idNot setting api_platform.jsonapi.use_iri_as_id explicitly is deprecated. The default changes from
true to false in 5.0. Set it explicitly to silence the deprecation and lock in the behavior you
want:
# api/config/packages/api_platform.yaml
api_platform:
jsonapi:
use_iri_as_id: true # keep IRIs as the "id" field; set to false to use entity identifiersAccessDeniedExceptionApiPlatform\Symfony\Security\Exception\AccessDeniedException is deprecated. Use
ApiPlatform\Metadata\Exception\AccessDeniedException instead.
The item normalizers no longer denormalize. Calling denormalize() on one of them triggers a
deprecation, and the denormalization code moves to a new ItemDenormalizer class in the same
namespace:
| Deprecated denormalization entry point | Replacement |
|---|---|
ApiPlatform\Serializer\ItemNormalizer | ApiPlatform\Serializer\ItemDenormalizer |
ApiPlatform\JsonLd\Serializer\ItemNormalizer | ApiPlatform\JsonLd\Serializer\ItemDenormalizer |
ApiPlatform\JsonApi\Serializer\ItemNormalizer | ApiPlatform\JsonApi\Serializer\ItemDenormalizer |
ApiPlatform\GraphQl\Serializer\ItemNormalizer | ApiPlatform\GraphQl\Serializer\ItemDenormalizer |
This affects you only if you decorate or extend one of these classes to change how API Platform reads an incoming payload. If you decorate a normalizer service to alter denormalization, decorate the matching denormalizer service instead:
| Format | Denormalizer service |
|---|---|
| Default | api_platform.serializer.denormalizer.item |
| JSON-LD | api_platform.jsonld.denormalizer.item |
| JSON:API | api_platform.jsonapi.denormalizer.item |
| GraphQL | api_platform.graphql.denormalizer.item |
Decoration that only changes normalization keeps working without a change.
propertyDoctrine parameter-based filters (ExactFilter, IriFilter, PartialSearchFilter, UuidFilter)
now throw InvalidArgumentException if the property attribute is missing. If you have filter
parameters without an explicit property, you must either add one or use the :property
placeholder in your parameter name.
// Before (would silently work without property):
#[ApiFilter(ExactFilter::class)]
// After (property is required):
#[ApiFilter(ExactFilter::class, property: 'name')]
// Or use the :property placeholder in the parameter name
Entities marked as readonly via Doctrine metadata ($classMetadata->markReadOnly()) no longer
expose PUT and PATCH operations. Clients sending PUT/PATCH to these resources will receive a 404. If
you need write operations on readonly entities, explicitly define them in your ApiResource
attribute.
@type with output and itemUriTemplateWhen using output with itemUriTemplate on a collection operation, the JSON-LD @type now uses
the resource class name instead of the output DTO class name for semantic consistency with
itemUriTemplate behavior. Update any client code that relies on the DTO class name in @type.
isGranted Evaluated Before ProviderSecurity expressions are now evaluated before the state provider runs. Expressions that do not
reference the object variable will be checked at the pre_read stage, improving security by
preventing unnecessary database queries on unauthorized requests. Expressions that reference
object still wait for the provider to resolve the entity. Review any security expressions that
relied on provider side-effects running before authorization.
@id Now Always Uses #ShortNameHydra documentation classes now consistently use #ShortName as their @id instead of schema.org
type URIs (e.g. schema:Product). Semantic types configured via types are now exposed through
rdfs:subClassOf. Clients should expect class @id and property range changes in the Hydra
documentation if resources had custom types configured.
API responses now include Allow and Accept-Post headers per the Linked Data Platform
specification. These are informational headers that help clients discover API capabilities and
should not break existing integrations.
Remove the keep_legacy_inflector, the event_listeners_backward_compatibility_layer and the
rfc_7807_compliant_errors flag:
api_platform:
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: false
extra_properties:
standard_put: true
rfc_7807_compliant_errors: true
If you use a custom normalizer for validation exception use:
api_platform:
validator:
legacy_validation_exception: trueIndeed, we will throw another validation class in API Platform 4 we will throw
ApiPlatform\Validator\Exception\ValidationException instead of
ApiPlatform\Symfony\Validator\Exception\ValidationException
It’s really important to add the use_symfony_listeners flag, set to true if you use Symfony
listeners or controllers:
api_platform:
use_symfony_listeners: falseThe keep_legacy_inflector flag will be removed from API Platform 4, you need to fix your issues
first. In API Platform 3.4, the Inflector is available as a service that you can configure through:
api_platform:
inflector: api_platform.metadata.inflectorImplement the ApiPlatform\Metadata\InflectorInterface if you need to tweak its behavior.
We added an hydra_prefix configuration as the hydra: prefix will be removed by default in API
Platform 4:
api_platform:
serializer:
hydra_prefix: falseStandard PUT is now true by default, you can change its value using:
api_platform:
defaults:
extra_properties:
standard_put: trueWe recommend using the standalone API Platform packages instead of the Core monolithic repository.
Update your composer.json like that:
{
"require": {
"api-platform/core": "^3",
"api-platform/symfony": "^3 || ^4"
// also add the extra packages you need, like "api-platform/doctrine-orm"
}
}
This is the recommended configuration for API Platform 3.2. We review each of these changes in this document.
api_platform:
title: Hello API Platform
version: 1.0.0
formats:
jsonld: ["application/ld+json"]
docs_formats:
jsonld: ["application/ld+json"]
jsonopenapi: ["application/vnd.openapi+json"]
html: ["text/html"]
defaults:
stateless: true
cache_headers:
vary: ["Content-Type", "Authorization", "Origin"]
extra_properties:
standard_put: true
rfc_7807_compliant_errors: true
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: falseWe noticed that API Platform was enabling json by default because of our OpenAPI support. We
introduced the new application/vnd.openapi+json. Therefore if you want json you need to
explicitly handle it:
formats:
json: ["application/json"]You can also remove documentations you’re not using via the new docs_formats.
A new option error_formats is also used for content negotiation.
For new users we recommend to use
event_listeners_backward_compatibility_layer: falseThis allows API Platform to not use http kernel event listeners. It also allows you to force options
like read: true or validate: true. This simplifies use cases like
validating a delete operation
Event listeners will not get removed and are not deprecated, they’ll use our providers and
processors in a future version.
We’re switching to symfony/string
inflector, to keep using
doctrine/inflector use:
keep_legacy_inflector: trueWe strongly recommend that you use your own inflector anyways with a PathSegmentNameGenerator.
defaults:
extra_properties:
rfc_7807_compliant_errors: trueAs this is an extraProperties it’s configurable per resource/operation. This is improving the
compatibility of Hydra errors with JSON problem. It also enables new extension points on
Errors such as
Error provider and
Error Resource.
You may want to convert your openApiContext to openapi, doing so is quite fastidious, @lyrixx created a rector script to help if needed:
You can also help us improve the documentation of this page.
Using an AI coding agent? See the documentation index for LLMs at /docs/llms.txt.
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