S
Crud

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:

TypeScript
// 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.

HandlerDeletesInput
removeOne documentThe id in the URL
removeMultipleThe listed documents, 1 to 100 of themA JSON body with a list of ids
removeAllEvery document the caller may deleteNothing

Delete One Document

A request names the document in the URL and needs no body:

Text
DELETE /api/v1/products/delete/665f1c2e8b3a4d0012a10001
Text
HTTP/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. 1
    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.
    2. Your before actions run. Any of them can stop the request by throwing an error.
    3. 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.
    4. On an admin-or-owner route, the handler checks that the caller owns the document or is an administrator. Otherwise it answers 403.
    5. The document is deleted, and the files attached to it are removed from storage.
    6. Your after actions run, and the handler answers 200 with the deleted document.
No Undo

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:

Text
DELETE /api/v1/products/delete-many
Content-Type: application/json

{
  "ids": [
    "665f1c2e8b3a4d0012a10004",
    "665f1c2e8b3a4d0012a10003"
  ]
}
Text
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:

Text
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.

Send Ids With POST

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:

Text
DELETE /api/v1/products/delete-all
Text
HTTP/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:

Text
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:

Text
DELETE /api/v1/notes/delete-all
Text
HTTP/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:

authremoveremoveMultipleremoveAll
"admin"Administrators delete any documentAdministrators delete the documents they listAdministrators delete everything
"admin-or-owner"An owner deletes their own document, and an administrator deletes any. Anyone else gets 403An owner deletes the listed documents they own, and an administrator deletes all of themAn owner deletes all of their own documents, and an administrator deletes everything
"authenticated"Any signed-in user deletes any documentAny signed-in user deletes any listed documentRefused when the app starts
"public"Anyone deletes any documentAnyone deletes any listed documentRefused when the app starts
Open Delete Routes

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:

Text
DELETE /api/v1/notes/delete-many
Content-Type: application/json

{
  "ids": [
    "665f1c2e8b3a4d0012a10002",
    "665f1c2e8b3a4d0012a10006"
  ]
}
Text
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

StatuscodeWhen it happensWhat to change
400INVALID_OBJECT_IDremove: 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 idSend valid ids
401ACCESS_TOKEN_MISSINGThe route needs sign-in and no token or cookie was sentSign in first
403ADMIN_REQUIREDAn admin route and a caller who is not an administratorSign in as an administrator
403ROLE_NOT_ALLOWEDThe route lists roles and the caller's role is not among themSign in with an allowed role
403OWNER_REQUIREDremove on a document that belongs to someone else, on an admin-or-owner routeDelete your own documents
404RESOURCE_NOT_FOUNDremove: no document has the idCheck the id

An editor who calls the admin-only delete route of the reference app gets this response:

Text
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.

:::