Module: Advanced content migration techniques
7 of 9 Pages
Optimize images during your upgrade
Xperience by Kentico (XbyK) boasts powerful features for working with images.
With image variants, you can automatically generate cropped versions of image assets across the system at a specified size. Automatic asset optimization allows you to define a format, quality, and maximum dimensions to improve load times and consistency. These attributes are applied to each image uploaded to a given content type field, allowing you to tailor the specifications for each use case.
While image variants can be generated at any time, automatic image optimization applies to images the moment they are uploaded—a problem if you’ve already migrated thousands of media library files and attachments from Kentico Xperience 13 (KX13) to XbyK.
It’s possible to optimize already-uploaded images, but editors can only process each image individually, which is not scalable.
Let’s go over the three quick steps to optimize all your images during migration and save time for your team!
Decide your optimization parameters
Start by considering the parameters you want to set for image optimization.
- Which types of images should Xperience convert to a new target format?
- What format should Xperience convert the images to?
- How much compression should Xperience apply to the images (if applicable to your selected target format)?
- Should you set width and height limits? If so, what should they be?
For reference, go to the Content types application in a non-production instance of Xperience by Kentico and create a new reusable content type with a Content item asset field called Asset. The Automatic image format conversion properties of the Asset uploader form control will allow you to configure image optimization.
For our example, let’s convert the other supported formats to WEBP and set the quality to 90%, meaning Xperience only applies a small degree of lossy compression to the images.

Since we’re going to apply these transformations to all media files and attachments, which could have a wide variety of purposes and sizes, let’s not set maximum dimensions in this case.
We recommend ensuring that the target extension you choose is allowed in your KX13 site. Allowed extension settings are copied to XbyK during the upgrade by default, so you might see an error if the extension is not allowed.
If the extension is forbidden in KX13, you can change the setting in XbyK afterward.
Save this content type for reference. You can come back to delete it after we’ve finished setting up our migration code.
Identify the correct format for your configurations
An interesting aspect of Automatic image format conversion is that, although these settings are configured through the Asset uploader form control assigned to a field, they still apply to images uploaded via API—not just those uploaded through the form control in the admin UI.
To apply these settings, we need to configure the form controls of the asset content types created by the migration tool. Let’s examine how the configurations we set in the UI are stored in the database, to see the proper format for this data.
In your preferred database manager, query the form definition of the dummy content type that you defined earlier:
SELECT [ClassFormDefinition]
FROM [CMS_Class]
WHERE [ClassName] = 'Test.Test' -- substitute your type's codename here
Copy the form definition, and optionally format the XML for readability in your preferred text processor or IDE.
Here, within the field you defined for the asset, you should be able to see the code names of the properties you configured in the UI, and the format of their stored values.
<form>
<field column="ContentItemDataID" columntype="integer" enabled="true" guid="8259c0ed-ee0c-4236-ae2e-ca2d3635760b" isPK="true"/>
<field column="ContentItemDataCommonDataID" columntype="integer" enabled="true" guid="4d4cd43c-51f9-402b-ba93-70c01d3f59b1" refobjtype="cms.contentitemcommondata" reftype="Required" system="true"/>
<field column="ContentItemDataGUID" columntype="guid" enabled="true" guid="4af7f2c6-69da-4a13-90f8-22384ab8a843" isunique="true" system="true"/>
<field allowempty="true" column="Asset" columnprecision="0" columntype="contentitemasset" enabled="true" guid="f4e1c06e-6d96-4536-b7c4-84faa9aaa27a" visible="true">
<properties>
<explanationtextashtml>False</explanationtextashtml>
<fieldcaption>Asset</fieldcaption>
<fielddescriptionashtml>False</fielddescriptionashtml>
</properties>
<settings>
<AllowedExtensions>_INHERITED_</AllowedExtensions>
<controlname>Kentico.Administration.ContentItemAssetUploader</controlname>
<InputImageExtensions>["avif","jpeg","jpg","png"]</InputImageExtensions>
<IsFormatConversionEnabled>True</IsFormatConversionEnabled>
<OutputImageExtension>webp</OutputImageExtension>
<OutputQuality>90</OutputQuality>
</settings>
</field>
</form>
Configure the form control in code
So far, we’ve decided which parameters to set and learned how to structure their data. Now, let’s move on to the code.
In your local copy of the Kentico migration tool, find the AssetFacade class in the Migration.Tool.Source project.
Here you can find definitions of the content types that correspond to KX13’s attachments and media library files. By default, they are called Legacy attachment and Legacy media file, and contain fields called LegacyMediaFileAsset and LegacyAttachmentAsset respectively.
You can Ctrl+F to the definitions of each relevant field to see how it is defined:
internal static readonly FormField LegacyMediaFileAssetFieldinternal static readonly FormField LegacyAttachmentAssetField
Since we’re setting the same parameters for each content type in this example, define constants to hold both the names and values we saw in the form definition.
Then use the constants to set the appropriate values in the CustomProperties dictionary of each field.
...
// Shared string constants for automatic image optimization of assets
private const string InputImageExtensionsKey = "InputImageExtensions";
private const string IsFormatConversionEnabledKey = "IsFormatConversionEnabled";
private const string OutputImageExtensionKey = "OutputImageExtension";
private const string OutputQualityKey = "OutputQuality";
private const string InputImageExtensionsValue = "[\"jpeg\", \"jpg\", \"png\", \"avif\"]";
private const string IsFormatConversionEnabledValue = "True";
private const string OutputImageExtensionValue = "webp";
private const string OutputQualityValue = "90";
internal static readonly FormField LegacyMediaFileAssetField = new()
{
Column = "LegacyMediaFileAsset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("DFC3D011-8F63-43F6-9ED8-4B444333A1D0"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings
{
CustomProperties = new Dictionary<string, object?>
{
{ "AllowedExtensions", "_INHERITED_" },
{ InputImageExtensionsKey, InputImageExtensionsValue },
{ IsFormatConversionEnabledKey, IsFormatConversionEnabledValue },
{ OutputImageExtensionKey, OutputImageExtensionValue },
{ OutputQualityKey, OutputQualityValue }
},
ControlName = "Kentico.Administration.ContentItemAssetUploader"
}
};
...
internal static readonly FormField LegacyAttachmentAssetField = new()
{
Column = "LegacyAttachmentAsset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("50C2BC4C-A8FF-46BA-95C2-0E74752D147F"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings
{
CustomProperties = new Dictionary<string, object?>
{
{ "AllowedExtensions", "_INHERITED_" },
{ InputImageExtensionsKey, InputImageExtensionsValue },
{ IsFormatConversionEnabledKey, IsFormatConversionEnabledValue },
{ OutputImageExtensionKey, OutputImageExtensionValue },
{ OutputQualityKey, OutputQualityValue }
},
ControlName = "Kentico.Administration.ContentItemAssetUploader"
}
};
...
Make the asset field required
Optimizing your images is one improvement you can make during migration. Another is ensuring that content editors always provide an asset for the migrated asset fields when they start editing the content in XbyK.
By default, the Pages and reusable content fields created by the migration tool allow empty values in the Combined content selector component, so editors can save a content item without selecting an asset (such as a legacy media file). If you want to ensure that editors always select an asset in XbyK, you can add a required validation rule to the asset fields during migration.
XbyK stores field validation through the ValidationRuleConfigurationsXmlData property, which maps to the validationrulesdata element of the form definition. To make a field required, add the Kentico.Administration.RequiredValue validation rule.
You have two options, depending on whether you want to apply the rule to all asset fields or only specific ones.
Require all asset fields
To force a value in every asset selector field, update the MigrateFieldDefinition() method of the AssetMigration class in the migration tool. At the end of the method—after the existing code that configures the field’s settings element—add the required validation rule to the field’s validationrulesdata element, as highlighted below:
// ... existing code ...
public void MigrateFieldDefinition(FormDefinitionPatcher formDefinitionPatcher, XElement field, XAttribute? columnTypeAttr, string fieldDescriptor)
{
columnTypeAttr?.SetValue(configuration.MigrateMediaToMediaLibrary
#pragma warning disable CS0618 // Type or member is obsolete
? FieldDataType.Assets
#pragma warning restore CS0618 // Type or member is obsolete
: FieldDataType.ContentItemReference);
var settings = field.EnsureElement(FormDefinitionPatcher.FieldElemSettings);
if (configuration.MigrateMediaToMediaLibrary)
{
settings.EnsureElement(FormDefinitionPatcher.SettingsElemControlname, e => e.Value = FormComponents.AdminAssetSelectorComponent);
settings.EnsureElement(FormDefinitionPatcher.SettingsMaximumassets, maxAssets => maxAssets.Value = FormDefinitionPatcher.SettingsMaximumassetsFallback);
}
else
{
settings.EnsureElement(FormDefinitionPatcher.SettingsElemControlname, e => e.Value = FormComponents.AdminContentItemSelectorComponent);
Guid[] allowedContentTypes = [AssetFacade.LegacyMediaFileContentType.ClassGUID!.Value, AssetFacade.LegacyMediaLinkContentType.ClassGUID!.Value, AssetFacade.LegacyAttachmentContentType.ClassGUID!.Value];
settings.EnsureElement(FormDefinitionPatcher.AllowedContentItemTypeIdentifiers, e => e.Value = JsonConvert.SerializeObject(allowedContentTypes.Select(x => x.ToString()).ToArray()));
var allowEmptyField = field.Attribute(FormDefinitionPatcher.FieldAttrAllowEmpty);
if (allowEmptyField == null || allowEmptyField.Value.Equals("false", StringComparison.InvariantCultureIgnoreCase))
{
field.SetAttributeValue(FormDefinitionPatcher.FieldAttrAllowEmpty, "true");
settings.EnsureElement(FormDefinitionPatcher.SettingsMaximumitems, x => x.Value = "1");
settings.EnsureElement(FormDefinitionPatcher.SettingsMinimumitems, x => x.Value = "1");
}
}
var requiredRule = @"<ValidationRuleConfiguration><ValidationRuleIdentifier>Kentico.Administration.RequiredValue</ValidationRuleIdentifier><RuleValues><ErrorMessage>No asset selected.</ErrorMessage></RuleValues></ValidationRuleConfiguration>";
var validationrules = field.EnsureElement("validationrulesdata");
validationrules.Add(XElement.Parse(requiredRule));
}
// ... existing code ...
Because this code runs for every field the method processes, the required rule is applied to all migrated asset fields.
Require specific asset fields
If you only want certain fields to be required, set the ValidationRuleConfigurationsXmlData property on the individual field during its migration. This approach also lets you provide a custom error message that editors see when they leave the field empty:
f.ValidationRuleConfigurationsXmlData = "<ValidationRuleConfiguration><ValidationRuleIdentifier>Kentico.Administration.RequiredValue</ValidationRuleIdentifier><RuleValues><ErrorMessage>No asset selected.</ErrorMessage></RuleValues></ValidationRuleConfiguration>";
With this approach, only the fields where you explicitly set ValidationRuleConfigurationsXmlData become required. For inspiration on how this approach works, you can inspect the method’s implementation in the Migration tool repository.
After you migrate your content items with some of the now-required asset fields empty, editors need to update the migrated items manually. When they try to save their changes to other fields in the content item without adding an asset, XbyK doesn’t save the item and shows No asset selected. error message.
The Kentico.Administration.RequiredValue rule ensures editors select at least one asset. Combine it with the image optimization settings above to migrate assets that are both optimized and required.
See the result
With this code in place, the migration tool will use the settings you defined for the asset fields of Legacy attachment and Legacy media file.
Run a migration that includes media libraries and pages, and you’ll see that page attachments and media library files that were other formats in KX13 have changed into the specified target format (WebP in our example) with the specified quality level.
Here is the command for full migration, for example:
.\Migration.Tool.CLI.exe migrate --sites --custom-modules --users --settings-keys --page-types --pages --contact-management --forms --media-libraries --data-protection --custom-tables --members --categories
KX13:

XbyK:

Now your images utilize the WebP format, helping reduce file sizes for faster loading.