Skip to content

Commit 08da8e2

Browse files
committed
docs(jsonapi): document opt-in client-generated IDs
Add a "Client-Generated IDs" section to core/jsonapi.md covering the JSON:API spec allowance, the default refusal (security footgun), and how to opt in globally (Symfony YAML / Laravel config) or per-operation via denormalizationContext. References api-platform/core#7930 and spec §crud-creating-client-ids.
1 parent 5ef9d77 commit 08da8e2

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

core/jsonapi.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,87 @@ class Tag
8080
This allows a parent resource to reference `Tag` objects in its relationships while `Tag` itself has
8181
no public item endpoint. The `NotExposed` operation requires a `uriTemplate` with a single URI
8282
variable.
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\ApiResource;
123+
124+
use ApiPlatform\JsonApi\Serializer\ItemNormalizer;
125+
use ApiPlatform\Metadata\ApiResource;
126+
use ApiPlatform\Metadata\Get;
127+
use ApiPlatform\Metadata\Post;
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

Comments
 (0)