optimizely-development
SKILL.md
Optimizely CMS Development
Overview
This skill covers core Optimizely CMS (formerly Episerver) development patterns including content types, initialization modules, and the content API.
Content Types
Page Types
using EPiServer.Core;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
[ContentType(
GUID = "f8d47a38-5b23-4c8e-9f12-3a7e8b9c2d1f",
DisplayName = "Article Page",
Description = "Standard article page with heading and body content",
GroupName = "Content")]
public class ArticlePage : PageData
{
[Display(
Name = "Heading",
Description = "Main heading for the article",
GroupName = SystemTabNames.Content,
Order = 100)]
[Required]
public virtual string Heading { get; set; }
[Display(
Name = "Main Content",
GroupName = SystemTabNames.Content,
Order = 200)]
public virtual XhtmlString MainBody { get; set; }
[Display(
Name = "Published Date",
GroupName = SystemTabNames.Content,
Order = 300)]
public virtual DateTime? PublishedDate { get; set; }
}
Block Types
[ContentType(
GUID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
DisplayName = "Hero Block",
Description = "Full-width hero section")]
public class HeroBlock : BlockData
{
[Display(Name = "Heading", Order = 10)]
[Required]
public virtual string Heading { get; set; }
[Display(Name = "Subheading", Order = 20)]
public virtual string Subheading { get; set; }
[Display(Name = "Background Image", Order = 30)]
[UIHint(UIHint.Image)]
public virtual ContentReference BackgroundImage { get; set; }
[Display(Name = "Call to Action", Order = 40)]
public virtual Url CallToActionUrl { get; set; }
}
Content Areas with Restrictions
[Display(Name = "Main Content Area", Order = 100)]
[AllowedTypes(typeof(TextBlock), typeof(ImageBlock), typeof(VideoBlock))]
public virtual ContentArea MainContentArea { get; set; }
[Display(Name = "Sidebar Blocks", Order = 200)]
[AllowedTypes(typeof(TeaserBlock), typeof(LinkListBlock))]
[MaxItems(3)]
public virtual ContentArea SidebarArea { get; set; }
Initialization Modules
Configurable Module
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using Microsoft.Extensions.DependencyInjection;
[InitializableModule]
[ModuleDependency(typeof(ServiceContainerInitialization))]
public class DependencyResolverInitialization : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
context.Services.AddScoped<IArticleService, ArticleService>();
context.Services.AddScoped<ISearchService, SearchService>();
}
public void Initialize(InitializationEngine context)
{
// Initialization logic
}
public void Uninitialize(InitializationEngine context)
{
// Cleanup logic
}
}
Content Events Module
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ContentEventsInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var events = context.Locate.ContentEvents();
events.PublishedContent += OnPublishedContent;
events.SavingContent += OnSavingContent;
}
private void OnPublishedContent(object sender, ContentEventArgs e)
{
// Handle content published event
}
private void OnSavingContent(object sender, ContentEventArgs e)
{
// Handle content saving event
}
public void Uninitialize(InitializationEngine context)
{
var events = context.Locate.ContentEvents();
events.PublishedContent -= OnPublishedContent;
events.SavingContent -= OnSavingContent;
}
}
Content API
IContentLoader (Cached Reads)
public class ArticleService : IArticleService
{
private readonly IContentLoader _contentLoader;
public ArticleService(IContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public T Get<T>(ContentReference contentLink) where T : IContent
{
return _contentLoader.Get<T>(contentLink);
}
public IEnumerable<T> GetChildren<T>(ContentReference parentLink) where T : IContent
{
return _contentLoader.GetChildren<T>(parentLink);
}
// Batch loading - more efficient than loading one by one
public IEnumerable<IContent> GetItems(IEnumerable<ContentReference> contentLinks)
{
return _contentLoader.GetItems(contentLinks, new LoaderOptions());
}
}
IContentRepository (Write Operations)
public class ContentManager
{
private readonly IContentRepository _contentRepository;
public ContentManager(IContentRepository contentRepository)
{
_contentRepository = contentRepository;
}
public ContentReference CreatePage<T>(ContentReference parentLink, string name) where T : PageData
{
var page = _contentRepository.GetDefault<T>(parentLink);
page.Name = name;
return _contentRepository.Save(page, SaveAction.Publish);
}
public void UpdateContent(IContent content)
{
var writableContent = content.CreateWritableClone();
// Make changes
_contentRepository.Save(writableContent, SaveAction.Publish);
}
}
Scheduled Jobs
[ScheduledPlugIn(
DisplayName = "Content Cleanup Job",
Description = "Removes expired content",
GUID = "12345678-1234-1234-1234-123456789012")]
public class ContentCleanupJob : ScheduledJobBase
{
private readonly IContentRepository _contentRepository;
public ContentCleanupJob(IContentRepository contentRepository)
{
_contentRepository = contentRepository;
}
public override string Execute()
{
var processedCount = 0;
// Job logic here
OnStatusChanged($"Processing... {processedCount} items");
return $"Completed. Processed {processedCount} items.";
}
}
Property Value Converters
public class TagListPropertyConverter : PropertyValueConverterBase
{
public override object Convert(object value, Type targetType)
{
if (value is string tags)
{
return tags.Split(',').Select(t => t.Trim()).ToList();
}
return new List<string>();
}
}
Best Practices
- Always use GUIDs on content types for serialization
- Use IContentLoader for reads (cached)
- Use IContentRepository for writes only
- Batch load content instead of loading in loops
- Bound queries with Take() to prevent loading thousands of items
- Use constructor injection instead of ServiceLocator
Weekly Installs
12
Repository
twofoldtech-dakā¦ketplaceGitHub Stars
1
First Seen
Jan 26, 2026
Security Audits
Installed on
github-copilot12
opencode11
gemini-cli11
codex11
amp10
kimi-cli10