A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Sanjay Kumar
Oct 8, 2022
  6723
(7 votes)

Delete unused properties and content types in CMS 12

The purpose of this blog is to delete unused properties, content references, and content types programmatically and keep clean content.

Problem: I have created a block type (e.g. TeaserBlock) and using this block created multiple contents and used it in different places, but after some time the requirement was changed and I removed this block type completely from the code. So for cleanup, we need to remove this block type from the Admin --> Content Types area in CMS because it no longer exists. But when I tried to delete it, we got content reference warnings because we already created content using a specific block type and added those references at many places (e.g. Main Content Area of other pages).

Then the question comes to mind how to delete it? So I tried the following solution and fixed it.

Solution

We have two options two remove the missing content type and its references:

  1. Remove all references from each content type and then delete it from the Admin -> Content Types area. - This is a time-consuming activity because you need to visit and delete each content type (moving and emptying the Trash folder).
  2. Write the code and clean up it programmatically.

I am using the second (2) option to achieve this.

Where do you write the code? I will suggest in the Initialization Module or create a Schedule Job to delete the unused properties and content types. It’s totally up to you  :)

Delete the missing properties which are no longer exist in the code for Content Type (PageType/BlockType): (e.g. TeaserBlock -> Sub Title)

 private void DeleteUnUsedProperties()
 {
            var pageTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository<PageType>>();
            var propertyDefinitionRepository = ServiceLocator.Current.GetInstance<IPropertyDefinitionRepository>();
            foreach (var type in pageTypeRepository.List())
            {
                foreach (var property in type.PropertyDefinitions)
                {
                    if (property != null && !property.ExistsOnModel)
                    {
                        propertyDefinitionRepository.Delete(property);
                    }
                }

               this.DeleteContentType(type);
            }

            var blockTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository<BlockType>>();
            foreach (var type in blockTypeRepository.List())
            {
                foreach (var property in type.PropertyDefinitions)
                {
                    if (property != null && !property.ExistsOnModel)
                    {                     
                        propertyDefinitionRepository.Delete(property);
                    }
                }

                this.DeleteContentType(type);
            }
        }

Delete the content references and missing content type (e.g. TeaserBlock)

  private void DeleteContentType(ContentType contentType)
  {
            if (contentType.ModelType != null)
            {
                return;
            }

            if (contentType.Saved != null &&
                contentType.Created.HasValue &&
                contentType.Created.Value.Year > 2021 &&
                contentType.IsAvailable)
            {
                // Find and deletes the content based on type.
                var contentModelUsage = ServiceLocator.Current.GetInstance<IContentModelUsage>();
                var contentUsages = contentModelUsage.ListContentOfContentType(contentType);
                var contentReferences = contentUsages
                    .Select(x => x.ContentLink.ToReferenceWithoutVersion())
                    .Distinct();

                var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
                foreach (var contentItem in contentReferences)
                {
                    contentRepository.Delete(contentItem, true, EPiServer.Security.AccessLevel.NoAccess);
                }

                // Delete type of content.
                var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
                if (contentType.ID > 4)
                {
                    contentTypeRepository.Delete(contentType.ID);
                }
            }
  }

Note: CotentType.ID > 4 means this will exclude the system/predefined page types e.g. Root Page.

Please leave your feedback in the comment box.

Thanks for your visit!

Oct 08, 2022

Comments

Mark Stott
Mark Stott Oct 9, 2022 10:03 PM

The agency I'm with has recently performed a major rebrand for an existing Optimizely client and we ended up doing something very similar using the MigrationStep functionality.  In the case of content types we were removing, we used their original GUIDs that were defined within source code to then retrieve their type from the IContentTypeRepository, but otherwise performed the same actions.

Great article, it's a shame the UI doesn't let you clean up in use content types easier.

Praful Jangid
Praful Jangid Oct 11, 2022 10:09 AM

Nice work, keep doing the great work.

Jonas Boman
Jonas Boman Oct 14, 2022 07:17 AM

Great article Sanjay! Thanks for taking the time for writing it up!

Siddharth Gupta
Siddharth Gupta Oct 17, 2022 01:51 PM

Nice artilce Sanjay, something very handy for sure!

Jonas Carlbaum
Jonas Carlbaum Mar 14, 2024 04:03 PM

I used this, with modifications, works fairly well.

But, use with caution.

Consider FormContainerBlock has some properties not exisiting in code, and I used this in an initializable module, so the very important properties was removed at startup causing Forms to be unstable, causing some actions to fail!

I would consider this issue more or less being an Optimizely bug than a bug in this code, but, just giving some context about how it should be used with caution!

Please login to comment.
Latest blogs
Looking back at Optimizely in 2025

Explore Optimizely's architectural shift in 2025, which removed coordination cost through a unified execution loop. Learn how agentic Opal AI and...

Andy Blyth | Dec 17, 2025 |

Cleaning Up Content Graph Webhooks in PaaS CMS: Scheduled Job

The Problem Bit of a niche issue, but we are building a headless solution where the presentation layer is hosted on Netlify, when in a regular...

Minesh Shah (Netcel) | Dec 17, 2025

A day in the life of an Optimizely OMVP - OptiGraphExtensions v2.0: Enhanced Search Control with Language Support and Synonym Slots

Supercharge your Optimizely Graph search experience with powerful new features for multilingual sites and fine-grained search tuning. As search...

Graham Carr | Dec 16, 2025

A day in the life of an Optimizely OMVP - Optimizely Opal: Specialized Agents, Workflows, and Tools Explained

The AI landscape in digital experience platforms has shifted dramatically. At Opticon 2025, Optimizely unveiled the next evolution of Optimizely Op...

Graham Carr | Dec 16, 2025

Optimizely CMS - Learning by Doing: EP09 - Create Hero, Breadcrumb's and Integrate SEO : Demo

  Episode 9  is Live!! The latest installment of my  Learning by Doing: Build Series  on  Optimizely Episode 9 CMS 12  is now available on YouTube!...

Ratish | Dec 15, 2025 |

Building simple Opal tools for product search and content creation

Optimizely Opal tools make it easy for AI agents to call your APIs – in this post we’ll build a small ASP.NET host that exposes two of them: one fo...

Pär Wissmark | Dec 13, 2025 |