@@ -80,3 +80,87 @@ class Tag
8080This allows a parent resource to reference `Tag` objects in its relationships while `Tag` itself has
8181no public item endpoint. The `NotExposed` operation requires a `uriTemplate` with a single URI
8282variable.
83+
84+ # # Client-Generated IDs
85+
86+ The [JSON:API specification allows clients to supply their own `id` on `POST`](https://jsonapi.org/format/#crud-creating-client-ids)
87+ when the server agrees to accept it. This is useful when the client generates a UUID before
88+ sending the request and needs the server to persist it as-is.
89+
90+ API Platform refuses client-supplied `data.id` on `POST` by default. Accepting arbitrary IDs on a
91+ public endpoint without opt-in is a footgun : a malicious client could overwrite existing records or
92+ enumerate internal identifiers. Sending `data.id` on a `POST` without opt-in results in a `400`
93+ response.
94+
95+ # ## Enabling Globally
96+
97+ **Symfony:**
98+
99+ ` ` ` yaml
100+ # config/packages/api_platform.yaml
101+ api_platform:
102+ jsonapi:
103+ allow_client_generated_id: true
104+ ` ` `
105+
106+ **Laravel** (`config/api-platform.php`):
107+
108+ ` ` ` php
109+ 'jsonapi' => [
110+ 'allow_client_generated_id' => true,
111+ ],
112+ ` ` `
113+
114+ # ## Enabling Per Operation
115+
116+ Use the `denormalizationContext` on the `#[Post]` operation to enable the feature for a single
117+ endpoint without affecting the rest of the API :
118+
119+ ` ` ` php
120+ <?php
121+
122+ namespace App\A piResource;
123+
124+ use ApiPlatform\J sonApi\S erializer\I temNormalizer;
125+ use ApiPlatform\M etadata\A piResource;
126+ use ApiPlatform\M etadata\G et;
127+ use ApiPlatform\M etadata\P ost;
128+
129+ #[ApiResource(
130+ formats: ['jsonapi' => ['application/vnd.api+json']],
131+ operations: [
132+ new Get(uriTemplate: '/books/{id}'),
133+ new Post(
134+ uriTemplate: '/books',
135+ denormalizationContext: [ItemNormalizer::ALLOW_CLIENT_GENERATED_ID => true],
136+ ),
137+ ],
138+ )]
139+ class Book
140+ {
141+ public ?string $id = null;
142+ public string $title = '';
143+ }
144+ ` ` `
145+
146+ A request that supplies `data.id` is then accepted :
147+
148+ ` ` ` http
149+ POST /api/books HTTP/1.1
150+ Accept: application/vnd.api+json
151+ Content-Type: application/vnd.api+json
152+
153+ {
154+ "data": {
155+ "type": "Book",
156+ "id": "01932b4c-a3f1-7b7e-9e5b-3d8f1c2e4a6d",
157+ "attributes": {
158+ "title": "Hyperion"
159+ }
160+ }
161+ }
162+ ` ` `
163+
164+ The supplied `id` is passed to the entity's `id` setter. The processor is responsible for
165+ persisting it. The response output schema still requires `id`; only the `POST` input schema
166+ marks it as optional.
0 commit comments