Migrate to Optimizely Forms 6.0.0
Optimizely Forms 6.0.0 runs on Optimizely Content Management System (CMS) 13 and .NET 10. Complete the CMS 13 migration before upgrading Forms.
Prerequisites
- .NET 10 or later
- CMS 13 migration completed
Migration steps
Complete each step to upgrade your project from Forms 5.x to 6.0.0.
- Update NuGet package references – Update the Forms NuGet package version references to 6.0.0 in your .csproj:
<PackageReference Include="EPiServer.Forms.Core" Version="6.0.0" />
<PackageReference Include="EPiServer.Forms.UI" Version="6.0.0" />
<!-- Or using umbrella package: -->
<PackageReference Include="EPiServer.Forms" Version="6.0.0" />
<!-- If using Azure Key Vault crypto: -->
<PackageReference Include="EPiServer.Forms.Crypto.AzureKeyVault" Version="3.0.0" />Remove the following package reference (the package no longer exists):
<PackageReference Include="EPiServer.AddOns.Helpers" />- Register Forms services in DI – Forms 6.0.0 drops the old [ServiceConfiguration] auto-registration pattern (where possible). You must now call the Forms extension methods explicitly in your Program.cs or Startup.cs.
- Before (5.10.7) – Services were auto-registered with InitializationModule.ConfigureContainer() and [ServiceConfiguration] attributes. No explicit registration required.
- After (6.0.0) – Add the following code in Program.cs or Startup.cs to register all Forms, FormsUI, and FormsCore services:
builder.Services.AddForms(); If you need only partial registration:
builder.Services.AddFormsCore(); // Core only
builder.Services.AddFormsUI(); // UI + Core
builder.Services.AddForms(); // Full stack (recommended)For Azure Key Vault encryption:
builder.Services.AddFormsCryptoAzureKeyVault();- Migrate configuration from Forms.config to appsettings.json. Forms 6.0.0 no longer supports the Forms.config XML file. Move all settings to appsettings.json.
- Forms.config before (5.10.7)
<EPiServerForms
WorkInNonJSMode="false"
InjectFormOwnJQuery="false"
InjectFormOwnStylesheet="true"
SendMessageInHTMLFormat="true"
DefaultUploadExtensionBlackList=".exe,.bat,.cmd"
RenderingFormUsingDivElement="false"
DisableFormCookies="false"
HidePartialSubmissions="false"
/>- appsettings.json after (6.0.0)
{
"Forms": {
"WorkInNonJSMode": false,
"InjectFormOwnJQuery": false,
"InjectFormOwnStylesheet": true,
"SendMessageInHTMLFormat": true,
"DefaultUploadExtensionBlackList": ".exe,.bat,.cmd",
"RenderingFormUsingDivElement": false,
"DisableFormCookies": false,
"HidePartialSubmissions": false
}
}Also move your storage provider configuration from Forms.config to appsettings.json:
{
"Forms": {
"Providers": {
"DefaultProvider": "ddsProvider",
"ddsProvider": {
"type": "EPiServer.Forms.Core.Data.Internal.DdsEncryptedPermanentStorage, EPiServer.Forms.Core"
}
}
}
}- Remove references to deleted interfaces and classes – Forms 6.0.0 removed IEPiServerFormsCoreConfig, IEPiServerFormsUIConfig, and the EPiServer.Forms.Configuration namespace.
- IEPiServerFormsCoreConfig and IEPiServerFormsUIConfig Before
public class MyService
{
private readonly IEPiServerFormsCoreConfig _config;
public MyService(IEPiServerFormsCoreConfig config) { _config = config; }
public bool IsDisabled => _config.DisableFormCookies;
}After
using Microsoft.Extensions.Options;
using EPiServer.Forms.Core.Options;
public class MyService
{
private readonly FormsConfigOptions _config;
public MyService(IOptions<FormsConfigOptions> config) { _config = config.Value; }
public bool IsDisabled => _config.DisableFormCookies;
}- EPiServer.Forms.Configuration namespace – Remove all using EPiServer.Forms.Configuration; directives and replace with appsettings.json-based configuration.
- Update custom post-submission actors – Remove ActiveExternalFieldMappingTable usage and overrides of removed methods from SendEmailAfterSubmissionActor. Remove overrides of GetFriendlySummaryText and GetPredefinedPlaceHolders from SendEmailAfterSubmissionActor subclasses. The logic now lives in DefaultPlaceHolderProvider. Before
public class MyActor : PostSubmissionActorBase
{
public override object Run(object input)
{
var table = ActiveExternalFieldMappingTable; // REMOVED
// ...
}
}After
public class MyActor : PostSubmissionActorBase
{
private readonly IExternalFieldMappingService _externalFieldMappingService;
public MyActor(IExternalFieldMappingService externalFieldMappingService)
{
_externalFieldMappingService = externalFieldMappingService;
}
public override object Run(object input)
{
var table = _externalFieldMappingService.GetActiveFieldMappingTable(FormIdentity);
// ...
}
}- Update IExcludeValidatorTypes implementations – Replace IExcludeValidatorTypes with IAvailableValidatorTypesAttribute. Before
public class MyElement : ValidatableElementBlockBase, IExcludeValidatorTypes
{
public Type[] ValidatorTypesToBeExcluded => new[] { typeof(RegularExpressionValidator) };
}After
[AvailableValidatorTypes(Include = new[] { typeof(RequiredValidator) })]
public class MyElement : ValidatableElementBlockBase
{
// Use AvailableValidatorTypesAttribute instead (AvailableValidatorTypesAttribute inherits from IAvailableValidatorTypesAttribute)
}- Update subclasses of InitializationModule – If you have a partial class or subclass of EPiServer.Forms.EditView.InitializationModule:
- Remove any ConfigureContainer(ServiceConfigurationContext) override and move its logic to services.AddForms() registration.
- Remove overrides of Instance_DeletedContent and Instance_PublishedContent. If you need to handle these events, subscribe directly to IContentEvents.
- Replace removed FormBusinessService method calls – Replace the following removed FormBusinessService method calls:
- ReCalculateStepIndexIfNeeded(FormIdentity, FormContainerBlock, Submission, int, Guid, bool) – Replace with ReCalculateStepIndexIfNeeded(FormContainerBlock, Submission, int, bool).
- IsMatchDependCondition(IElementDependant, IDictionary<string, object>) – Replace with IsConditionMatched(IElementDependant, object submittedFieldValue).
- ReCalculateStepIndex(FormContainerBlock, int, IDictionary<string, object>, bool) – Replace with ReCalculateStepIndexIfNeeded(FormContainerBlock, Submission, int, bool).
- BuildStepsAndElements(FormContainerBlock, bool) – Use overload requiring currentForm parameter.
- ToHtmlStringWithFriendlyUrls(XhtmlString) – Replace with XhtmlRenderService.ToHtmlStringWithFriendlyUrls(...).
- ReplaceFragmentWithFriendlyUrls(IStringFragment) – Replace with XhtmlRenderService.ReplaceFragmentWithFriendlyUrls(...).
- Replace removed extension methods – Replace the following removed extension methods:
- FormsExtensions.HasAnyExternalSystem() – Replace with ExternalSystemService.HasExternalSystem().
- contentRef.GetContent(string language) (two-parameter) – Replace with contentRef.GetContent(bool shouldFallback, string language).
- str.SplitBySeparator(string separator) – Replace with str.SplitBySeparators(string[] separators).
- FormsExtensionsUI.RenderWebFormViewToString(...) – Use standard ASP.NET Core view rendering.
- IExternalFieldMappingService.GetAllExternalSystems() – Replace with ExternalSystemService.GetAllExternalSystems().
- ExternalSystemEditorDescriptors.GetSelectedFieldsName(FormIdentity, string) – Use its protected method GetSelectedFieldsName(FormContainerBlock, ElementBlockBase, string).
- Remove form data usage – IForm.Data and Form.Data usage. Before
var data = form.Data; // PropertyBagAfter
var data = submission.Data; // IDictionary<string, object>- Update subclasses – Update subclasses of PropertyGenericList<T>. Before
public override object SaveData(PropertyDataCollection properties)
{
return base.SaveData(properties);
}After
public override object SaveData()
{
return base.SaveData();
}- Review PropertyGenericList<T> model types for System.Text.Json compatibility – PropertyGenericList<T> uses System.Text.Json with camelCase naming. Update model types that use Newtonsoft.Json-specific attributes. Before
using Newtonsoft.Json;
public class MyModel
{
[JsonIgnore]
public string InternalProp { get; set; }
[JsonProperty("my_key")]
public string MyKey { get; set; }
}After
using System.Text.Json.Serialization;
public class MyModel
{
[JsonIgnore]
public string InternalProp { get; set; }
[JsonPropertyName("my_key")]
public string MyKey { get; set; }
}🚧 Important Existing data serialized with Newtonsoft.Json (camelCase or otherwise) round-trips correctly when the property names match. Verify each model type used in PropertyGenericList<T>.
- Update scheduled job references – If you reference scheduled job types directly, switch to IScheduledJobRepository. Before
var job = new UpdateUploadFolderACLJob(); // REMOVED (now internal)After
var job = _scheduledJobRepository.Get(Guid.Parse("EB289EAA-5485-4886-A59C-5C5E0B950199"));- Update SpecializedProperties if subclassed – If you subclass any SpecializedProperties type, verify database property definition names are consistent. CMS 13 does not allow spaces in property definition names, so the new display names are without spaces. The following display names changed:
- Dependency conditions collection – Changed to DependencyConditionsCollection.
- Message Template – Changed to MessageTemplate.
- Property Field Mapping Collection – Changed to PropertyFieldMappingCollection.
- Property Connected Data Source Collection – Changed to PropertyConnectedDataSourceCollection.
- Validator with message collection – Changed to ValidatorWithMessageCollection.
- Web Hook – Changed to WebHook.
📘 Note CMS syncs these definitions automatically on startup. Update custom code that queries property definitions by name to use the updated names.
- Update FreeGeolocationProvider configuration – If you configured FreeGeolocationProvider in Forms.config or web.config provider XML, update it to appsettings.json. Before
<providers>
<add name="FreeGeoIP" type="..." geoApiUrl="https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY" />
</providers>After
services.AddFreeGeolocationProvider(options =>
{
options.GeoApiUrl = "https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY";
});
// OR
services.AddFreeGeolocationProvider("https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY");📘 Note The EPiServer.CloudPlatform.Cms package registers a default IGeolocationProvider. Register the Forms FreeGeoLocationProvider after AddCmsCloudPlatformSupport(...) so it takes precedence.
services.AddCmsCloudPlatformSupport(_configuration);
services.AddFreeGeolocationProvider("https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY");- Update custom actor constructors – Remove parameterless constructors that use ServiceLocator from custom actors. Let the DI container provide dependencies instead. Before
public class MyActor : PostSubmissionActorBase
{
public MyActor() : this(ServiceLocator.Current.GetInstance<MyService>()) { }
public MyActor(MyService service) { ... }
}After
public class MyActor : PostSubmissionActorBase
{
public MyActor(MyService service) { ... }
}Troubleshoot common migration errors
Resolve errors encountered during or after the Forms 6.0.0 upgrade by matching symptoms to fixes.
Missing services.AddForms() call
- Symptom – InvalidOperationException: Unable to resolve service for type 'EPiServer.Forms.Core.Internal.DataSubmissionService'.
- Fix – Add services.AddForms() in Program.cs.
Forms.config settings silently ignored
- Symptom – Behaviors configured in Forms.config (such as WorkInNonJSMode and upload denylist) no longer apply after upgrade.
- Fix – Migrate all settings to appsettings.json under the Forms key.
PropertyGenericList deserialization failures
- Symptom – Editors show empty lists or errors for properties that previously had data.
- Fix – Check that your model types are System.Text.Json-compatible. Verify camelCase property naming matches stored JSON. Consider a one-time data migration if necessary.
Scheduled jobs not found
- Symptom – Accessing UpdateUploadFolderACLJob and others causes a compile or runtime error.
- Fix – Use IScheduledJobRepository.Get(Guid) with the job GUIDs instead.
Azure Key Vault crypto engine not registered
- Symptom – Decrypted CSV export not available, or IFormCryptoEngine not resolved.
- Fix – Call services.AddFormsCryptoAzureKeyVault() in addition to services.AddForms().
Email not sent after upgrade
- Symptom – Emails fail to send and logs show errors mentioning ISmtpClient.
- Fix – Verify that FormsSmtpClient (MailKit) is registered with services.AddFormsUI(). Verify SMTP settings in appsettings.json.
Migration checklist
Verify each item before finalizing the upgrade.
- Update EPiServer.Forms NuGet package to 6.0.0.
- Remove EPiServer.AddOns.Helpers package reference.
- Add services.AddForms() in Program.cs.
- Migrate Forms.config settings to appsettings.json.
- Remove all references to IEPiServerFormsCoreConfig, IEPiServerFormsUIConfig, EPiServer.Forms.Configuration.*.
- Update or remove custom InitializationModule subclass code.
- Update custom actors by removing ActiveExternalFieldMappingTable and updating constructors.
- Remove IExcludeValidatorTypes implementations and use IAvailableValidatorTypesAttribute.
- Replace removed FormBusinessService methods with their non-obsolete equivalents.
- Replace removed FormsExtensions and FormsExtensionsUI extension methods.
- Update PropertyGenericList model types for System.Text.Json compatibility.
- Update SaveData() overrides to remove parameter.
- Verify scheduled job GUIDs are accessible in IScheduledJobRepository if needed.
- Verify property definitions in CMS database after upgrade.
- Test existing form submission data for deserialization correctness.
- Configure geo provider URL in appsettings.json if used.
- Call services.AddFormsCryptoAzureKeyVault() if using AzureKeyVault crypto.