How to write migrations for custom plugins and components

When you rename, reorganize, or replace custom plugins and components, existing plugin instances in the database still reference the old plugin_type. A Django data migration lets you update those references so that the CMS can find the correct plugin class again.

This guide covers the two most common scenarios:

  • Renaming a plugin (changing its class name or moving it to a different app).

  • Replacing one plugin with another.

Note

djangocms-frontend plugins share a single database table (djangocms_frontend_frontenduiitem) through proxy models. This means renaming a plugin does not require any schema migration – only the plugin_type (and optionally ui_item) fields need to be updated.

Using the management command

For one-off renames that you run manually on each environment, the frontend rename management command is the quickest option:

python -m manage frontend rename OldPlugin NewPlugin

See the management commands reference for details and validation rules.

Writing a data migration

If you want the rename to happen automatically when manage.py migrate is run – for example as part of a deployment pipeline – wrap it in a Django data migration.

Create an empty migration in your app:

python -m manage makemigrations --empty yourapp -n rename_old_plugin

Then add the rename logic using RunPython:

from django.db import migrations


def rename_plugin(apps, schema_editor):
    CMSPlugin = apps.get_model("cms", "CMSPlugin")
    CMSPlugin.objects.filter(
        plugin_type="OldPlugin",
    ).update(plugin_type="NewPlugin")

    # Only needed if the plugin model is based on AbstractFrontendUIItem
    FrontendUIItem = apps.get_model("djangocms_frontend", "FrontendUIItem")
    FrontendUIItem.objects.filter(
        plugin_type="NewPlugin",
    ).exclude(
        ui_item="NewModel",
    ).update(ui_item="NewModel")


def reverse_rename(apps, schema_editor):
    CMSPlugin = apps.get_model("cms", "CMSPlugin")
    CMSPlugin.objects.filter(
        plugin_type="NewPlugin",
    ).update(plugin_type="OldPlugin")

    FrontendUIItem = apps.get_model("djangocms_frontend", "FrontendUIItem")
    FrontendUIItem.objects.filter(
        plugin_type="OldPlugin",
    ).update(ui_item="OldModel")


class Migration(migrations.Migration):
    dependencies = [
        ("yourapp", "0001_initial"),
        ("djangocms_frontend", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(rename_plugin, reverse_rename),
    ]

Replace OldPlugin / NewPlugin with the actual plugin_type values (typically the plugin class name, e.g. HeroPlugin). Replace OldModel / NewModel with the corresponding model class names (e.g. Hero).

Tip

Always provide a reverse_rename function so that the migration can be rolled back with manage.py migrate yourapp <previous_migration>.

Combining with schema migrations

If your rename also involves schema changes (e.g. moving from a proxy model to a concrete model with extra fields), put the data migration between the schema migrations:

  1. Schema migration that creates the new table or fields.

  2. Data migration that copies/renames plugin instances.

  3. Schema migration that removes the old table or fields (if any).

This ensures the data migration can read from the old structure and write to the new one.