Skip to main content

Outline

At a glance
  • Goal: Create and update CMS content via code for automation, integrations, and repeatable workflows.
  • Core APIs: IContentLoader (read), IContentRepository (write), CreateWritableClone() (immutability rule).
  • Typical scenarios: Create pages, update blocks from external systems, create language variants, and control publish states.
  • Operational note: Programmatic writes affect versioning, approvals, and (often) indexing—so treat bulk changes as release-grade operations.


Introduction

In modern CMS implementations, “content” rarely lives in one place. You may need to synchronize data from external systems, generate pages at scale, run scheduled updates, or implement controlled publishing workflows. Optimizely CMS 12 provides APIs that make these scenarios possible without relying on manual editor actions for every change.

This page walks through common scenario patterns for creating and updating content programmatically in a CMS 12 (PaaS) solution. The examples focus on repeatable, safe patterns that align with Optimizely’s versioning and security model.



1. Core concepts to keep straight

Most scenario-based implementations boil down to three rules: use the right interface for intent, clone before you modify, and choose the right SaveAction based on your workflow.

  • IContentLoader is for reading. Prefer it for Get<T>, GetItems, and GetChildren.
  • IContentRepository is for writing. Use it for GetDefault<T>, Save, Delete, and Move.
  • CreateWritableClone() is non-negotiable when updating existing items.
Expand: A quick mental model (read vs write)
  • If your method should never change content, inject IContentLoader.
  • If your method can change content, inject IContentRepository (and still use IContentLoader for reads).
  • Updates are always: load → clone → modify → save.


2. Scenario 1: Creating a new page programmatically

Page creation typically happens in imports, synchronization jobs, or “generate pages from a dataset” flows. The stable approach is GetDefault<T>(parent) → set properties → Save with the correct action.

Expand: Steps to create and publish a page
  1. Identify the parent ContentReference.
  2. Create the instance via GetDefault<T>.
  3. Set typed properties (preferred) or properties via Property collection.
  4. Save with SaveAction that matches the workflow (draft, request approval, publish).
using System;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Security;

// Assume StandardPage is a PageData type in your solution.
// public class StandardPage : PageData { public virtual string MainBody { get; set; } }

public class PageCreationService
{
    private readonly IContentRepository _contentRepository;

    public PageCreationService(IContentRepository contentRepository)
    {
        _contentRepository = contentRepository;
    }

    public ContentReference CreateAndPublishNewPage(string pageName, string mainBodyContent, ContentReference parentPageLink)
    {
        var newPage = _contentRepository.GetDefault<StandardPage>(parentPageLink);

        newPage.PageName = pageName;
        newPage.MainBody = mainBodyContent;

        _contentRepository.Save(newPage, SaveAction.Publish, AccessLevel.Publish);

        return newPage.ContentLink;
    }
}
Operational note

If you’re generating many pages, include logging and a rollback plan. In large batches, failures are inevitable—surprises shouldn’t be.



3. Scenario 2: Updating an existing block based on external data

Integration-driven updates are a classic “load → clone → modify → save” loop. The key discipline is to keep reads on IContentLoader, and only switch to IContentRepository when you save.

using System;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Security;

// Assume ProductBlock is a BlockData type in your solution.
// public class ProductBlock : BlockData { public virtual decimal Price { get; set; } public virtual string ProductName { get; set; } }

public class BlockUpdateService
{
    private readonly IContentRepository _contentRepository;
    private readonly IContentLoader _contentLoader;

    public BlockUpdateService(IContentRepository contentRepository, IContentLoader contentLoader)
    {
        _contentRepository = contentRepository;
        _contentLoader = contentLoader;
    }

    public void UpdateProductBlockPrice(ContentReference blockLink, decimal newPrice)
    {
        var existing = _contentLoader.Get<ProductBlock>(blockLink);
        var writable = existing.CreateWritableClone() as ProductBlock;

        writable.Price = newPrice;

        _contentRepository.Save(writable, SaveAction.Publish, AccessLevel.Publish);
    }
}
Expand: Hardening an external-data update
  • Validate the payload: treat external input as untrusted; apply business rules before saving.
  • Be explicit about state: consider draft saves for changes that require review.
  • Make updates idempotent: repeated runs should not create unintended changes.
  • Log with identifiers: log content IDs and correlation IDs from the external system.


