= delete()
:description: Deletes an ECM object via dms.XMLDelete. Defaults to soft-delete (recycle bin); pass hard_delete=True for permanent removal. Accepts a model instance or (object_id, object_type).
:keywords: delete, XMLDelete, recycle bin, hard_delete, soft-delete, object_id, object_type
:page-tags: api, dms

Deletes an existing ECM object.
By default the object is moved to the recycle bin — with `hard_delete=True` it is permanently removed.

The object to delete can be identified either by a model instance or directly by `object_id` and `object_type`.

== Signature

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.delete(
    model: ECMFolderModel | ECMRegisterModel | ECMDocumentModel | int,
    object_type: int | str | None = None,
    *,
    hard_delete: bool = False,
    delete_cascading: bool = False,
) -> None
----

Async::
+
[source,python]
----
await ecm.dms.delete(
    model: ECMFolderModel | ECMRegisterModel | ECMDocumentModel | int,
    object_type: int | str | None = None,
    *,
    hard_delete: bool = False,
    delete_cascading: bool = False,
) -> None
----
====

== Parameters

[cols="1,1,3"]
|===
|Parameter |Default |Description

|`model`
|—
|The object to delete — either a model instance with `id` set,
or a plain `int` object ID (in which case `object_type` must be provided).

|`object_type`
|`None`
|Only required when `model` is a plain `int`. Identifies the object type:
`int` for a numeric type ID or `str` for the internal name.

|`hard_delete`
|`False`
|When `True`, permanently deletes the object (`HARDDELETE=1`) instead of moving it to the recycle bin.

|`delete_cascading`
|`False`
|When `True`, recursively deletes all child objects as well (`DELETECASCADING=1`).
Useful for folders or registers with nested content.
|===

== Exceptions

`ValueError`:: `model` is a plain `int` but `object_type` was not provided.
`ValueError`:: `model` is a model instance with `id = None`.

== Examples

=== Delete after loading

The most common case: load the object first, then delete it.

[tabs]
====
Sync::
+
[source,python]
----
folder = ecm.dms.get(InvoiceFolder, 12345)
ecm.dms.delete(folder)
----

Async::
+
[source,python]
----
folder = await ecm.dms.get(InvoiceFolder, 12345)
await ecm.dms.delete(folder)
----
====

=== Delete by object_id and internal name

If the ID is known, the object does not need to be fully loaded.
Pass the internal name of the object type as `object_type`:

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.delete(12345, "InvoiceFolder")
----

Async::
+
[source,python]
----
await ecm.dms.delete(12345, "InvoiceFolder")
----
====

=== Delete by object_id and numeric type ID

Alternatively, use the numeric type ID when the internal name is not known:

[tabs]
====
Sync::
+
[source,python]
----
# Determine the type ID e.g. via get_object_type_by_id()
type_id = ecm.dms.get_object_type_by_id(12345)
ecm.dms.delete(12345, type_id)
----

Async::
+
[source,python]
----
type_id = await ecm.dms.get_object_type_by_id(12345)
await ecm.dms.delete(12345, type_id)
----
====

=== Permanent delete (bypass recycle bin)

With `hard_delete=True` the object is removed permanently and cannot be restored:

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.delete(12345, "InvoiceFolder", hard_delete=True)
----

Async::
+
[source,python]
----
await ecm.dms.delete(12345, "InvoiceFolder", hard_delete=True)
----
====

=== Delete a folder with its entire subtree

With `delete_cascading=True` all child registers and documents are deleted as well:

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.delete(42, "InvoiceFolder", delete_cascading=True)
----

Async::
+
[source,python]
----
await ecm.dms.delete(42, "InvoiceFolder", delete_cascading=True)
----
====

=== Delete all results of a query

Delete objects in batches: each iteration fetches a limited number of objects and deletes them,
repeating until no more results are returned. This avoids loading everything into memory at once
and sidesteps the paging inconsistencies of `stream()` when objects are deleted while iterating.

[tabs]
====
Sync::
+
[source,python]
----
while True:
    batch = (
        ecm.dms.select(InvoiceFolder)
        .where(InvoiceFolder.Year == 2020)
        .limit(100)
        .execute()
    )
    if not batch:
        break
    for folder in batch:
        ecm.dms.delete(folder, hard_delete=True, delete_cascading=True)
----

Async::
+
[source,python]
----
while True:
    batch = await (
        ecm.dms.select(InvoiceFolder)
        .where(InvoiceFolder.Year == 2020)
        .limit(100)
        .execute()
    )
    if not batch:
        break
    for folder in batch:
        await ecm.dms.delete(folder, hard_delete=True, delete_cascading=True)
----
====

== See also

* xref:api/dms/get.adoc[get()] — Load a single object by its ID
* xref:api/dms/select.adoc[select()] — Query objects with a typed model
* xref:api/dms/insert.adoc[insert()] — Create a new object
