Skip to main content

Outline

At a glance
  • What it is: IContentRepository is the write-capable API for CRUD operations on Optimizely content.
  • What to prefer for reads: Use IContentLoader for read-only operations to keep intent clear and reduce accidental writes.
  • Core "gotcha": Content instances are effectively immutable; update via CreateWritableClone() + Save().
  • PaaS mindset: Treat content operations as versioned data changes: language, permissions, save actions, and workflow impact matter.


Introduction

Optimizely CMS 12 provides a robust API for managing content programmatically, forming the backbone of dynamic digital experiences. At the heart of content manipulation for developers lies the IContentRepository interface, a cornerstone for interacting with the CMS content model. This article walks through practical usage of IContentRepository and its read-only counterpart IContentLoader, with concrete examples and the operational considerations that matter in CMS 12 PaaS deployments.



1. Understanding IContentRepository and IContentLoader

The EPiServer.IContentRepository interface is the primary gateway for operating on objects that implement EPiServer.Core.IContent (pages, blocks, media, and custom types). It supports full CRUD semantics and versioning-aware save behavior.

Expand: IContentRepository vs IContentLoader (the practical rule)
  • IContentRepository: read + write. Use only when you intend to create/update/delete/move content.
  • IContentLoader: read-only. Prefer this for queries, page rendering, navigation building, and listing content.
  • Why it matters: the interface choice communicates intent and reduces accidental writes (and review surprises).


2. Getting started: obtaining repository instances

In CMS 12, dependency injection (DI) is the recommended and most maintainable way to access IContentRepository and IContentLoader. DI keeps dependencies explicit, improves testability, and avoids hidden coupling.

2.1 Dependency injection (recommended)

using EPiServer; using EPiServer.Core; public class ContentService { private readonly IContentRepository _contentRepository; private readonly IContentLoader _contentLoader; public ContentService(IContentRepository contentRepository, IContentLoader contentLoader) { _contentRepository = contentRepository; _contentLoader = contentLoader; } // Use _contentLoader for reads; _contentRepository for writes. }

2.2 ServiceLocator (alternative, use with caution)

ServiceLocator can be useful in static or legacy contexts where constructor injection is not feasible, but it hides dependencies and makes testing harder. Treat it as a "last resort."

Governance note

If you see ServiceLocator spreading across the codebase, that's usually a refactoring signal. It's convenient, but it quietly makes your architecture harder to reason about.

using EPiServer; using EPiServer.ServiceLocation; var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); var loader = ServiceLocator.Current.GetInstance<IContentLoader>();


3. Core operations: loading content

Loading content is the most frequent operation. When you know the type, use Get<T>. When you don't, load as IContent and inspect/cast safely.

3.1 Loading a single content item by type

using EPiServer.Core; using EPiServer.Web; ContentReference pageLink = ContentReference.StartPage; // Typical: use IContentLoader for reads var page = _contentLoader.Get<TextPage>(pageLink);
Expand: Handling unknown types without exceptions

If you don't know what's at a ContentReference, load as IContent and use pattern matching. This avoids type-mismatch exceptions and keeps flow explicit.

using EPiServer.Core; IContent content = _contentLoader.Get<IContent>(pageLink); if (content is TextPage textPage) { // Safe access to TextPage properties here }

If you prefer "try-cast" semantics:

TextPage page = _contentLoader.Get<IContent>(pageLink) as TextPage; if (page == null) { // Not a TextPage (or not loadable) }

3.2 Loading multiple items efficiently

For bulk loads, GetItems is typically more efficient than repeated single calls. It also pairs naturally with language options via LoaderOptions.

using System.Collections.Generic; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.Web; var references = new List<ContentReference> { ContentReference.StartPage, new ContentReference(123), new ContentReference(456) }; var items = _contentLoader.GetItems( references, new LoaderOptions { LanguageLoaderOption.FallbackWithMaster() } );

3.3 Language-specific loading

CMS 12 is built for multilingual sites. Use CultureInfo or language loader options deliberately—otherwise you'll load based on the current request context, which is sometimes what you want, and sometimes very much not.

using System.Globalization; using EPiServer.Globalization; var swedish = _contentLoader.Get<TextPage>(pageLink, CultureInfo.GetCultureInfo("sv")); var current = _contentLoader.Get<TextPage>(pageLink, ContentLanguage.PreferredCulture);


4. Traversing the content tree: listing children

Optimizely content is hierarchical. Listing children is a foundational operation for navigation, listings, and content discovery features.

using System.Globalization; using EPiServer.Core; using EPiServer.Web; ContentReference parentLink = ContentReference.StartPage; var allChildren = _contentLoader.GetChildren<IContent>(parentLink); var childPages = _contentLoader.GetChildren<PageData>(parentLink); var swedishChildren = _contentLoader.GetChildren<IContent>(parentLink, CultureInfo.GetCultureInfo("sv"));


5. Writing content safely: writable clones and save actions

Modifying content programmatically requires one non-negotiable step: create a writable clone. Loaded content instances are treated as immutable snapshots. Update the clone, then save with the correct SaveAction and required AccessLevel.

Critical rule

If you try to modify a loaded content instance without CreateWritableClone(), you're either going to fail at runtime or end up with "why didn't this save?" debugging pain.

using System; using EPiServer.Core; using EPiServer.DataAccess; using EPiServer.Security; using EPiServer.Web; ContentReference pageLinkToUpdate = ContentReference.StartPage; // Load via repository when you intend to write TextPage pageToUpdate = _contentRepository.Get<TextPage>(pageLinkToUpdate); // Clone -> modify -> save TextPage writablePage = pageToUpdate.CreateWritableClone() as TextPage; writablePage.MainBody = $"Programmatically updated at {DateTime.Now}."; writablePage.Name = $"Updated Start Page - {DateTime.Now:yyyyMMddHHmmss}"; // SaveAction.Publish publishes immediately; adjust to match your workflow needs _contentRepository.Save(writablePage, SaveAction.Publish, AccessLevel.Publish);
Expand: Choosing SaveAction deliberately
  • SaveAction.Publish: publish immediately (be careful in automated jobs).
  • SaveAction.Save: save as draft; useful when you want review/approval steps.
  • SaveAction.CheckIn: check in changes; relevant when using check-out/check-in workflows.
  • SaveAction.ForceCurrentVersion: advanced/rare; use only when you fully understand versioning implications.


6. Best practices and operational considerations

These guidelines keep your content operations predictable, reviewable, and easier to maintain—especially in larger PaaS solutions where multiple teams and automated jobs touch content.

Expand: Best practices checklist
  • Use the right interface: IContentLoader for reads, IContentRepository for writes.
  • Always clone before writing: CreateWritableClone() is mandatory for updates.
  • Pick SaveAction intentionally: publishing in a job is very different than saving a draft for review.
  • Enforce permissions: set AccessLevel appropriately to avoid unauthorized modifications.
  • Handle errors explicitly: type mismatches and access denied errors should be visible and actionable in logs.
  • Be language-aware: specify culture/loader options to avoid "wrong language" surprises.
  • Prefer batch loading: use GetItems for efficiency and predictable performance.


Conclusion

IContentRepository, complemented by IContentLoader, is a foundational API surface for building dynamic Optimizely CMS 12 solutions. If you keep read/write intent clear, handle languages deliberately, clone before updates, and save with the correct workflow semantics, your content operations will remain stable and maintainable as the solution grows.