Frontend SDK - v1 vs v2 examples

This page documents the breaking changes between Magnolia Frontend SDK v1 and v2, with parallel code examples for Angular, React, and Vue frameworks.

Migration checklist

Key steps to migrate from v1 to v2

  1. Update Package Imports

    Replace wrapper imports with direct imports from @magnolia/frontend-helpers-base.

  2. Remove iframe-based Detection

    Replace inIframe(), inEditor(), inEditorAsync() with EditorContextService.getMagnoliaContext().

  3. Update EditablePage Properties

    Remove: templateDefinitions.

    Add: magnoliaContext, pass config and templateAnnotations as props.

  4. Rewrite Extended Components (React only)

    Convert class-based extensions to functional wrapper components.

  5. Update TypeScript Declarations

    Add proper type annotations using new TypeScript types.

  6. Test EditableArea Custom Views

    Verify custom rendering works with new children processing.

  7. Update Configuration Files

    Add TypeScript types to config files if using TypeScript.

  8. Test SSR/Personalization

    Verify server-side rendering works on both author and public instances.

Package installation & imports

The EditorContextHelper and PersonalizationService wrappers have been removed. All functions must be imported directly from @magnolia/frontend-helpers-base.

  • Angular

  • React

  • Vue

v1 (Old Approach) v2 (New Approach)
import { EditorContextService } from '@magnolia/angular-editor';

export class AppComponent {
  constructor(
    private editorContext: EditorContextService
  ) { }
}
import {
  EditorContextService,
  PersonalizationService
} from '@magnolia/frontend-helpers-base';
v1 (Old Approach) v2 (New Approach)
import {
  EditablePage,
  EditorContextHelper
} from '@magnolia/react-editor';
import {
  EditorContextService,
  PersonalizationService
} from '@magnolia/frontend-helpers-base';
v1 (Old Approach) v2 (New Approach)
import {
  EditablePage,
  EditorContextHelper
} from '@magnolia/vue-editor';
import {
  EditorContextService,
  PersonalizationService
} from '@magnolia/frontend-helpers-base';

Editor context detection

The new magnoliaContext object standardizes usage across all frameworks and determines page mode (edit/preview) instead of iframe-based detection methods like inEditor or inEditorAsync.

  • Angular

  • React

  • Vue

v1 (Iframe-based Detection) v2 (magnoliaContext Object)
import { EditorContextService } from '@magnolia/angular-editor';

export class AppComponent {
  constructor(
    private editorContext: EditorContextService
  ) { }

  async checkEditor() {
    // Check if in iframe
    if (this.editorContext.inIframe()) {
      // Fetch template annotations
      const templateAnnotationsRes = await fetch(
        'http://localhost:8080/.rest/template-annotations/v1/spa-home'
      );
      const templateAnnotations = await templateAnnotationsRes.json();

      this.editorContext.setTemplateAnnotations(
        templateAnnotations
      );
    }
  }
}
import {
  EditorContextService,
  IMagnoliaContext,
  MgnlContent,
  MgnlTemplateAnnotations
} from '@magnolia/frontend-helpers-base';
import {
  EditablePage,
  MagnoliaConfig
} from '@magnolia/angular-editor';
import { config } from './magnolia.config';

@Component({
  templateUrl: './root.component.html'
})
export class RootComponent {
  content?: MgnlContent;
  templateAnnotations?: MgnlTemplateAnnotations;
  magnoliaContext?: IMagnoliaContext;
  magnoliaConfig: MagnoliaConfig;

  constructor(private router: Router) {
    this.magnoliaConfig = config;

    // Use EditorContextService.getMagnoliaContext()
    this.magnoliaContext = EditorContextService.getMagnoliaContext();
  }
}
v1 (Iframe-based Detection) v2 (magnoliaContext Object)
import {
  EditablePage,
  EditorContextHelper
} from '@magnolia/react-editor';

async componentDidMount() {
  let templateAnnotations;

  const pageRes = await fetch(
    'http://localhost:8080/.rest/pages/spa-home'
  );
  const page = await pageRes.json();

  // Using EditorContextHelper.inIframe()
  if (EditorContextHelper.inIframe()) {
    const templateAnnotationsRes = await fetch(
      'http://localhost:8080/.rest/template-annotations/v1/spa-home'
    );
    templateAnnotations = await templateAnnotationsRes.json();
  }

  this.setState({ page, templateAnnotations });
}
import {
  EditorContextService
} from '@magnolia/frontend-helpers-base';
import {
  EditablePage
} from '@magnolia/react-editor';
import { useState, useEffect } from 'react';
import { config } from './magnolia.config';

