Delete
Remove one, several or all documents with remove, removeMultiple and removeAll
Three handlers delete documents. remove deletes the document with the id in the URL, removeMultiple deletes the documents whose ids a request lists, and removeAll deletes every document that the caller may delete. Deleting is permanent. StarkForge keeps no trash and has no soft delete, so a deleted document is gone. This page shows how to declare the routes, how ownership limits what a caller can delete, and what each response and error looks like.
Declare the Delete Routes
Each handler has its own route. A remove route ends with the parameter :id, and the other two take no id in the URL:
// src/collections/products.ts
import { collection, defineRoutes, fields } from "@starklabs/forge";
import { z } from "zod";
export const products = collection({
type: "crud",
route: "products",
model: "Product",
schema: {
name: fields.requiredString,
price: fields.requiredNumber,
sku: { mongoose: { type: String, unique: true, sparse: true }, zod: z.string().optional() },
},
routes: defineRoutes([
{ method: "delete", path: "/delete/:id", handler: "remove", auth: "admin" },
{ method: "delete", path: "/delete-many", handler: "removeMultiple", auth: "admin" },
{ method: "delete", path: "/delete-all", handler: "removeAll", auth: "admin" },
]),
});The paths are yours to choose. This reference app uses /delete/:id, /delete-many and /delete-all, which makes DELETE /api/v1/products/delete/:id and its two siblings. None of the three routes needs validation.
| Handler | Deletes | Input |
|---|---|---|
remove | One document | The id in the URL |
removeMultiple | The listed documents, 1 to 100 of them | A JSON body with a list of ids |
removeAll | Every document the caller may delete | Nothing |
Delete One Document
A request names the document in the URL and needs no body:
DELETE /api/v1/products/delete/665f1c2e8b3a4d0012a10001HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"message": "Product deleted successfully.",
"data": {
"name": "Mechanical keyboard",
"price": 79.9,
"sku": "KB-001",
"_id": "665f1c2e8b3a4d0012a10001",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-15T10:30:00.000Z"
}
}The response carries the deleted document, with its private fields removed. The handler works through these steps in order, and stops at the first one that fails:
- 1
- The authenticator checks the access level and the roles. A caller who is not signed in gets 401, and one with the wrong role gets 403.
- Your
beforeactions run. Any of them can stop the request by throwing an error. - The id in the URL must be 24 hexadecimal characters, or the handler answers 400. The document is then loaded, and a missing document answers 404.
- On an
admin-or-ownerroute, the handler checks that the caller owns the document or is an administrator. Otherwise it answers 403. - The document is deleted, and the files attached to it are removed from storage.
- Your
afteractions run, and the handler answers200with the deleted document.
A deleted document is gone for good. Deleting a document also does not delete the documents that refer to it, so remove those documents yourself.
Delete Several Documents
removeMultiple reads a list of ids from the JSON body. The list is under the key ids, and config.targetField on the route sets another name. This request deletes two products:
DELETE /api/v1/products/delete-many
Content-Type: application/json
{
"ids": [
"665f1c2e8b3a4d0012a10004",
"665f1c2e8b3a4d0012a10003"
]
}HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"message": "2 Product items deleted.",
"data": {
"deleted": 2
}
}The list must hold 1 to 100 ids, and each one must be 24 hexadecimal characters. An id that appears twice counts once. The maximum of 100 is fixed. A list that breaks these rules answers 400:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"success": false,
"message": "'ids' must be an array of 1-100 valid ids.",
"code": "INVALID_OBJECT_ID",
"requestId": "84d071db-11bd-4e62-8cbf-dfa96a7c1e29"
}An id that matches no document is skipped without an error, so data.deleted is the only way to know how many documents were removed.
Some proxies and HTTP clients drop the body of a DELETE request. When yours does, declare the route with method: "post". The handler reads the same body and answers the same way.
Delete Everything
removeAll deletes every document that the caller may delete. It takes no body:
DELETE /api/v1/products/delete-allHTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"message": "2 Product items deleted.",
"data": {
"deleted": 2
}
}A request that could empty a whole collection must not be open to everyone, so StarkForge checks the route when the app starts. removeAll must use auth: "admin" or auth: "admin-or-owner", and any other value stops the server before it listens:
ConfigurationError (CONFIGURATION_ERROR): Invalid 'things' collection:
- DELETE /things/delete-all: 'removeAll' must use auth 'admin' or 'admin-or-owner' - otherwise any caller could wipe the whole collection.On an admin-or-owner route, removeAll deletes only the documents that the caller owns, and an administrator deletes the whole collection. When Rita calls it on the owned notes collection of the reference app, it deletes her two notes and leaves the notes of every other user alone:
DELETE /api/v1/notes/delete-allHTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"message": "2 Note items deleted.",
"data": {
"deleted": 2
}
}Choose Who Can Delete
The auth key on the route decides who can delete and, for admin-or-owner, which documents:
auth | remove | removeMultiple | removeAll |
|---|---|---|---|
"admin" | Administrators delete any document | Administrators delete the documents they list | Administrators delete everything |
"admin-or-owner" | An owner deletes their own document, and an administrator deletes any. Anyone else gets 403 | An owner deletes the listed documents they own, and an administrator deletes all of them | An owner deletes all of their own documents, and an administrator deletes everything |
"authenticated" | Any signed-in user deletes any document | Any signed-in user deletes any listed document | Refused when the app starts |
"public" | Anyone deletes any document | Anyone deletes any listed document | Refused when the app starts |
remove and removeMultiple accept authenticated and public routes. On such a route, whoever knows an id can delete that document. Use admin or admin-or-owner for deletes.
On an admin-or-owner route, removeMultiple silently skips every document that the caller does not own. This request from Rita lists one of her notes and one of Sam's. Only hers is deleted, and the response does not say why the other one was skipped:
DELETE /api/v1/notes/delete-many
Content-Type: application/json
{
"ids": [
"665f1c2e8b3a4d0012a10002",
"665f1c2e8b3a4d0012a10006"
]
}HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"message": "1 Note items deleted.",
"data": {
"deleted": 1
}
}The ownership rules are the same as for the other handlers, and Authorization covers them in full.
Files and Actions
StarkForge removes the files that a deleted document refers to from storage. When storage cannot remove a file, StarkForge logs the warning Could not remove a stored file. and the delete still succeeds, so that file stays in storage. Uploads covers storage providers.
Actions run around all three handlers. In an after action of remove, ctx.item is the deleted document. For removeMultiple and removeAll it is { deleted }. A before action runs first, before the document is loaded, so it cannot rely on the ownership check. Actions describes the full context.
Errors
| Status | code | When it happens | What to change |
|---|---|---|---|
| 400 | INVALID_OBJECT_ID | remove: the id in the URL is not 24 hexadecimal characters. removeMultiple: the list is missing, empty, longer than 100 or holds a value that is not an id | Send valid ids |
| 401 | ACCESS_TOKEN_MISSING | The route needs sign-in and no token or cookie was sent | Sign in first |
| 403 | ADMIN_REQUIRED | An admin route and a caller who is not an administrator | Sign in as an administrator |
| 403 | ROLE_NOT_ALLOWED | The route lists roles and the caller's role is not among them | Sign in with an allowed role |
| 403 | OWNER_REQUIRED | remove on a document that belongs to someone else, on an admin-or-owner route | Delete your own documents |
| 404 | RESOURCE_NOT_FOUND | remove: no document has the id | Check the id |
An editor who calls the admin-only delete route of the reference app gets this response:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"success": false,
"message": "Administrator access is required.",
"code": "ADMIN_REQUIRED",
"requestId": "72ff31ed-07b7-4736-b9eb-8428eea4cfda"
}Where to Go Next
Actions
Run your own code or send an email before or after a delete.
Authorization
Choose who can call a route and which documents they own.
:::