Part 7 of the tutorial runs up the FAQ react to consume FAQ content from AEM Content Services.

Tutorial table of contents

The FAQ App

This tutorial uses a simple React JavaScript App to consume and display FAQ content exposed by AEM Content Services.

The use of React is largely unimportant, and the consuming app could be written in any language: Angular, Vue, Ember or any of their ilk for JavaScript apps, Objective C or Swift for iOS apps, Java or Kotlin for Android apps, and anything inbetween.

React is used for tutorial due to its popularity, relative simplicity and that it is written in JavaScript, a common language.

The tutorial React App is not intended to instruct how to build React apps or convey React best practices, but rather to illustrate how AEM Content Services can be consumed from technology outside of AEM-owned ecosystem.

How AEM Content Services drives the FAQ App experience

  1. The app logo as defined by the FAQ Main API page's first Image component.

  2. The header background image as defined by the FAQ Main API page's second Image component.

  3. The app title as defined on the FAQ Main API page's Title component.

  4. The introductory text as defined on the FAQ Main API page's Text component.

  5. The FAQ categories as defined on the FAQ Main API page's List component.

    The link labels are derived from each FAQ Listing API page's Title.

    Tapping on a FAQ category link results in an AJAX request to that specific FAQ category page (Orders, Shipping and Returns or Company) retrieving the JSON representation of the FAQ list to display below.

  6. This FAQ list is derived from the JSON representation of the requested FAQ Listing API page (Orders, Shipping and Returns or Company). The React app displays each FAQ in the JSON payload in the order defined by the author.

  7. Each FAQ is represented by the We.Retail Content Fragment component, which in turn, points at a Content Fragment created using the FAQ Content Fragment Model.

    Both the fields names (ie. "Question" and "Answer") and values are available to the React app.

FAQ App demonstration


FAQ App configuration for AEM Author

If AEM Publish is not used, and the Part 6b or All packages are installed on AEM Author, update the AEM host and port to be AEM Author (http://localhost:4502) in the FAQ App before connecting to AEM Content Services to initialize the app.


Running the FAQ App locally

  1. Download and unzip the FAQ App.

  2. Start a local Web server to serve the FAQ App.

    Any local Web server will work. If you do nothave a favorite, several free options are listed below:


    The host and port the Web server runs on MUST match in the Allowed Origins Regexp in the Cross-Origin Resource Sharing (CORS) OSGi configuration specified in Part 6.

    In Part 6, this should have been set to .*, allowing any host and port. If this guidance was not followed, ensure the entered value allows this local Web server to access AEM Publish.

  3. Using the local Web server, select the unzipped faq-react-app folder as the Web server's doc root.

  4. In a new Web browser window, request the host and port the Web server is serving the FAQ App from.

    The FAQ App serves from a "index.html" so the root path should suffice.

  5. Follow the steps on the right side of the FAQ App.

The FAQ App code

This section highlights the React JavaScript code that most interacts and depends on AEM Content Services and it's JSON output.

The initial interaction with AEM Content Services is an AJAX call to /content/we-retail/api/faqs.model.json.

Because this template is fixed, the React App can be coded to look for specific information in specific locations in the JSON response.

Notice on in lines 7 - 11, the React Component's state is updated with data from a well-known properties within the JSON response. For example:

  • The title from data[":items"].root[":items"].title.text
  • the logo from the first image compoent from findNthType(1, 'weretail/components/content/image', data[":items"].root).src
  • and the header background image from the second image component findNthType(2, 'weretail/components/content/image', data[":items"].root).src,

If the underlying Template structured of the FAQ Main API template changes disrupting the execpted structure, this application will not be able to source the content from the JSON response appropriately.

Notice there is a custom method findNthType(...) used to locate the Nth occurance of a component of a specific type. This is how the Logo Image component and Header Background Image component are determined; The logo is always the 1st and the Header Background Image is the 2nd.

let that = this;

fetch(host + '/content/we-retail/api/faqs.model.json').then(function (response) {
    response.json().then(function (data) {
            categories: data[":items"].root[":items"].list.items,
            logo: host + that.findNthType(1, 'weretail/components/content/image', data[":items"].root).src,
            headerImage: host + that.findNthType(2, 'weretail/components/content/image', data[":items"].root).src,
            title: data[":items"].root[":items"].title.text,
            intro: data[":items"].root[":items"].text.text,

A similar pattern is followed for retrieving each FAQ listing page's JSON. The different is because a variable number of FAQ's can be added to a Page, the JavaScript must collect and expose the data points in the correct order (the order is provided in the JSON as well).

Notice the layout container's order is retrieved via the :itemsOrder array on Line 6. This order is then used to retrieve the JSON Object, each representing a Content Fragament component, in lines 9 - 17.

let that = this;

fetch( + category.path + '.model.json').then(function (response) {
    response.json().then(function(data) {
        let items = data[":items"].root,
            order = items[":itemsOrder"],
            orderedFaqs = [];

        order.forEach(function(itemName) {
            let item = items[":items"][itemName];
            if (item) {
                    question: item.elements.question.value,
                    answer: item.elements.answer.value

            faqs: orderedFaqs

Finally, to render each FAQ collected in previous code snippet, a Faq React Component renders each JSON object, which represents a FAQ, collected into orderedFaqs.

  • this.props.faq.question is populated with the JSON for the question's authored value.
  • this.props.faq.answer is populated with the JSON property for the answer's authored value.
class Faq extends Component {
    render() {
        return (
            <div className={this.state.questionVisible ? 'faq faq--open' : 'faq'}>
                <a  href={'#faq'}
                    className="faq__toggle"> </a>

                <div className="faq__question">
                    <span className="faq__question-text"

                <div className="faq__answer"
                    dangerouslySetInnerHTML={{__html: this.props.faq.answer}} />

Building the FAQ App from source

  1. Download and unzip the file.
  2. Ensure npm is installed.
  3. Execute the following commands to start the FAQ React App on http://localhost:3000.
$ cd <to the unzipped src folder>
$ npm i -g npm
$ npm update

$ npm start

or optionally, specify the AEM host via the command line:

$ HOST= npm start

Next steps

Congratulations! You've completed with the AEM Content Services tutorial!

  • The aem-content-services-tutorial.all solution packages provides all the AEM Author configuration and content created in this tutorial.
    • Note the all package will enable anonymous access to AEM Author, so that starting an AEM Publish instance is optional.
  • The aem-content-services-tutorial.all package can also be installed on AEM Publish and will contain all the content and necessary CORS configurations.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy