---
title: Deleting records safely
order: 11
updated: 2026-07-12
---

# Deleting records safely

Master records are the shared lists the rest of the app points at — a customer sits behind every enquiry, a product code behind every quotation and stock movement, a warehouse rack behind every arrival. Because so much history depends on them, the app never lets you delete a record that is still in use. When you press *Delete*, the server first scans every collection for anything that still references the record. If it finds something, the delete is held back and a *references window* opens listing exactly where the record is used, so you can clean up first (or, in the cases where it is safe, proceed anyway).

This page explains which records are guarded, what the references window shows, the difference between a *hard block*, a *warning*, and an *awareness* prompt, and the plain-banner cases that resolve differently.

This check is enforced entirely on the server, from a single firm-wide reference map. A forged request that skips the front-end cannot delete a referenced record — the delete endpoint re-runs the same scan and refuses. The reference map is the same one the firm-backup system uses, so every new record type and every new link is covered automatically; nothing silently slips through.

## Which records are guarded

The safe-delete check runs when you delete any of these master records:

- *Customers* and their *Contact Persons* — see [Customers](/docs/records/customers) and [Customer contacts](/docs/records/customer-contacts).
- *Suppliers'* *Contact Persons* — see [Supplier contacts](/docs/records/supplier-contacts). (A *Supplier* itself has no delete action at all — you can only edit one. See the [Common questions](#can-i-delete-a-supplier).)
- *Products* — a *Product Head* and each *Product Code* under it — see [Products](/docs/records/products) and [Product codes](/docs/records/product-codes).
- *Manufacturers* (makes) and *Product Units* — see [Manufacturers](/docs/records/manufacturers) and [Utilities](/docs/records/utilities).
- *Additional Charge* types (firm-admin only).
- *Warehouses* (godowns), *Rooms*, and *Racks* — see [Warehouses](/docs/records/warehouses) and [Warehouse rooms & racks](/docs/records/warehouse-rooms).
- *Enquiry Folders*.
- HR reference lists — *Leave Types*, *Holidays*, and *Expense Categories* — see [HR settings](/docs/human-resources/hr-settings).
- *Users* (a boss removing a subordinate).
- *Email Accounts* — see [Document templates](/docs/settings/document-templates#email-accounts-and-sending-policy).

Each delete also requires the matching permission; without it the button is hidden and the server returns *Forbidden*. The exact permission per record is listed under [Record-by-record](#record-by-record) below.

## Open the check

You never open the check by hand — it runs automatically:

1. Find the record in its list (or in the manager modal for makes, units, and warehouse rooms).
2. Press its *Delete* action and confirm the plain *Delete …?* prompt.
3. If the record is unused, it is removed immediately with a success toast.
4. If it is still referenced, the delete is refused and the references window (or a plain banner) appears instead. Nothing is deleted until you resolve it.

## The references window

When a delete is held back for reference reasons, a window opens. It is the same window in every case (the docs call it the *Where Is This Used?* window), but its heading, wording, and buttons change with how serious the situation is. There are three situations.

### Hard block — you cannot proceed

For a plain delete where keeping the record is the only safe option, the window is a pure block:

- Heading: *Unable to delete — references found* (shown in red).
- Body: *"…is referenced by the records below. Please remove or reassign them before deleting."*
- Buttons: only *Close*.

There is no way to force the delete. Resolve the references first — open each referenced record and point it at a different value, or delete those records if they too are unwanted — then try the delete again. This is what you get for a *Customer*, *Contact Person*, *Product Head*, *Product Code*, *Make*, *Unit*, *Additional Charge*, *Godown*, *Room*, *Rack*, *Enquiry Folder*, *Leave Type*, and a referenced *Email Account*.

### Warning — you can proceed if you accept the trade-off

For a change where the old, historical entries are allowed to keep their existing value, the window offers a way through:

- Heading: *Heads up — references found* (shown in amber).
- Body: *"…is still referenced by the records below. New documents won't be able to use it, but historical entries will keep their existing reference."*
- Buttons: *Close* and *Proceed anyway (history keeps reference)*.

Pressing *Proceed anyway* re-runs the action with a cascade flag set; the change goes through and the old records keep their existing value (which will now show as a missing lookup). You get this for deleting a *Holiday* that only has **unused** comp-off credits, deleting an *Expense Category* that appears on old vouchers, and for the change-flow cases below (removing a make from a product head or supplier).

### Awareness — you confirm and proceed

Used only for the *User* soft-delete. The check never blocks; it just shows you what the person is connected to so you can decide:

- Heading: *This record is referenced elsewhere*.
- Body: *"…appears in the records below. Deleting will not remove it from existing entries — they will keep their historical reference."*
- Buttons: *Cancel* and *Delete anyway*.

Pressing *Delete anyway* re-fires the delete with a confirmed flag and completes the soft-delete.

### Reference tabs

Whatever the situation, the middle of the window is the same: one tab per collection that still references the record, sorted with the busiest collection first. Each tab label reads as the collection's friendly plural name followed by the true total in brackets — for example *Enquiries (12)*, *Purchase Orders (3)*, *Stock Deliveries (1)*.

Inside a tab you see a small table of the **latest 5** matching records (newest first), regardless of how large the total is — the tab count already tells you the full number, and there is no page-through. Each row shows context columns appropriate to that collection (numbers, names, dates) and a rightmost *Open* button.

> Empty collections never appear — a tab only exists when its count is at least `1`.

### The *Open* button

The *Open* button (an outward-arrow icon) on each row jumps straight to that specific referencing record so you can fix it:

- *Enquiries*, *Orders*, and *Purchase Orders* open as full pages.
- *Customers*, *Suppliers*, *Product Heads*, *Product Codes*, *Users*, *Stock Arrivals*, *Stock Adjustments*, *Assembly Costs*, and *Stock Deliveries* open in a stacked edit/view modal on top of the window, so closing it returns you to the list.
- *Makes* and *Units* open their manager modal (there is no single-item view for them).
- Notes, edit-logs, and drafts open their parent enquiry or purchase order.
- Follow-up queue jobs open the relevant *Follow-up* page.
- A few leaf-level rows (contact persons, comp-off ledger entries, email logs, storage locations) have no direct viewer — pressing *Open* copies the record's ID to your clipboard and shows a toast so you can search for it.

The window stays open behind whatever you opened, so you can work through several references in a row.

## Plain-banner cases

Some delete attempts never reach the references window — they resolve with a single toast banner instead, because the fix is one obvious follow-up action.

### Delete child records first

A parent that still contains children is blocked with a plain message telling you how many children to remove first. This happens for:

- A *Customer* that still has *Contact Persons* — *"Cannot delete customer. This customer has associated contact persons."*
- A *Product Head* that still has *Product Codes* — *"Delete the N child product code(s) first."*
- A *Godown* that still has *Rooms* — *"Cannot delete: this godown has N room(s). Remove all rooms first."*
- A *Room* that still has *Racks* — *"Cannot delete: this room has N rack(s). Remove all racks first."*

Open the children list and delete them one at a time. Each child delete runs through its own full safe-delete check, so you may hit the references window there. Once the last child is gone, the parent delete proceeds (or surfaces its own remaining references).

### Pending mail (email accounts)

An *Email Account* is blocked from deletion while any queued outgoing mail still points at it — *"Cancel pending mails first before deleting this email account."* This covers pending quotation, purchase-order, delivery-receipt, arrival-receipt, and vendor-dispatch jobs that are still `QUEUED` or `PROCESSING`. A job that has already sent or has permanently failed does **not** block, and past sent-mail history (the email log) never blocks — you are free to delete an account you once sent from. Cancel the pending jobs from their respective follow-up/queue screens first, then delete the account. See [Email accounts and sending policy](/docs/settings/document-templates#email-accounts-and-sending-policy).

## Record-by-record

The behaviour and required permission for each guarded record:

### Customer

Permission: *Delete Customer* (`database.deleteCustomer`). First blocked by a plain banner if it still has contact persons; otherwise a **hard block** if any enquiry, order, or other record references it.

### Contact person (customer or supplier)

Permission: *Delete Contact Person* (`database.deleteCP`). **Hard block** if any enquiry, order, purchase order, commission entry, or similar still names the contact person.

### Product head

Permission: *Delete Product Head* (`database.deleteProductHead`). First blocked by a plain banner if it still has child product codes; otherwise a **hard block** if it is referenced elsewhere (for example, a supplier's product categories). Deleting a head no longer cascade-deletes its codes — you must delete each code yourself so its own history is checked.

### Product code

Permission: *Delete Product Code* (`database.deleteProductCode`). **Hard block** if referenced by any enquiry, order, purchase order, stock arrival/delivery/transfer/adjustment/conversion, assembly cost, vendor quotation, or draft.

### Make (manufacturer)

Permission: *Edit Product Head* (`database.editProductHead`). **Hard block** if the make appears on any enquiry, order, PO, RFQ line, stock movement, product code, product head's allowed makes, or supplier's makes.

### Product unit

Permission: *Edit Product Head* (`database.editProductHead`). **Hard block** if the unit is used on any product head, product code, or line item across enquiries, orders, POs, stock movements, or assembly costs.

### Additional charge type

Permission: **firm administrator only**. **Hard block** if any enquiry, order, or purchase order still carries the charge.

### Godown, room, rack

Permissions: a *Godown* needs administrator **or** the *Manage Godowns* permission at `all` scope; a *Room* needs administrator **or** *Manage Godowns* scoped to its parent godown; a *Rack* needs administrator **or** *Manage Rooms* scoped to its parent room, **or** *Manage Godowns* scoped to its grandparent godown. Each is first blocked by a plain banner if it still holds child rooms/racks, then a **hard block** if any historical stock movement (arrival, delivery, transfer, adjustment, conversion) references the location. See [Storage locations](/docs/inventory/storage-locations).

### Enquiry folder

**Hard block** if any enquiry or enquiry draft is still filed in the folder. Move or delete those enquiries first. See [Folders](/docs/enquiry-bank/folders).

### Leave type

Permission: administrator **or** *Leave Type* management (`attendence.leaveTypeCrud`). **Hard block** if any historical leave application or salary revision references it. To stop offering it for new applications without deleting the history, deactivate it from its edit screen instead. See [HR settings](/docs/human-resources/hr-settings).

### Holiday

Permission: administrator **or** *Manage Holidays* (`attendence.manageHolidays`). Two outcomes:

- If any comp-off credit earned on that day has already been **used** in an approved leave → **hard block** (that leave cannot be unwound).
- If only **unused** comp-off credits exist → **warning**. *Proceed anyway* deletes the holiday and its unused credits together in one atomic step. With no credits at all, the holiday deletes straight away.

### Expense category

Permission: administrator **or** *Expense Category* management (`expense.expenseCategoryCrud`). If existing vouchers use it → **warning** with an extra *Deactivate instead* button:

- *Proceed anyway* hard-deletes the category; old vouchers keep their category ID but lose the category name in lookups.
- *Deactivate instead* keeps the category and its history but hides it from new vouchers (it runs the normal edit endpoint with the active flag off).

### User (subordinate)

You can only delete a **direct report** — lateral or upward deletes are rejected. The delete is a **soft-delete with awareness**: the first attempt shows every place the user appears across the firm (enquiries and orders they created, leave applications, expense vouchers, and any subordinates who report to them) and never blocks. *Delete anyway* soft-deletes the user, ends all their sessions, and frees their username. Their historical records keep pointing at the deleted user for audit. See [Users & permissions](/docs/settings/users-and-permissions).

### Email account

Firm accounts need the *Manage Email Configuration* permission (or administrator); a personal account can be removed by its owner. **Hard block** only while pending outgoing mail references the account, as described under [Pending mail](#pending-mail-email-accounts).

## Change-flow checks

The same references window also guards **edits** that would strand data — not just deletes. These fire when you save a change that removes an option something is still using:

- **Removing a make from a product head's allowed makes** — a **warning**, scoped to product codes under that head. Old enquiries and documents keep the make; *Proceed anyway* lets the removal through.
- **Removing a make or a product category from a supplier** — a **warning**, scoped to that supplier's purchase orders and arrivals. Historical entries keep their value; *Proceed anyway* saves the change.
- **Removing a unit from a product head** — a **hard block** if any line item under that head still uses the unit. There is no proceed option; resolve the references first. (The old bulk "swap this unit for another everywhere" helper has been retired — you resolve references by hand, which is safer and works the same for every record type.)
- **Moving a product code to a different head** — blocked if the new head does not already allow everything the code is currently using (its makes, HSN code, or tax rate). The window lists what the new head is missing; add those to the new head first, or leave the code where it is.

In each case the window behaves exactly as described above — same tabs, same *Open* buttons.

## Why the check matters

Two years from now you may not remember that a particular *Make* was used on a stock conversion in 2024. If the app let you delete that make, the old conversion would still hold its ID but the make itself would be gone — opening the record later would show a blank value, and any report or aggregation built from it could break. Multiply that across customers, product codes, units, and storage locations and you get silent data rot. The safe-delete check makes that impossible: a record can only leave once nothing depends on it, or (in the warn and awareness cases) only once you have explicitly accepted that old entries will keep a now-nameless reference.

## Common questions

### What do the tabs and the number in brackets mean?

Each tab is one type of record that still uses the thing you tried to delete; the bracketed number is how many of that type reference it. The table under a tab shows only the latest 5 of them — the count is the true total. Use the *Open* button on a row to jump to that record and fix it.

### It says "up to 5" — how do I see the rest?

You don't need to. The count in the tab tells you the full number, and you resolve references by editing the actual records, not from this window. Fix the visible ones, retry the delete, and the window reappears with the next batch until the count reaches zero.

### What is the difference between *Close* and *Proceed anyway*?

*Close* cancels — nothing is deleted or changed. *Proceed anyway* only appears in the warning and awareness cases, where letting old records keep their existing (now-nameless) reference is acceptable. It re-runs the action and completes it. In a hard block there is no *Proceed anyway* — you must clear the references first.

### Can I delete a supplier?

No — suppliers have no delete action; you can only edit one. Removing a supplier's makes or product categories on the edit screen runs the change-flow warning check. To retire a supplier, edit it so it is no longer used. See [Suppliers](/docs/records/suppliers).

### The delete button is missing on a record — why?

Deleting each record type needs its own permission (listed under [Record-by-record](#record-by-record)). If you lack it, the button is hidden, and the server would refuse the delete even if it were sent. Ask your administrator to grant the relevant permission — see [Users & permissions](/docs/settings/users-and-permissions).

### I deleted a user but their old enquiries still show their name — is that a bug?

No. A user delete is a soft-delete for audit safety: their historical records keep pointing at them so past work stays attributable. The account is disabled, signed out, and its username freed for reuse.

### Why can't I delete an email account I no longer use?

Only pending outgoing mail blocks it. Cancel any queued quotation, purchase-order, delivery-receipt, arrival-receipt, or vendor-dispatch mail that still uses the account, then delete it. Past sent mail never blocks. See [Email accounts and sending policy](/docs/settings/document-templates#email-accounts-and-sending-policy).

### I removed a make/unit and got a references window on save, not on delete — why?

Because removing an option from a product head or supplier is the same kind of risk as deleting it: something may still be using it. Removing a make gives a warning you can proceed past (history keeps the make); removing a unit that is still in use is a hard block. See [Change-flow checks](#change-flow-checks).