function App() {
  const [content, setContent] = useState(null);
  const [templateAnnotations, setTemplateAnnotations] = useState(null);

  // Get magnoliaContext from EditorContextService
  const magnoliaContext = EditorContextService.getMagnoliaContext();

  useEffect(() => {
    // Fetch content and annotations logic here
    // fetchPageData();
  }, []);

  return (
    <EditablePage
      content={content}
      config={config}
      templateAnnotations={templateAnnotations}
      magnoliaContext={magnoliaContext}
    />
  );
}
v1 (Iframe-based Detection) v2 (magnoliaContext Object)
import {
  EditablePage,
  EditorContextHelper
} from '@magnolia/vue-editor';

async mounted() {
  const pageRes = await fetch(
    'http://localhost:8080/magnoliaAuthor/.rest/pages/spa-home'
  );
  const page = await pageRes.json();

  // Using EditorContextHelper.inIframe()
  if (EditorContextHelper.inIframe()) {
    const templateAnnotationsRes = await fetch(
      'http://localhost:8080/magnoliaAuthor/.rest/template-annotations/v1/spa-home'
    );
    const templateAnnotations = await templateAnnotationsRes.json();

    this.templateAnnotations = templateAnnotations;
  }

  this.page = page;
}
import {
  EditorContextService
} from '@magnolia/frontend-helpers-base';
import {
  EditablePage
} from '@magnolia/vue-editor';
import { config } from './magnolia.config';

export default {
  components: { EditablePage },
  data() {
    return {
      content: null,
      templateAnnotations: null,
      // Get magnoliaContext
      magnoliaContext: EditorContextService.getMagnoliaContext(),
      config
    };
  },
  async mounted() {
    // Fetch page data logic
    // await this.fetchPageData();
  }
}

EditablePage component configuration

  • Removed: templateDefinitions property.

  • Removed: EditorContextService methods for templateAnnotations and config.

  • Added: magnoliaContext property (required).

  • Added: config property passed directly to component.

  • Added: templateAnnotations property passed directly to component.

  • Angular

  • React

  • Vue

v1 (Configuration) v2 (Configuration)
<editable-page
  [content]="content"
  [templateDefinitions]="templateDefinitions">
</editable-page>
<editable-page
  [content]="content"
  [templateAnnotations]="templateAnnotations"
  [magnoliaContext]="magnoliaContext"
  [config]="magnoliaConfig">
</editable-page>
v1 (Configuration) v2 (Configuration)
<EditablePage
  content={page}
  config={config}
  templateDefinitions={templateDefinitions}
  templateAnnotations={templateAnnotations}
/>
<EditablePage
  content={content}
  config={config}
  templateAnnotations={templateAnnotations}
  magnoliaContext={magnoliaContext}
/>
v1 (Configuration) v2 (Configuration)
<EditablePage
  v-if="page"
  v-bind:content="page"
  v-bind:config="config"
  v-bind:templateDefinitions="templateDefinitions"
  v-bind:templateAnnotations="templateAnnotations"
/>
<EditablePage
  v-if="content"
  v-bind:content="content"
  v-bind:config="config"
  v-bind:templateAnnotations="templateAnnotations"
  v-bind:magnoliaContext="magnoliaContext"
/>

EditableArea component

  • Angular: Now supports both tag and directive syntax.

  • Angular: editable-area can have additional children besides content and customView.

  • React: Updated children processing logic.

  • Angular

  • React

  • Vue

v1 (Configuration) v2 (Enhanced Support)
<div class="main-area">
  <editable-area [content]="main">
  </editable-area>
</div>
<div class="flex flex-col min-h-screen">
  <main>
    <editable-area [content]="main">
    </editable-area>
  </main>

  <div
    editable-area
    [content]="footer"
    [customView]="renderFooter">
  </div>

  <ng-template #renderFooter let-components="components">
    <app-footer-renderer [components]="components">
    </app-footer-renderer>
  </ng-template>
</div>
v1 (Configuration) v2 (Enhanced Support)
import { EditableArea } from '@magnolia/react-editor';

function PageComponent(props) {
  return (
    <div className="main-area">
      <EditableArea content={props.main} />
    </div>
  );
}
import { EditableArea } from '@magnolia/react-editor';

