Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Core Laravel Package

api-platform/core

API Platform Core is an easy-to-use, powerful PHP library for creating hypermedia-driven REST and GraphQL APIs. Integrate it with Symfony, support JSON-LD, Hydra, OpenAPI, JSON:API, HAL, and Problem Details, and build fully featured API-first projects in minutes.

View on GitHub
Deep Wiki
Context7

Getting Started

API Platform Core is a metadata-driven API engine: you declare what a resource is and which operations exist, and the library generates routing, serialization, validation wiring, OpenAPI/Hydra docs, and (optionally) a full GraphQL schema. Day-to-day work therefore happens in attributes on your models and services, not in controllers.

Minimal boot in Symfony:

composer require api-platform/core
# config/bundles.php
# ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true]
# config/packages/api_platform.yaml
api_platform:
    title: Acme API
    version: 1.0.0
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    formats:
        jsonld: ['application/ld+json']
        json: ['application/json']
#[ApiResource(operations: [new Get(), new GetCollection(), new Post()])]
#[ORM\Entity]
class Book { /* ... */ }

Then bin/console debug:router should show /api/books and /api/books/{id}, bin/console api:openapi:export should emit a document, and /api/docs should render Swagger/Scalar UI. If those three artifacts exist, the pipeline (metadata → routes → serializer → docs) is wired correctly.

