Delete
ids is always an array, even for a single record. By default this is a soft-delete: the record stops appearing in normal listings and searches (it simply is not in the results, the same as one that does not exist), but nothing is actually gone from the database. Listing it again requires withTrashed or onlyTrashed — see Search — which is itself gated behind the delete permission, not the read one. Each entry reports its own status — one record you don’t have delete on never blocks the others in the same request.
To undo this, see Restore.
POST /blog-posts/delete
Authorization: Bearer <token>
Content-Type: application/json
{
"ids": [
"cxmpl00000000000000000016"
]
}Body fields
Section titled “Body fields”| Field | Type | What it does |
|---|---|---|
ids |
array | The records to delete |
mode |
'soft' | 'hard' |
Defaults to soft |
Hard delete
Section titled “Hard delete”mode: 'hard' removes the row for good, not a soft flag — nothing to restore afterwards. Gated by its own admin-only capability, checked in addition to (never instead of) the delete permission above. A caller without it gets the usual 403 capability.forbidden, and no record is touched. On success, data is null for that entry: there is no row left to describe (see the “Suppression définitive” scenario above).
The real generated handler, from examples/blog-post/blog-post.controller.ts:
@Post('delete')
@Capability(canDeleteAnyBlogPost)
async remove(
@Req() req: RequestWithUser,
@Body(new ZodValidationPipe(deleteBlogPostRequestSchema))
body: DeleteBlogPostRequestBody,
) {
const subject = subjectOf(req.user);
const loaded = await this.loadAndAuthorize(body.ids, subject, (s, record) =>
canDeleteBlogPost(s, record as never),
);
if (body.mode === 'hard') {
const hardDecision = canHardDeleteBlogPost(subject);
if (!hardDecision.allowed) {
throw new CapabilityForbiddenException(hardDecision);
}
}
const entries = loaded.map(
(entry, index): BatchEntry<BlogPostView | null> => {
if (!entry.ok) {
return { index, id: entry.id, failure: entry.error };
}
return {
index,
id: entry.id,
run: async () => {
if (body.mode === 'hard') {
await this.blogPosts.hardDelete(entry.record);
return null;
}
return toBlogPostView(
await this.blogPosts.softDelete(entry.record),
);
},
};
},
);
const { results, meta } = await this.runBatch(entries, body.atomic);
return ok(results, meta);
}