4. Scenario 3: Creating content in a specific language

Multilingual creation is the same pattern as page creation, with one difference: the language version is created by calling GetDefault<T>(parent, culture).

using System.Globalization;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Security;

public class MultilingualContentService
{
    private readonly IContentRepository _contentRepository;

    public MultilingualContentService(IContentRepository contentRepository)
    {
        _contentRepository = contentRepository;
    }

    public ContentReference CreatePageInSpecificLanguage(ContentReference parent, CultureInfo language, string name, string body)
    {
        var newPage = _contentRepository.GetDefault<StandardPage>(parent, language);

        newPage.PageName = name;
        newPage.MainBody = body;

        _contentRepository.Save(newPage, SaveAction.Publish, AccessLevel.Publish);

        return newPage.ContentLink;
    }
}
Expand: Language version gotchas
  • Fallback isn’t creation: fallback loading finds content; it doesn’t create missing language versions.
  • Keep naming conventions consistent: avoid “same page, different name” chaos across locales.
  • Workflow alignment: localized content often needs approval gates per market/team.


5. Scenario 4: Managing publishing status (draft vs published vs scheduled)

Publishing control is expressed via SaveAction. The important part is to be deliberate: draft saves are not a “lesser publish”; they are a workflow decision.

using System;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Security;

public class PublishingService
{
    private readonly IContentRepository _contentRepository;
    private readonly IContentLoader _contentLoader;

    public PublishingService(IContentRepository contentRepository, IContentLoader contentLoader)
    {
        _contentRepository = contentRepository;
        _contentLoader = contentLoader;
    }

    public void SaveAsDraft(ContentReference contentLink, Action<PageData> mutate)
    {
        var existing = _contentLoader.Get<PageData>(contentLink);
        var writable = existing.CreateWritableClone() as PageData;

        mutate(writable);

        _contentRepository.Save(writable, SaveAction.Save, AccessLevel.Edit);
    }

    public void PublishNow(ContentReference contentLink, Action<PageData> mutate)
    {
        var existing = _contentLoader.Get<PageData>(contentLink);
        var writable = existing.CreateWritableClone() as PageData;

        mutate(writable);

        _contentRepository.Save(writable, SaveAction.Publish, AccessLevel.Publish);
    }

    public void SchedulePublish(ContentReference contentLink, DateTime publishDate)
    {
        var existing = _contentLoader.Get<PageData>(contentLink);
        var writable = existing.CreateWritableClone() as PageData;

        writable.StartPublish = publishDate;

        _contentRepository.Save(writable, SaveAction.CheckIn | SaveAction.Schedule, AccessLevel.Publish);
    }
}
Expand: Choosing the right SaveAction
  • SaveAction.Save keeps it as a draft (not live).
  • SaveAction.RequestApproval is for workflows with reviewers/approvers.
  • SaveAction.Publish makes the version live immediately.
  • SaveAction.CheckIn | SaveAction.Schedule moves content into delayed publish.
  • Use caution: flags like ForceCurrentVersion can overwrite version history.


6. Best practices for scenario-based operations

  • Wrap operations in guardrails: validate inputs, enforce business rules, and fail fast on invalid data.
  • Be explicit with permissions: choose AccessLevel to match the action (edit vs publish).
  • Log like you’ll need it later: content IDs, external identifiers, version references, and the exact action taken.
  • Plan for scale: bulk writes can cause indexing load and longer processing time; batch and schedule responsibly.
  • Prefer deterministic behavior: avoid “works locally” drift by keeping versions aligned and processes repeatable.

Conclusion

Programmatic content creation and updates are core to automation, integration, and controlled delivery in Optimizely CMS 12. When you consistently apply the patterns in this page—read with IContentLoader, modify with writable clones, and save with deliberate SaveAction choices—you get predictable outcomes without undermining versioning, workflow, or security.