---
title: Marketing (dev)
slug: commerce-connect-15/docs/marketing-dev
docTags: 
createdAt: 2026-07-30T20:34:43.000Z
---

Use the API in Optimizely Commerce Connect to work with promotions (discounts) as part of a campaign.

Promotions (discounts) are available to end users under **Marketing** in the Commerce Connect user interface. By definition, a *campaign* is a container for creating and applying promotions. A *campaign* is also a content type, supported by the [content model](docId\:MKHqEwrszh7KCqfoErRzj) in Optimizely.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/_4NE8qgEr5QBJn3ox8bXE_5c0b30066967d391a28e32e763480d42bc804aa09978145a2b91508ba2b23331-image.png)

Classes in this topic are available in the following namespace:

- `EPiServer.Commerce.Marketing`

## Campaign content type

Commerce Connect provides one standard campaign type: `SalesCampaign`. This class is a basic content, which means it does not follow the normal publishing workflow of content. The class has the properties `IsActive`, `ValidFrom`, and `ValidUntil`, which control the campaign's status.

This class can also restrict a campaign to one or more visitor groups through the `VisitorGroup` property, which supports targeting customer segments. The sales campaign also has a `RevenueGoal` property.

```csharp
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Commerce.Marketing;
using EPiServer.Commerce.Marketing.DataAnnotations;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EPiServer.Commerce.Sample.Models.Campaigns {
  [ContentType(GUID = "530a7f07-8d12-4625-bda3-8e135a10b74d")]
  [AvailableContentTypes(Include = new [] {
    typeof (PromotionData)
  })]
  public class SeasonalCampaign: SalesCampaign {
    [Display(Order = 12, GroupName = SystemTabNames.PageHeader, Prompt = "Hero Image")]
    public virtual ContentReference HeroImage {
      get;
      set;
    }

    [Display(Order = 13, GroupName = SystemTabNames.PageHeader, Prompt = "Banner Image")]
    public virtual ContentReference BannerImage {
      get;
      set;
    }
  }
}
```

### Use render templates

Campaign content uses normal rendering templates. By default, only partial views are available for campaign content. To enable campaigns and pages, see [Campaign content as pages](docId:0PLACwd_XLl0fxM8O6ESI).

The following example defines a partial controller for campaign content:

```csharp
using EPiServer.Commerce.Marketing;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;

namespace MyOptimizelySite.Controllers {
  [TemplateDescriptor(Default = true)]
  public class CampaignPartialController: PartialContentComponent<SalesCampaign> {
    protected override IViewComponentResult InvokeComponent(SalesCampaign currentCampaign) {
      // Implementation of action view the page
      return View(currentCampaign);
    }
  }
}
```

The following example shows the rendering view for the campaign partial view:

```html
@using EPiServer.Core
@using EPiServer.Web.Mvc.Html

@model EPiServer.Commerce.Marketing.SalesCampaign

<h1>
  @Html.DisplayFor(m => m.Name) 
</h1> 
  
<h3>
  @Html.PropertyFor(m => m.Description) 
</h3>
```

To enable a full page rendering for a campaign, add a content controller:

```csharp
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using EPiServer;
using EPiServer.Commerce.Marketing;
using EPiServer.Core;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using MyOptimizelySite.Models.Pages;
using System.Web.Security;

namespace MyOptimizelySite.Controllers {
  [TemplateDescriptor(Default = true)]
  public class CampaignPageController: ContentController<SalesCampaign> {
    public ActionResult Index(SalesCampaign currentCampaign) {
      // Implementation of action view the page. 
      return View(currentCampaign);
    }
  }
}
```

The following example shows the rendering view for the campaign page view:

```html
@using EPiServer.Core
@using EPiServer.Web.Mvc.Html
    
@model EPiServer.Commerce.Marketing.SalesCampaign
    
<h1>
    @Html.DisplayFor(m => m.Name)
</h1>
<h3>
    @Html.PropertyFor(m => m.Description)
</h3>
```

### Campaign content as pages

The following example defines a campaign content type that implements `IRoutable`:

```csharp
using EPiServer.Commerce.Marketing;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.Web.Routing;
using System.ComponentModel.DataAnnotations;

namespace EPiServer.Commerce.Sample.Models.Campaigns {
  [ContentType(GUID = "530a7f07-8d12-4625-bda3-8e135a10b74d")]
  [AvailableContentTypes(Include = new [] {
    typeof (PromotionData)
  })]
  public class SeasonalCampaign: SalesCampaign, IRoutable {
    [Display(Order = 12, GroupName = SystemTabNames.PageHeader, Prompt = "Hero Image")]
    public virtual ContentReference HeroImage {
      get;
      set;
    }

    [Display(Order = 13, GroupName = SystemTabNames.PageHeader, Prompt = "Banner Image")]
    public virtual ContentReference BannerImage {
      get;
      set;
    }

    public virtual string RouteSegment {
      get;
      set;
    }
  }
}
```

Register a route for the campaign content type during initialization:

```csharp
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using EPiServer.Web.Routing;
using EPiServer.Web.Routing.Segments;
using System.Linq;
using System.Web.Routing;

namespace EPiServer.Commerce.Sample.Business.Initialization {
  /// <summary>
  /// Initialization module to handle the initialization of Commerce.
  /// </summary>
  [ModuleDependency(typeof (EPiServer.Commerce.Initialization.InitializationModule))]
  [InitializableModule]
  public class CampaignInitialization: IConfigurableModule {
    /// <summary>
    /// Initializes Commerce using the specified context.
    /// </summary>
    /// <param name="context">The context.</param>
    public void Initialize(InitializationEngine context) {
      EPiServer.Global.RoutesRegistered += Global_RoutesRegistered;
    }

    private void Global_RoutesRegistered(object sender, RouteRegistrationEventArgs e) {
      RegisterRoutes(RouteTable.Routes);
    }

    private static void RegisterRoutes(RouteCollection routes) {
      // Route for editing commerce content (which has a root not connected to the global root)
      MapCampaignRoute(routes,
        name: "campaignroot",
        url: "Campaigns/{language}/{nodeedit}/{partial}/{action}",
        defaults: new {
          action = "index"
        });
    }

    private static void MapCampaignRoute(RouteCollection routes, string name, string url, object defaults, object constraints = null) {
      var contentRootService = ServiceLocator.Current.GetInstance < ContentRootService > ();
      var root = ServiceLocator.Current.GetInstance < IContentLoader > ()
        .GetItems(contentRootService.List(), new LoaderOptions())
        .SingleOrDefault(x => x.Name.Equals("SysCampaignRoot"));
      var segmentRouter = ServiceLocator.Current.GetInstance < IUrlSegmentRouter > ();
      segmentRouter.RootResolver = (sd) => root.ContentLink;

      var parameters =
        new MapContentRouteParameters {
          UrlSegmentRouter = segmentRouter,
            BasePathResolver = null, //Use Cms default
            Direction = SupportedDirection.Both,
            Constraints = constraints
        };

      routes.MapContentRoute(
        name,
        url,
        defaults,
        parameters);
    }

    public void Uninitialize(InitializationEngine context) {
      // Uninitialize catalog content version
    }

    public void ConfigureContainer(ServiceConfigurationContext context) {}
  }
}
```

**Related blog post:** [Filter your commerce campaigns for access](https://jstemerdink.wordpress.com/2018/07/31/filter-your-commerce-campaigns-for-access/) by Jeroen Stemerdink
