API Platform 3.3 shipped in April 2024 with a set of targeted additions. None of them reshape the architecture — 3.2 already closed that chapter. What 3.3 adds is control over things that were previously either hardcoded or required a workaround: response headers, link visibility on sub-resources, and webhooks in the generated spec.

Declarative header configuration

Before 3.3, setting custom response headers required either a custom processor that modified the response object or a Symfony event listener on kernel.response. Both approaches worked but lived outside the resource definition.

3.3 adds a parameters parameter to operation metadata:

use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\HeaderParameter;

#[Get(
    parameters: [
        'X-Custom-Header' => new HeaderParameter(description: 'A custom header'),
    ]
)]

For headers that vary per response (like Cache-Control with a computed max-age), the processor can still set them directly on the response. The headers parameter is primarily for documenting expected headers in the OpenAPI spec and for static header values.

When a resource exposes links to related resources, those links appear in the serialized output regardless of whether the current user can access the linked resource. This creates a disclosure problem: a user who can read a book but not its author profile still sees the author’s URI in the response.

3.3 adds security expressions to the Link descriptor:

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;

#[ApiResource]
#[Get]
class Book
{
    #[Link(
        toClass: Author::class,
        security: "is_granted('ROLE_ADMIN')"
    )]
    public Author $author;
}

The link is omitted from the response when the security expression evaluates to false. The linked resource itself is not affected — only whether the current response includes the reference to it.

ApiProperty::security

The same security expression mechanism is available at the property level via ApiProperty::security. This lets you hide individual fields based on the current user without writing a custom normalizer:

use ApiPlatform\Metadata\ApiProperty;

class Book
{
    #[ApiProperty(security: "is_granted('ROLE_ADMIN')")]
    public string $internalNote;
}

The property is excluded from serialization when the expression is false. This is cleaner than a normalizer for the common case of role-gated fields.

OpenAPI webhooks

OpenAPI 3.1 supports webhooks — outbound HTTP calls that your API makes to registered listeners — in the spec document itself. Before 3.3, there was no way to document these in API Platform’s generated spec.

3.3 adds a Webhook class you pass to the openapi parameter of an operation. Declare a dedicated PHP class with #[ApiResource] and use Webhook on each operation to describe the outbound call shape:

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Attributes\Webhook;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\PathItem;

#[ApiResource(
    operations: [
        new Post(
            openapi: new Webhook(
                name: 'bookCreated',
                pathItem: new PathItem(
                    post: new Operation(summary: 'A book was created'),
                ),
            )
        ),
    ]
)]
class BookWebhook {}

The webhook definitions appear in the generated spec under the webhooks key alongside regular paths. Swagger UI renders them in a separate section.

Swagger UI deep linking

Swagger UI supports deep linking — bookmarkable URLs that open directly to a specific operation in the interface. Before 3.3, the API Platform integration did not enable this. 3.3 turns on the Swagger UI deepLinking option, configurable via swagger_ui_extra_configuration:

api_platform:
    openapi:
        swagger_ui_extra_configuration:
            deepLinking: true

With this enabled, the URL fragment updates as you navigate the UI, and pasting or sharing the URL opens the same operation. Useful when writing docs that link directly to a specific endpoint.

Strict query parameter validation

3.3 tightens the query parameter validator: parameters not declared on the operation now return a 400 response instead of being silently ignored. This behavior is opt-in:

api_platform:
    validator:
        query_parameter_validation: true

The intent is to catch typos and API misuse early. If you rely on pass-through query parameters for custom logic (logging, feature flags), you need to declare them explicitly on the operation before enabling this.