Try our conversational search powered by Generative AI!

Alex Boesen
Jul 7, 2020
(3 votes)

Generate Typescript interfaces for pages and blocks

At Alm Brand we are using Vue.js together with the content delivery api for our Single Page Applications(SPA) that powers parts of our website and App, when we starting doing this we took a lot of inspiration from episerver musicfestival vue template and we ended up with what we call the "Vue-Epi Framework" (yea the name needs work).

One of our key differences with the template, and the focus for this blog post, is we are using TypeScript which leads to the question:
How do we get that sweet typing for our episerver models when using the content delivery api? read on and find out!

Typescript all the things

The first thing to do is find or create the folder where your typescript definitions lives and create a file named content.d.ts that maps all the base/common types/properties like the short sample below

/** interface for ContentLanguage coming from episerver content delivery api */
export interface ContentLanguage {
    /** the link to the this langauge version of the current content */
    link: string;
    /** the localized displayName for the language */
    displayName: string;
    /** the ISO name for the language */
    name: string;

/** interface for ContentReference coming from episerver content delivery api */
export interface ContentReference {
    /** the episerver content id */
    id: number;
    /** the episerver content work id */
    workId: number;
    /** the guid id of the content */
    guidValue: string;
    /** the content providerName */
    providerName?: string;
    /** url to the content (block points to the frontpage) */
    url: string;

(full file here

That is used by the ContentTypeCodeGenerator, a simple mapper that scans our assemblies for content, enums and "other things" to turn them into typescript interfaces and saving those to a file.

private void GenerateTypescriptInterfaces()
    IEnumerable<Assembly> assemblies = GetAssemblies();
    IEnumerable<Type> types = assemblies.SelectMany(a => GetTypesFromAssembly(a)).ToList();
    var contentTypes = types.Where(t => t.GetCustomAttribute<ContentTypeAttribute>() != null && !typeof(IContentMedia).IsAssignableFrom(t))
    StringBuilder builder = new StringBuilder();
    builder.AppendLine("import { IContent, ContentLanguage, ContentReference } from './content'");
    GenerateTypescriptEnums(types, builder)
    foreach (var contentType in contentTypes)
        Logger.Information("Adding {ContentType} as typescript interface", contentType.Name);
        builder.AppendLine($"export interface {contentType.Name} extends IContent {{");
    var fileText = builder.ToString();
    if (HasFileContentChanged(fileText))
        File.WriteAllText(FilePath, fileText);

(full class here)

most of the secret sauce is in the GetDataType method that maps an property to an typescript type

string GetDataType(Type contentType, PropertyInfo property)
    if (TypeMappings.TryGetValue(property.PropertyType, out var func))
        return func(contentType, property);
        if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
            return FindListType(contentType, property);
        return property.PropertyType.Name;

As you can see we either lookup the type in TypeMappings or return the name of the property type(mostly used with Enum and Block properties on models)
the TypeMappings is a simple Dictionary with mappings as can seen below 

private static IDictionary<Type, Func<Type, PropertyInfo, string>> CreateTypeMapping()
    var mappingDictionary = new Dictionary<Type, Func<Type, PropertyInfo, string>>();
    mappingDictionary.Add(typeof(string), (t, p) => "string");
    mappingDictionary.Add(typeof(int), (t, p) => "number");
    mappingDictionary.Add(typeof(int?), (t, p) => "number");
    mappingDictionary.Add(typeof(decimal), (t, p) => "number");
    mappingDictionary.Add(typeof(decimal?), (t, p) => "number");
    mappingDictionary.Add(typeof(float), (t, p) => "number");
    mappingDictionary.Add(typeof(float?), (t, p) => "number");
    mappingDictionary.Add(typeof(double), (t, p) => "number");
    mappingDictionary.Add(typeof(double?), (t, p) => "number");
    mappingDictionary.Add(typeof(bool), (t, p) => "boolean");
    mappingDictionary.Add(typeof(bool?), (t, p) => "boolean");
    mappingDictionary.Add(typeof(DateTime), (t, p) => "string");
    mappingDictionary.Add(typeof(DateTime?), (t, p) => "string");
    mappingDictionary.Add(typeof(ContentReference), (t, p) => GetContentReferenceType(t, p));
    mappingDictionary.Add(typeof(PageReference), (t, p) => "ContentReference");
    mappingDictionary.Add(typeof(ContentArea), (t, p) => "Array<IContent>");
    mappingDictionary.Add(typeof(LinkItemCollection), (t, p) => "Array<string>");
    mappingDictionary.Add(typeof(PropertyContentReferenceList), (t, p) => "Array<IContent>");
    mappingDictionary.Add(typeof(Url), (t, p) => "string");
    mappingDictionary.Add(typeof(XhtmlString), (t, p) => "string");
    return mappingDictionary;
    string GetContentReferenceType(Type contentType, PropertyInfo property)
        //we convert ContentReferences that point to Images to an url in content delivery api
        var uiHint = property.GetCustomAttribute<UIHintAttribute>();
        if (uiHint?.UIHint == UIHint.Image)
            return "string";
        return "ContentReference";

the resulting file is like this (only with much more data of course)

export enum TileVariantEnum {

export interface TileBlock extends IContent {
  tileVariant: TileVariantEnum;
  title: string;
  icon: string;
  link: string;
  phoneNumber: string;
  renderHtml: boolean;
  hide: boolean;

You can now import your models in your typescript code and get that sweet, sweet typing experience.


import { Vue, Component } from "vue-property-decorator";
import { PropType } from "vue";
import { mapState } from "vuex";
import template from './TileBlock.vue';
import { TileBlock, TileVariantEnum } from "@scripts/definitions/episerver/content-types";
import { AbLink } from "@scripts/app/components/sharedcomponents/baseComponents/components";

    name: 'TileBlockComponent',
    components: {
    computed: mapState<any>({
        isEditable: state => state.epiContext.isEditable,
        parentModel: state => state.epiContent.model
    props: {
        model: Object as PropType<TileBlock>

export default class TileBlockComponent extends Vue {
    model: TileBlock;
    path: string = "";

    mounted() {
        this.path =;
        if (this.model.tileVariant === TileVariantEnum.Call) {
            this.path = this.model.phoneNumber;

There is some small cavats with this, it assumes you are using SetFlattenPropertyModel(true) and SetExtendedContentTypeModel(true) with content delivery api and some of the mappings like ContentReference to string when it have the UIHint.Image attribute also requires you to expand the content delivery api with an custom IPropertyModelConverter to actually make the conversion but that another blog post ;) 

we call this code from both an InitializableModule when on localhost for devs to always have an up to date version of the file and for our deployments we have an CI/CD pipeline that does the same thing to make sure the frontend can compile with the code that are currently building.

Play around with ContentTypeCodeGenerator and content.d.ts and see if it is something you can use :) 

Jul 07, 2020


Michael Clausing
Michael Clausing Jul 7, 2020 08:41 PM

We are working on an addon for content management using react, and have had some success using Reinforced.Typings to generate Typescript interfaces/enums. It took a little trial an error to find the correct settings, but I think it works pretty well now.

Please login to comment.
Latest blogs
Optimizely Opal... what it does actually do?

At Opticon 2023, Optimizely announced its first AI product Opal. AI is definitely the new tech buzzword in 2024 and with promises that AI will be...

Jon Jones | Feb 25, 2024 | Syndicated blog

How to add more Content Area Context Menu Item in Optimizely CMS 12

Hey folks, today I will share something related to Context Menu customization in the Content Area of Optimizely CMS. As you know, the content area ...

Binh Nguyen | Feb 25, 2024

Developer meetups in Stockholm & Helsinki

It's time for developer meetups! Next month we will be in Stockholm and Helsinki. Join us for getting the latest updates from Optimizely, be inspir...

Magnus Kjellander | Feb 23, 2024

Roll Your Own Security Headers

Proper security headers are a must for your Optimizely driven website. There are a variety of tools out there that will help with this, but when...

Ethan Schofer | Feb 21, 2024