Build Your First CMS Site
What you will build
Section titled “What you will build”By the end of this tutorial, you will have a running Optimizely CMS site with:
- A custom page type for articles
- A reusable block type for call-to-action components
- A content tree with published pages
- Content rendering in a browser
This tutorial targets CMS PaaS (self-hosted). If you are using CMS SaaS, the content modeling concepts are the same, but project setup differs — see the SaaS-specific notes throughout.
Before you start
Section titled “Before you start”Ensure you have:
- .NET 8 SDK — Download from Microsoft
- An Optimizely CMS license — Request a trial at optimizely.com
- A code editor — Visual Studio 2022+ or VS Code with C# Dev Kit
Verify your .NET installation:
dotnet --version
# Expected: 8.0.x or higher Step 1: Create a new project
Section titled “Step 1: Create a new project”Use the Optimizely CMS templates to scaffold a new project. Install the templates first, then create the project.
# Install Optimizely templates
dotnet new install EPiServer.Templates
# Create a new CMS project
dotnet new epi-cms-empty -n MyFirstSite
cd MyFirstSite
# Restore dependencies
dotnet restore This creates a minimal CMS project with the required NuGet packages but no content types — you will create those yourself.
Step 2: Configure the database
Section titled “Step 2: Configure the database”Optimizely CMS needs a database to store content. For local development, use SQL Server LocalDB or a full SQL Server instance.
{
"ConnectionStrings": {
"EPiServerDB": "Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=MyFirstSite;Integrated Security=True;MultipleActiveResultSets=True"
}
} The database is created automatically when you first run the application.
Step 3: Create your first page type
Section titled “Step 3: Create your first page type”A page type defines the structure of a page. Create an ArticlePage that represents a basic article with a headline, body, and author.
Create the file Models/ArticlePage.cs:
using EPiServer.Core;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace MyFirstSite.Models
{
[ContentType(
DisplayName = "Article Page",
GUID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
Description = "A page for publishing articles")]
public class ArticlePage : PageData
{
[Display(
Name = "Headline",
Description = "The main headline of the article",
GroupName = SystemTabNames.Content,
Order = 10)]
[Required]
public virtual string Headline { get; set; }
[Display(
Name = "Article Body",
Description = "The main content of the article",
GroupName = SystemTabNames.Content,
Order = 20)]
public virtual XhtmlString ArticleBody { get; set; }
[Display(
Name = "Author",
Description = "Who wrote this article",
GroupName = SystemTabNames.Content,
Order = 30)]
public virtual string Author { get; set; }
}
} What each attribute does:
[ContentType]registers this class as a CMS content type with a display name and unique GUID[Display]controls how the property appears in the editor — name, description, tab, and order[Required]makes the property mandatory — authors cannot publish without filling it invirtualis required — CMS uses property proxying to track changes
Step 4: Create a reusable block type
Section titled “Step 4: Create a reusable block type”Blocks are reusable content components. Create a call-to-action block that can be placed on any page.
Create Models/CallToActionBlock.cs:
using EPiServer.Core;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace MyFirstSite.Models
{
[ContentType(
DisplayName = "Call to Action",
GUID = "b2c3d4e5-f6a7-8901-bcde-f23456789012",
Description = "A reusable call-to-action component")]
public class CallToActionBlock : BlockData
{
[Display(Name = "Heading", Order = 10)]
[Required]
public virtual string Heading { get; set; }
[Display(Name = "Description", Order = 20)]
public virtual string Description { get; set; }
[Display(Name = "Button Text", Order = 30)]
public virtual string ButtonText { get; set; }
[Display(Name = "Button Link", Order = 40)]
public virtual Url ButtonLink { get; set; }
}
} Step 5: Add a ContentArea to your page type
Section titled “Step 5: Add a ContentArea to your page type”Now connect blocks to pages. Add a ContentArea property to ArticlePage so authors can compose pages with blocks.
Add this property to your ArticlePage class:
[Display(
Name = "Sidebar Content",
Description = "Drag blocks here to build the sidebar",
GroupName = SystemTabNames.Content,
Order = 40)]
public virtual ContentArea SidebarContent { get; set; } This property lets authors drag and drop any block type (including the CallToActionBlock you just created) into the sidebar area.
Step 6: Create a start page type
Section titled “Step 6: Create a start page type”Every CMS site needs a start page — the root of the content tree. Create a simple start page type.
Create Models/StartPage.cs:
using EPiServer.Core;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace MyFirstSite.Models
{
[ContentType(
DisplayName = "Start Page",
GUID = "c3d4e5f6-a7b8-9012-cdef-345678901234",
Description = "The site's home page")]
public class StartPage : PageData
{
[Display(Name = "Site Title", Order = 10)]
[Required]
public virtual string SiteTitle { get; set; }
[Display(Name = "Introduction", Order = 20)]
public virtual XhtmlString Introduction { get; set; }
[Display(Name = "Main Content Area", Order = 30)]
public virtual ContentArea MainContentArea { get; set; }
}
} Step 7: Create views for your content types
Section titled “Step 7: Create views for your content types”Content types define the data; views define the presentation. Create Razor views that render your content types.
Create Views/ArticlePage/Index.cshtml:
@using EPiServer.Web.Mvc.Html
@model MyFirstSite.Models.ArticlePage
<article>
<h1>@Model.Headline</h1>
<p class="author">By @Model.Author</p>
<div class="article-body">
@Html.PropertyFor(m => m.ArticleBody)
</div>
<aside class="sidebar">
@Html.PropertyFor(m => m.SidebarContent)
</aside>
</article> Html.PropertyFor renders the property value and enables on-page editing — when an author is logged into CMS, they can click on these areas to edit content directly on the page.
Step 8: Run the application
Section titled “Step 8: Run the application”Build and run the application. The CMS creates the database and starts the admin interface.
dotnet run
# The site starts at https://localhost:5000
# CMS admin: https://localhost:5000/episerver/cms The first time you run, CMS will prompt you to create an admin account. After logging in, you will see the CMS editing interface.
Step 9: Create content in the editor
Section titled “Step 9: Create content in the editor”Now use the CMS editor to create your first content:
- Create a Start Page — In the content tree, create a new page using the “Start Page” type. Fill in the Site Title and Introduction.
- Create an Article Page — Under the Start Page, create a child page using “Article Page”. Fill in the Headline, Body, and Author.
- Add a Call to Action block — In the Article Page editor, find the Sidebar Content area. Click “Add block” and select “Call to Action”. Fill in the heading, description, and button text.
- Publish — Click “Publish” to make the content live.
Step 10: View your published content
Section titled “Step 10: View your published content”Navigate to your site root (https://localhost:5000) to see your published content rendered through the views you created.
Expected result: Your Start Page renders with the site title and introduction. Clicking through to the Article Page shows the headline, author, body text, and the Call to Action block in the sidebar.
Step 11: Enable on-page editing
Section titled “Step 11: Enable on-page editing”On-page editing lets authors edit content directly in the page preview. This is already enabled through the Html.PropertyFor helpers you used in Step 7. To test it:
- Log into the CMS admin interface
- Navigate to your Article Page in the content tree
- Click “Preview” to see the page with editable regions
- Click on any text area to edit it directly
Step 12: Next steps
Section titled “Step 12: Next steps”You now have a working CMS site with custom content types, blocks, and views. From here, you can:
- Add more content types — Create product pages, event pages, or landing pages
- Build more blocks — Image carousels, testimonials, feature grids
- Add personalization — Configure visitor groups to show different content to different audiences (see Visitor Groups and Personalization)
- Enable headless delivery — Connect Optimizely Graph to serve your content via API (see Content Delivery with Graph)
- Set up workflows — Configure approval workflows for content publishing