function PageComponent(props) {
  return (
    <div>
      <EditableArea content={props.main}>
        {/* Can now include additional children */}
      </EditableArea>

      <EditableArea
        content={props.footer}
        customView={(components) => (
          <FooterRenderer components={components} />
        )}
      />
    </div>
  );
}
v1 (Configuration) v2 (Enhanced Support)
<template>
  <div class="main-area">
    <EditableArea :content="main" />
  </div>
</template>
<template>
  <div class="page-container">
    <EditableArea :content="main" />

    <EditableArea
      :content="footer"
      :customView="renderFooter"
    />
  </div>
</template>

<script>
export default {
  methods: {
    renderFooter(components) {
      return h(FooterRenderer, { components });
    }
  }
}
</script>

Component types and extensibility (React Only)

  • EditablePage, EditableArea, and EditableComponent are now functional components.

  • Cannot extend these components using class inheritance.

  • Custom components must be rewritten as wrapper components or composition patterns.

v1 (Class-based) v2 (Functional)
import React from 'react';
import {
  EditablePage,
  EditableArea,
  EditableComponent
} from '@magnolia/react-editor';

class CustomArea extends EditableArea {
  render() {
    return (
      <div className="custom-area">
        {super.render()}
      </div>
    );
  }
}

class CustomPage extends EditablePage {
  componentDidMount() {
    super.componentDidMount();
    // Additional logic
  }
}

export { CustomArea, CustomPage };
import React from 'react';
import {
  EditablePage,
  EditableArea,
  EditableComponent
} from '@magnolia/react-editor';

function CustomArea({ content, ...props }) {
  return (
    <div className="custom-area">
      <EditableArea content={content} {...props} />
    </div>
  );
}

function CustomPage({ content, config, ...props }) {
  React.useEffect(() => {
    // Additional logic
  }, []);

  return (
    <div className="custom-page">
      <EditablePage
        content={content}
        config={config}
        {...props}
      />
    </div>
  );
}

export { CustomArea, CustomPage };

Configuration files

  • Configuration structure remains the same.

  • TypeScript support added with MagnoliaConfig type.

  • Component mapping syntax unchanged.

  • Angular

  • React

  • Vue

v1 (Configuration) v2 (Configuration)
ngOnInit(): void {
  this.editorContext.setComponentMapping({
    'angular-minimal-lm:pages/basic': BasicComponent,
    'angular-minimal-lm:pages/contact': ContactComponent,
    'spa-lm:components/headline': HeadlineComponent,
    'spa-lm:components/image': ImageComponent
  });
}
import { MagnoliaConfig } from '@magnolia/angular-editor';
import { BasicComponent } from './pages/Basic.component';
import { ContactComponent } from './pages/Contact.component';
import { HeadlineComponent } from './components/Headline.component';

export const config: MagnoliaConfig = {
  componentMappings: {
    'angular-minimal-lm:pages/basic': BasicComponent,
    'angular-minimal-lm:pages/contact': ContactComponent,
    'spa-lm:components/headline': HeadlineComponent
  }
};
v1 (Configuration) v2 (Configuration)
import Basic from './pages/Basic';
import Contact from './pages/Contact';
import Headline from './components/Headline';
import Image from './components/Image';
import Paragraph from './components/Paragraph';

const config = {
  componentMappings: {
    'react-minimal-lm:pages/basic': Basic,
    'react-minimal-lm:pages/contact': Contact,
    'spa-lm:components/headline': Headline,
    'spa-lm:components/image': Image,
    'spa-lm:components/paragraph': Paragraph
  }
};

export default config;
import Basic from './pages/Basic';
import Contact from './pages/Contact';
import Headline from './components/Headline';
import Image from './components/Image';
import Paragraph from './components/Paragraph';
import { MagnoliaConfig } from '@magnolia/react-editor';

const config: MagnoliaConfig = {
  componentMappings: {
    'react-minimal-lm:pages/basic': Basic,
    'react-minimal-lm:pages/contact': Contact,
    'spa-lm:components/headline': Headline,
    'spa-lm:components/image': Image,
    'spa-lm:components/paragraph': Paragraph
  }
};

export { config };
v1 (Configuration) v2 (Configuration)
import Basic from './pages/Basic.vue';
import Contact from './pages/Contact.vue';
import Headline from './components/Headline.vue';

const config = {
  componentMappings: {
    'vue-minimal-lm:pages/basic': Basic,
    'vue-minimal-lm:pages/contact': Contact,
    'spa-lm:components/headline': Headline
  }
};

export default config;
import Basic from './pages/Basic.vue';
import Contact from './pages/Contact.vue';
import Headline from './components/Headline.vue';
import { MagnoliaConfig } from '@magnolia/vue-editor';