Where to look first:

  • The official docs at api-platform.com/docs/core are the real entry point; the README itself is deliberately thin.
  • The demo application (api-platform/demo) is the best reference for idiomatic attributes; the changelog is the best reference for what's moving.
  • In the repo: src/Metadata (resource/operation model), src/State (providers/processors), src/Serializer, src/Doctrine (filters, ORM extension), and per-format packages (src/JsonLd, src/Hydra, src/JsonApi, src/Hal, src/OpenApi, src/GraphQl).
  • src/Symfony/Bundle/Resources/config/*.php is the fastest way to see which service IDs you can decorate.

First realistic use case: expose CRUD over an existing Doctrine entity, confirm the Hydra entrypoint and OpenAPI doc, then add one SearchFilter and one OrderFilter. That single loop exercises metadata, filters, pagination, docs, and content negotiation — which is 80% of what you'll do daily.

Implementation Patterns

Operation-level resource declaration. Treat the resource as a container for operations rather than a single REST endpoint. Per-operation configuration beats global config because security, pagination, and formats rarely apply uniformly:

#[ApiResource(
    shortName: 'Book',
    paginationItemsPerPage: 30,
    paginationClientItemsPerPage: true,
)]
#[Get(security: "is_granted('ROLE_USER')")]
#[GetCollection(security: "is_granted('ROLE_USER')")]
#[Post(securityPostDenormalize: "is_granted('ROLE_ADMIN')")]
#[Put(security: "is_granted('EDIT', object)")]
#[Delete(security: "is_granted('ROLE_ADMIN')")]
class Book {}

security runs before/around the operation; securityPostDenormalize runs after the payload is hydrated, which is what you want for ownership checks (object.getOwner() === user).

DTOs as input/output boundaries. For anything non-trivial, don't expose your entity. Use input:/output: so writes go through a DTO and a processor maps it to the entity. This keeps validation rules next to the shape they describe and stops accidental mass-assignment via serializer groups.

#[Post(input: CreateBookInput::class, output: BookOutput::class, processor: CreateBookProcessor::class)]

For read-only or computed resources, skip Doctrine entirely and use provider: (or #[Provide]) with a plain service. This is also the path for aggregating third-party APIs and for "dashboard" endpoints where a repository would be the wrong abstraction.

State providers/processors as the extension seam. Nearly every real project ends up with custom ProviderInterface/ProcessorInterface implementations. Prefer them over custom controllers: you keep content negotiation, validation, docs, security, and GraphQL for free. Use stateOptions for provider-specific tuning — e.g. repositoryMethod for a custom QueryBuilder, and fetch_data: false when you only need a reference to link a relation rather than load it.

Filters: pick the modern classes. The v4.4 line deprecates the legacy SearchFilter/Boolean/Numeric/BackedEnum/OrderFilter forms and the AbstractFilter base, introducing standalone DateFilter, ExistsFilter, ComparisonFilter (including between, replacing RangeFilter), StartSearchFilter/WordStartSearchFilter, EndSearchFilter, and promoting OrFilter. For new code on 4.4+, write filters in the new style; there's a codemod (api:upgrade-filter) available to migrate existing ones. For full-text search, FreeTextQueryFilter now supports a per-property filter map, which is far more practical than one global strategy.

#[ApiFilter(SearchFilter::class, properties: ['title' => 'partial', 'author' => 'exact'])]
#[ApiFilter(OrderFilter::class, properties: ['title', 'publishedAt'])]

Apply filters at the operation level when a collection shouldn't expose them — an internal admin endpoint and a public catalog endpoint usually want different filter sets on the same entity.

Serialization groups and ApiProperty. Add #[Groups(['book:read'])] on the entity/DTO and reference the group from operations. Use #[ApiProperty(readableLink: false, writableLink: false)] to keep relations as IRIs instead of embedding them — this is the single biggest lever on payload size and on circular-reference pain. Also use #[ApiProperty(fetchEager: true)] (or explicit joins in the provider) for relations you know the client always needs.

Pagination. Defaults are fine for admin tools; for public collections turn on paginationClientItemsPerPage with a sensible paginationMaximumItemsPerPage, and consider cursor-based pagination for large, append-only datasets where offset pagination degrades.

Validation is the contract. You don't write 400/422 responses — Symfony Validator constraints plus #[Assert\Valid] on nested DTOs produce Problem Details automatically, including denormalization violations. This is why DTO inputs pay off: the constraints describe the public contract exactly.

GraphQL comes along for free. The same #[ApiResource]/operation metadata generates queries and mutations. If you want to prevent a resource from appearing in GraphQL, that's an operation-level decision (graphql: false), not a separate implementation. Custom mutation output classes are honored in payload types (a fix landed in 4.3.12), so you can return a purpose-built payload instead of the resource.

Testing. Use api-platform/symfony's test client conventions, or plain KernelBrowser with explicit Accept headers — content negotiation is the most common source of confusing test failures. Snapshot the exported OpenAPI document in CI: it catches accidental breaking changes to the public contract that unit tests miss.

Upgrade cadence. This package moves fast and the 4.x line carries real deprecations (legacy filters, AbstractFilter, the Symfony AccessDeniedException, the jsonapi.use_iri_as_id default). Budget a small, regular slice of time for running the codemods and reading the changelog rather than a big-bang 5.0 migration — 5.0 removes the deprecated APIs wholesale.

Gotchas and Tips

Metadata is cached aggressively. Attribute changes that "don't take effect" are almost always a stale cache. bin/console cache:clear first, then debug:router and api:openapi:export to see what the metadata compiler actually produced. In CI, never rely on a warm cache built from a different branch.

Filters vs. name converters vs. DQL. A recurring class of bugs: filter properties are declared in serialized (camelCase or snake_case) names but must resolve to PHP/DQL property names. There have been multiple fixes here (4.3.16 fixed both the DQL property name with a name converter and the OpenAPI document inheriting a name converter). If a filter silently returns unfiltered results or throws a DQL error, check that filter property names match the PHP property names and that your name converter is applied consistently.

IRI vs. identifier in JSON:API. The jsonapi.use_iri_as_id default is being deprecated (4.4). If you have clients keyed to id format, pin the setting explicitly now and test both directions of relation denormalization — including union-typed and nullable collections, which received several fixes across 4.3.x.

Relations: IRI, embedded, or eager? The three failure modes are (a) massive payloads from embedded relations, (b) N+1 queries from IRI resolution, (c) circular references. Decide per-property: readableLink: false for "just a reference", fetchEager/joins for "always needed", and explicit serializer depth limits for the rest. fetch_data: false on references is a performance tool for writes, and there have been fixes around uri-variable and parent-link handling there — verify link generation after upgrading.

Security exceptions. The Symfony AccessDeniedException path is deprecated in 4.4 in favor of the library's own. If you catch exceptions globally (e.g., a listener that maps to Problem Details), audit it during upgrade or your 403s may change shape.

OpenAPI quirks. The generated document is a build artifact with its own bugs (entrypoint exposure, $ref serialization, jsonapi relationship schemas, parameters without name in YAML config). Treat the doc as testable output: export it in CI and diff it. For parameters defined in YAML, always set name explicitly — a clear error is thrown but only at generation time.

Custom filters: clone before mutating. Parameter objects are shared; mutating them in place leaks state between requests/operations. There was a specific fix for this in security and validator providers (4.3.16). Follow the same discipline in your own filters/providers. BackwardCompatibleFilterDescriptionTrait is now documented public API — use it if you maintain filters across versions.

Version and dependency constraints bite. From 4.4, symfony/* is pinned to ^7.4 || ^8.0 and Symfony 6.4/7.0–7.3 support is dropped. Laravel support is stabilized in 4.4, but inter-package versions must align (a broken api-platform/laravel install was traced to a metadata constraint). Pin the whole api-platform/* family to the same minor and upgrade them together.

Laravel specifics. The Laravel integration is newer and moving (metadata dumping without a DB landed in 4.3.14). Expect the Symfony path to be better documented; read the changelog entries tagged laravel before upgrading.

Debugging toolkit. debug:config api_platform, debug:router, api:openapi:export, the profiler's API Platform panel (request→operation→provider→normalizer trace), and debug:container --tag=api_platform.* for extension points. When behavior is surprising, the question is nearly always "which operation did the metadata resolve to?" — print the operation name and URI template before anything else.

Extension points worth knowing. Decorate providers, processors, metadata factories, and normalizers rather than subclassing. Prefer #[Provide]/#[Process] over global decoration when the behavior is resource-specific — it keeps the customization co-located with the resource and avoids surprising other endpoints.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tobya/saloonfire
kjmtrue/laravel-debugbar
hexincorp/debugbar
razorbacks/laravel-shibboleth
adilazhari/laravel-idempotency
mamitech/laravel-slow-query-log
mamitech/laravel-http-logger
medeirosrafael/docxmustache
webdevlabs/phreak
vis/log-envelope
digitickets/lalit
acacha/names
scool/foundation
frankkessler/guzzle-oauth2-middleware
delatbabel/site-config
delatbabel/applog
eusonlito/laravel-gettext
henzeb/housepedia-intelligence-laravel
aliqsm/laravel-authkit
itsjustvita/laravel-wp-migrator