HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Migrate to Optimizely Forms 6.0.0

Migrate from Optimizely Forms 5.x (EPiServer.Forms 5.x) to the major version 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.

  1. 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" />
  2. 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();
  3. 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"
            }
          }
        }
      }
  4. 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.
  5. 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);
            // ...
        }
    }
  6. 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)
    }
  7. 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.
  8. 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(...).
  9. 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).
  10. Remove form data usageIForm.Data and Form.Data usage.

    Before

    var data = form.Data; // PropertyBag

    After

    var data = submission.Data; // IDictionary<string, object>
  11. 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();
    }
  12. Review PropertyGenericList<T> model types for System.Text.Json compatibilityPropertyGenericList<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>.

  13. 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"));
  14. 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.

  15. 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");
  16. 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

  • SymptomInvalidOperationException: 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.