export const config: MagnoliaConfig = {
  componentMappings: {
    'vue-minimal-lm:pages/basic': Basic,
    'vue-minimal-lm:pages/contact': Contact,
    'spa-lm:components/headline': Headline
  }
};

TypeScript support

  • Angular

  • React

  • Vue

v1 (Limited TypeScript) v2 (Full TypeScript)
export class AppComponent {
  content: any;
  templateDefinitions: any;

  constructor(
    private editorContext: EditorContextService
  ) { }
}
import {
  EditorContextService,
  IMagnoliaContext,
  MgnlContent,
  MgnlTemplateAnnotations
} from '@magnolia/frontend-helpers-base';
import {
  EditablePage,
  MagnoliaConfig
} from '@magnolia/angular-editor';
import { config } from './magnolia.config';

@Component({
  templateUrl: './root.component.html'
})
export class RootComponent {
  content?: MgnlContent;
  templateAnnotations?: MgnlTemplateAnnotations;
  magnoliaContext?: IMagnoliaContext;
  magnoliaConfig: MagnoliaConfig;

  constructor(private router: Router) {
    this.magnoliaConfig = config;
    this.magnoliaContext = EditorContextService.getMagnoliaContext();
  }
}
v1 (Limited TypeScript) v2 (Full TypeScript)
class App extends React.Component {
  state = {};

  async componentDidMount() {
    let templateAnnotations;
    const pageRes = await fetch(pageUrl);
    const page = await pageRes.json();

    this.setState({
      page,
      templateAnnotations
    });
  }
}
import {
  EditorContextService,
  MgnlContent,
  MgnlTemplateAnnotations,
  IMagnoliaContext
} from '@magnolia/frontend-helpers-base';
import {
  EditablePage,
  MagnoliaConfig
} from '@magnolia/react-editor';
import { useState } from 'react';
import { config } from './magnolia.config';

interface AppState {
  content: MgnlContent

null; templateAnnotations: MgnlTemplateAnnotations

null; }

function App(): JSX.Element { const [content, setContent] = useState<MgnlContent

null>(null); const [templateAnnotations, setTemplateAnnotations] = useState<MgnlTemplateAnnotations

null>(null);

const magnoliaContext: IMagnoliaContext = EditorContextService.getMagnoliaContext();

return ( <EditablePage content={content} templateAnnotations={templateAnnotations} magnoliaContext={magnoliaContext} config={config} /> ); } ----

v1 (Limited TypeScript) v2 (Full TypeScript)
export default {
  data() {
    return {
      content: null,
      templateDefinitions: null
    }
  }
}
import {
  EditorContextService,
  MgnlContent,
  MgnlTemplateAnnotations,
  IMagnoliaContext
} from '@magnolia/frontend-helpers-base';
import { EditablePage, MagnoliaConfig } from '@magnolia/vue-editor';
import { config } from './magnolia.config';

export default {
  data() {
    return {
      content: null as MgnlContent

null, templateAnnotations: null as MgnlTemplateAnnotations

null, magnoliaContext: EditorContextService.getMagnoliaContext() as IMagnoliaContext, config: config as MagnoliaConfig } } } ----

Server-side rendering (SSR) and personalization

Full SSR support in v2:

  • Server-Side Rendering enabled for both author and public instances.

  • Author instance supports SSR of personalized content.

  • Improved performance and SEO.

  • Better support for meta-frameworks (Next.js, Nuxt, etc.).

  • Angular

  • React

  • Vue

v1 (No SSR awareness) v2 (Supports SSR)
export class AppComponent {
  ngOnInit(): void {
    // Only works on client
    this.editorContext.loadPageContent();
  }
}
export class AppComponent {
  async ngOnInit(): Promise<void> {
    const magnoliaContext = EditorContextService.getMagnoliaContext();
    const page = await fetchPageData(magnoliaContext);
    this.content = page;
  }
}
v1 (No SSR awareness) v2 (Supports SSR)
class App extends React.Component {
  async componentDidMount() {
    const res = await fetchPage();
    this.setState({ content: res });
  }
}
function App() {
  const magnoliaContext = EditorContextService.getMagnoliaContext();
  const [content, setContent] = useState<MgnlContent
v1 (No SSR awareness) v2 (Supports SSR)
export default {
  mounted() {
    fetchPage().then(res => this.content = res);
  }
}
export default {
  data() {
    return {
      content: null as MgnlContent
Feedback

DX Core

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules