DEV Community

Busra Sengul
Busra Sengul

Posted on • Edited on

Deleting Umbraco Forms(13) Entries via SQL

When working with Umbraco Forms, it's common to accumulate a large number of form submissions over time — especially on high-traffic sites. While Umbraco provides a UI to manage entries, bulk deletion (especially based on date) isn't available out of the box.

What About Scheduled Deletion?

Umbraco Forms does include a scheduled record deletion feature, which allows you to configure automatic cleanup of form entries after a specified number of days. This is a great option for routine maintenance, especially when you want to apply a blanket policy across all forms.

However, there are scenarios where more granular control is needed — for example, deleting entries from specific forms, targeting a custom date range, or integrating cleanup into a broader data management workflow. In such cases, a custom SQL-based approach offers the flexibility and precision required.

In this post, we’ll walk through how to safely delete form entries for Umbraco Forms 13, before a specific date using SQL, while maintaining referential integrity across the related tables.

Understanding the Umbraco Forms Data Structure

Umbraco Forms stores form submissions across several tables:

UFRecords: Each form submission (entry) is stored here.
UFRecordFields: Links each field in a form to a specific submission.
UFRecordAudit: This table logs changes made to form entries via the Umbraco backoffice. It helps track who edited what and when — useful for auditing and compliance.
UFRecordDataXXX: Stores the actual field values, split by data type:

  • UFRecordDataString
  • UFRecordDataLongString
  • UFRecordDataInteger
  • UFRecordDataBit
  • UFRecordDataDateTime

Each UFRecordDataXXX table uses a Key column (a GUID) that maps to the Id of a record in UFRecordFields.

The Challenge

To delete a form entry, you must:

Remove all related field values from the UFRecordDataXXX tables.
Remove the corresponding rows from UFRecordFields.
Finally, delete the entry from UFRecords.
Failing to follow this order can lead to orphaned data or foreign key constraint errors.

The Solution: SQL Script for Safe Deletion

Here’s the SQL script we used to delete all entries created before a specific date (e.g., 2024-01-01):

-- Set the cutoff date
DECLARE @CutoffDate DATETIME = '2024-01-01';

-- Step 1: Delete from UFRecordDataXXX tables using JOINs
DELETE ds
FROM UFRecordDataString ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;

DELETE ds
FROM UFRecordDataLongString ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;

DELETE ds
FROM UFRecordDataInteger ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;

DELETE ds
FROM UFRecordDataBit ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;

DELETE ds
FROM UFRecordDataDateTime ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;


-- Step 2: Delete from UFRecordAudit
DELETE a
FROM UFRecordAudit a
JOIN UFRecords r ON a.Record = r.Id
WHERE r.Created < @CutoffDate;

-- Step 3: Delete from UFRecordFields
DELETE f
FROM UFRecordFields f
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate;

-- Step 4: Delete from UFRecords
DELETE FROM UFRecords
WHERE Created < @CutoffDate;
Enter fullscreen mode Exit fullscreen mode

Targeting a Specific Form

If you only want to delete entries for a specific form, you’ll need to filter by the form’s unique identifier (GUID), which is stored in the Form column of the UFRecords table.

You can find the form’s GUID in the Umbraco backoffice or by querying:

SELECT DISTINCT [Key], [Name] FROM UFForms;
Enter fullscreen mode Exit fullscreen mode

Once you have the form’s GUID, you can modify the SQL script like this:

-- Example: Only delete entries for a specific form
DECLARE @CutoffDate DATETIME = '2024-01-01';
DECLARE @FormId UNIQUEIDENTIFIER = 'YOUR-FORM-GUID-HERE';

-- Step 1: Delete from UFRecordDataXXX tables
DELETE ds
FROM UFRecordDataString ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

-- Repeat for other UFRecordDataXXX tables...
DELETE ds
FROM UFRecordDataString ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

DELETE ds
FROM UFRecordDataLongString ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

DELETE ds
FROM UFRecordDataInteger ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

DELETE ds
FROM UFRecordDataBit ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

DELETE ds
FROM UFRecordDataDateTime ds
JOIN UFRecordFields f ON ds.[Key] = f.[Key]
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);


-- Step 2: Delete from UFRecordAudit
DELETE a
FROM UFRecordAudit a
JOIN UFRecords r ON a.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

-- Step 3: Delete from UFRecordFields
DELETE f
FROM UFRecordFields f
JOIN UFRecords r ON f.Record = r.Id
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

-- Step 4: Delete from UFRecords
DELETE FROM UFRecords
WHERE r.Created < @CutoffDate And (@FormId IS NULL or r.Form = @FormId);

Enter fullscreen mode Exit fullscreen mode

This approach gives you precise control over which form’s data is deleted — ideal for scenarios where different forms have different retention policies.

Safety Tips

Always back up your database before running deletion scripts.
Test the script in a local/dev environment first.
Consider wrapping this logic in a stored procedure or SQL Agent job for scheduled cleanups.

Bonus: Make It Reusable

You can easily convert this into a stored procedure:

CREATE PROCEDURE DeleteOldUmbracoFormEntries
    @CutoffDate DATETIME,
    @FormId UNIQUEIDENTIFIER = NULL
AS
BEGIN
    -- [Insert the full script here]
END
Enter fullscreen mode Exit fullscreen mode

Conclusion

Cleaning up old form entries in Umbraco Forms can help improve performance and reduce storage overhead. With a clear understanding of the data structure and a safe deletion strategy, you can confidently manage your form data at scale.

Hope this helps to someone :)

Top comments (4)

Collapse
 
andybutland profile image
Andy Butland

Thanks for sharing Busra, very useful. Just for completeness I thought to mention two further tables that you'll likely also need to delete from if removing from UFRecords.

  • UFRecordAudit - tracks edits made in the backoffice
  • UFRecordWorkflowAudit tracks workflow completion and retries

You may not have any records in them depending on your setup, but if you do, am pretty sure you'll get a foreign key constraint error when deleting from UFRecords if there are related records in these tables. So you might want to add them to your script too.

Collapse
 
busrasengul profile image
Busra Sengul

Thank you @andybutland
I will update my script and blog accordingly!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

pretty cool seeing this broken down so clean - honestly, i always wonder, you think the real struggle is just keeping on top of old data or is it more about building better habits around cleanup from the start

Collapse
 
busrasengul profile image
Busra Sengul

Definitely!
As I added, there's a scheduled cleanup already which would prevent this problem from the begining.