Atlas Content Modeler Walk-Through

17 minute read
Kellen Mace
Kellen Mace
Sr. Staff Developer Advocate

In my recent “What is Atlas Content Modeler?” blog post, I described Atlas Content Modeler (ACM) as a single tool that allows you to create content models and expose them in the WPGraphQL/REST API schema– something that previously required cobbling together several other plugins.

In this post, we’ll walk through all the features that ACM brings to the table, including the new Taxonomy and Relationships features. We’ll see how we can easily build our content models in the WordPress admin, then see an example Next.js app that consumes the data those models provide and uses it to render the pages of our app.

What We’ll Build

To showcase ACM’s superpowers, we’re going to build the most exciting app imaginable:

🎉 A Healthcare Provider Registry! 🎉 🙃

Healthcare provider registry

We’ll use ACM to create the content models we need in WordPress, then we’ll query for that data from a decoupled frontend Next.js app.

First things first. Let’s set the scene. 🌆

We’ve been tasked with building a web app that provides a listing of healthcare professionals. It needs to display a number of details about each provider including the locations where they practice.

In our talks with the client, we’ve learned that the following three types of pages are required for the minimum viable product:

Pages

  • /providers – This “Providers” page should contain a list of cards, one for each provider.
  • /provider/123 – This “Provider” page should contain the info about an individual provider. “123” in this example represents the post ID of the provider.
  • /location/456 – This “Location” page should contain the info about an individual location. “456” in this example represents the post ID of the location.

Content Models

Now that we know what pages need to exist in the frontend of our app, we need to think about what content models we need to create to store the necessary data. After some deliberation, we come up with these:

Provider

  • Name – Text field. The person’s name.
  • Credentials – Text Field (MD or DO, for example)
  • Profile Photo – Media Field.
  • Bio – Rich Text field. This is a blurb about the provider.
  • Practicing Since – Number field. This is the year the provider began practicing medicine.
  • Locations – Relationship field. These relationships between ProviderLocations represent the locations where the provider practices.
  • Specialty – Taxonomy. These taxonomy terms represent the provider’s specialties (Emergency Medicine, Pediatrics, etc.).

Location

  • Name – The name of the location.
  • Description – A description of the location.
  • Photo – A photo of the location.

Now that we have our game plan together for what pages we’ll need and what content models we’ll need to set up in Atlas Content Modeler, let’s hop to it!

Create Content Models

If you’d like to follow along, you can spin up a new local WordPress site, then install and activate the WPGraphQL and Atlas Content Modeler plugins.

Let’s create our Location model first, since it’s a bit simpler.

Location Model

In the WordPress admin sidebar, we’ll go to Content Modeler > Models and click the button to create a new model.

We’ll fill out the fields as follows:

  • Give the model a Singular Name of “Location” and a Plural Name of “Locations”.
  • Stick with the auto-generated Model ID of “location”.
  • Set the API Visibility to “Public” so we’ll be able to query for Location data from our decoupled JS app.
  • Select a Model Icon.
  • Enter a Description for the model.
Location content model registration

Then click the button to create the new model.

In the WordPress admin sidebar, you’ll see that a menu item has been added for our new Location custom post type.

Location Model Fields

Hit the + button to add a new field to our Location model.

Fist, let’s create the Name field to store the name of each location. We’ll set the field up like this:

  • Select Text for the type of field.
  • Enter a Name of “Name”.
  • Stick with the auto-generated API Identifier of “name”.
  • Select an Input Type of “Single line”.
Name field creation

Then click the button to create the field.

We’ll create the Description and Photo fields in a similar fashion. They’ll end up looking like this:

Description:

  • Type of field: Rich Text.
  • Name: “Description”.
  • API Identifier: “description”.
Description field creation

Photo:

  • Type of field: Media.
  • Name: “Photo”.
  • API Identifier: “photo”.
Photo field creation

That’s it in terms of fields for our Location model.

Next let’s see how our content creators can create and edit Location data. From the WordPress admin sidebar, go to Locations > Add New. You’ll see the fields we registered reflected here.

New Location page screenshot

Go ahead and create a few dummy Location posts so we’ll have some data to work with.

Location Model Query

At this point, our Location model and its fields have been created, and ACM has automatically added them to the GraphQL schema for us. This means that things are already set up for us to query for Location data from our decoupled frontend JS app! Next let’s fire off a test query to try that out.

Head back to the Content Modeler > Models page.

Click the ... icon next to our Location model, then click Open in GraphiQL.

Open in GraphiQL screenshot

This will shoot you over to the embedded GraphiQL IDE that the WPGraphQL plugin provides and auto-populate it with a query and fragment for the Location model’s data.

Go ahead and click the ▶️ icon to fire off the query, and you’ll see that the data for the Location posts you had created comes back in the response. Magic! ✨

GraphiQL query for Locations

We’ll make use of some of these GraphQL fields in our Next.js app in just a bit. For now, let’s turn our attention to the other Provider model we need to create.

Provider Model

In the WordPress admin sidebar, head back to Content Modeler > Models and click the button to create a new model.

We’ll fill out the fields as follows:

  • Give the model a Singular Name of “Provider” and a Plural Name of “Providers”.
  • Stick with the auto-generated Model ID of “provider”.
  • Set the API Visibility to “Public” so we’ll be able to query for Provider data from our decoupled JS app.
  • Select a Model Icon.
  • Enter a Description for the model.
Provider model screenshot

Then we’ll click the button to create the new model.

In the WordPress admin sidebar, you’ll see that a menu item has been added for our new Provider custom post type.

Provider Model Fields

Hit the + button to add a new field.

The first several fields we’ll add the same way we added fields to the Location model. Here are the settings to use:

  • Name – Make this a single-line Text field with a Name of “Name” and an API Identifier of “name”.
  • Credentials – Make this a single-line Text field with a Name of “Credentials” and an API Identifier of “credentials”.
  • Bio – Make this a Rich Text field with a Name of “Bio” and an API Identifier of “bio”.
  • Practicing Since – Make this a Number field with a Name of “Practicing Since” and an API Identifier of “practicingSince”. For Number Type, select “Integer”.

Next, we’ll see our first and only Relationship field. This kind of field is very useful for maintaining relationships between posts. In our app, we’re going to use it to keep track of which Providers practice at which Locations.

Click the + button to add one more field. Configure it like this:

  • Select a field type of Relationship.
  • Enter a Name of “Locations”.
  • Stick with the auto-generated API Identifier of “locations”.
  • For Model to Reference, select “Locations”. This tells ACM which model to use at the other end of this relationship.
  • For Connections, select “Many to Many”, since many of our healthcare Providers will practice at many different Locations.
  • Enter a Description of “Locations where the provider practices” to help us remember what this relationship represents.
Provider Locations relationship field

Click the button to create this field.

Specialty Taxonomy

We’re done creating our fields for the Provider model, but we still need a way to group together Providers based on their specialty (Emergency Medicine, Pediatrics, etc.). That’s a perfect use-case for a WordPress taxonomy.

From the WordPress admin sidebar, go to Content Modeler > Taxonomies.

In the Add New section, configure a new taxonomy thusly:

  • Enter a Singular Name of “Specialty”.
  • Enter a Plural Name of “Specialties”.
  • Stick with the auto-generated Taxonomy ID of “specialty”.
  • For Models, select “Providers”.
  • Set API Visibility to “Public” to make sure this taxonomy is added to the GraphQL schema.
Specialty taxonomy

Click the button to create the taxonomy.

As a final step in this section, head to Providers > Add New in the WordPress admin sidebar. Then create a few dummy Provider posts so we have some data to work with.

Provider Model Query

Our Provider model has been created, including its locations Relationship field and the Specialty taxonomy. Just as we did for the Location model, let’s fire off a test query to take it for a spin.

Head back to the Content Modeler > Models page.

Click the ... icon next to our Provider model, then click “Open in GraphiQL”.

This will once again shoot you over to the embedded GraphiQL IDE that the WPGraphQL plugin provides. This time, it will be auto-populated with a query and fragment for the Provider model’s data.

If it’s not there already, add in fields to get the Specialty taxonomy term names for each Provider, like this:

specialties {
  nodes {
    name
  }
}

Go ahead and click the ▶️ icon to fire off the query, and you’ll see that the data for the Provider posts you had created comes back in the response.

GraphiQL Provider query

Done! Our Location and Provider content models are now fully built out, and we’ve seen how we can query for each via WPGraphQL. Now let’s query for this data from our frontend Next.js app.

Next.js App Walkthrough

You can follow these steps to get up and running with the Next.js app on your machine:

  1. Clone down the Next.js app repo.
  2. Create a .env.local file inside of the app’s root folder. Open that file in a text editor and paste in NEXT_PUBLIC_WORDPRESS_API_URL=http://acm-demo.local/graphql, replacing http://acm-demo.local with the domain for your local WordPress site. This is the endpoint that Apollo Client will use when it sends requests to your WordPress backend.
  3. Run npm install (or yarn) to install the app’s NPM dependencies.
  4. Run npm run dev to get the server running locally.
  5. You should now be able to visit http://localhost:3000/ in a web browser and see the app’s homepage.

Providers Page

Let’s first take a look at the Providers page. Navigate to /providers. You should see a list of Provider cards. Let’s open up pages/providers.js in a code editor to see how it works.

export default function Providers({ providers }) {
  return (
    <Layout>
      <h1>Providers</h1>
      <ProvidersList providers={providers} />
    </Layout>
  );
}

export async function getStaticProps() {
  const response = await client.query({
    query: GET_PROVIDERS,
  });

  return {
    props: {
      providers: response?.data?.providers?.nodes ?? [],
    },
  };
}

You can see that inside of Next.js’ getStaticProps() function, we’re using Apollo Client to query for the list of providers. That data is then passed into the Providers component as a prop.

The GraphQL query that we’re firing off to get this data looks like this:

const GET_PROVIDERS = gql`
  query getProviders {
    providers(first: 10) {
      nodes {
        ...ProviderCardFields
      }
    }
  }
  ${PROVIDER_CARD_FIELDS}
`;

…and the ProviderCardFields fragment that it uses is being pulled in from components/ProviderCard.js. It looks like this:

export const PROVIDER_CARD_FIELDS = gql`
  fragment ProviderCardFields on Provider {
    databaseId
    uri
    name
    credentials
    profilePhoto {
      sourceUrl
      altText
    }
    specialties {
      nodes {
        name
      }
    }
    locations {
      nodes {
        databaseId
        name
        uri
      }
    }
  }
`;

You can see that these fields look very similar to the ones we used in the test query we fired off in the GraphiQL IDE.

These fields are broken out into a fragment for code colocation; from the components/ProviderCard.js file, you can easily manage both the GraphQL fields being requested and how they’re used inside of the component in that same file.

Back in our pages/providers.js file, you can see that we’re taking the list of providers and rendering a ProvidersList component.

The ProvidersList component lives in components/ProvidersList.js and looks like this:

export default function ProvidersList({ providers }) {
  return (
    <ul className="doctors-list">
      {providers.map((provider) => (
        <li key={provider.databaseId}>
          <ProviderCard provider={provider} />
        </li>
      ))}
    </ul>
  );
}

You can see that it renders a ul, then maps over the providers and renders an li for each with a ProviderCard component inside.

The ProviderCard component lives in components/ProviderCard.js and looks like this:

export default function ProviderCard({ provider }) {
  const { uri, name, credentials, profilePhoto, specialties, locations } =
    provider;

  return (
    <article className="card">
      <Link href={uri}>
        <a>
          <h2>
            {name}, {credentials}
          </h2>
        </a>
      </Link>
      {profilePhoto ? (
        <Link href={uri}>
          <a>
            <img src={profilePhoto.sourceUrl} alt={profilePhoto.altText} />
          </a>
        </Link>
      ) : null}
      <h3>Specialty</h3>
      {specialties.nodes.map((specialty) => specialty.name).join(", ")}
      <h3>Locations</h3>
      <ul className="locations-list">
        {locations.nodes.map((location) => {
          return (
            <li key={location.databaseId}>
              🏥 <Link href={location.uri}>{location.name}</Link>
            </li>
          );
        })}
      </ul>
    </article>
  );
}

This component is responsible for rendering the individual cards that you see on the /providers page that list the details about each provider.

There are a few noteworthy things here:

  1. The person’s name and profile photo (if they have one) are both links that point to that single Provider page.
  2. We’re mapping over the Specialty taxonomy terms that this provider has and displaying them on a list. In your applications, you can even go a step further and create taxonomy archive pages using the WPGraphQL Tax Query extension.
  3. We’re making use of our Provider => Locations relationships. We map over the locations and for each, render a link that points to that single Location page.

Next, let’s explore the single Provider page (#1 on the list above).

Click on a provider’s name or photo to be sent to their single Provider page. It’ll look something like this:

Single Provider page

Open up pages/provider/[id].js in a code editor to see the code for this page.

Inside of getStaticProps(), we’re firing off the GET_PROVIDER query and passing through to it the provider’s id that’s in the URL. When that provider’s data comes back, it is passed through to the Provider component as a prop.

const GET_PROVIDER = gql`
  query getProvider($id: ID!) {
    provider(id: $id, idType: DATABASE_ID) {
      name
      credentials
      bio
      practicingSince
      profilePhoto {
        sourceUrl
        altText
      }
      specialties {
        nodes {
          name
        }
      }
      locations {
        nodes {
          databaseId
          name
          uri
        }
      }
    }
  }
`;

export async function getStaticProps(context) {
  const { id } = context.params;
  const response = await client.query({
    query: GET_PROVIDER,
    variables: { id },
  });

  const provider = response?.data?.provider;

  if (!provider) {
    return { notFound: true };
  }

  return {
    props: { provider },
    revalidate: 600,
  };
}

The GET_PROVIDER query should give you a good sense of what it looks like to get the data for one of your ACM model’s individual posts.

The Provider component that’s responsible for rendering the page looks like this:

export default function Provider({ provider }) {
  const {
    name,
    credentials,
    bio,
    practicingSince,
    profilePhoto,
    specialties,
    locations,
  } = provider;

  return (
    <Layout>
      <Link href="/providers">
        <a className="providers-link">&#8592; View Providers</a>
      </Link>
      <article className="provider">
        {profilePhoto ? (
          <img src={profilePhoto.sourceUrl} alt={profilePhoto.altText} />
        ) : null}
        <h1>
          {name}, {credentials}
        </h1>
        <p className="practicing-since">Practicing since {practicingSince}</p>
        <h2>Bio</h2>
        <div dangerouslySetInnerHTML={{ __html: bio }} />
        <h2>Specialty</h2>
        {specialties.nodes.map((specialty) => specialty.name).join(", ")}
        <h2>Locations</h2>
        <ul className="locations-list">
          {locations.nodes.map((location) => {
            return (
              <li key={location.databaseId}>
                🏥 <Link href={location.uri}>{location.name}</Link>
              </li>
            );
          })}
        </ul>
      </article>
    </Layout>
  );
}

Again, we see that we’re looping over the locations and rendering a link for each. Go ahead and click through to one of those single Location pages.

The page you land on will look something like this:

Single Location page

Now let’s open up pages/location/[id].js in a code editor to see how it works. You’ll notice a similar pattern to what we saw with the single Provider page.

Inside of the getStaticProps() function, we fire off a GET_LOCATION query and pass to it the location’s id that’s in the URL. When that Provider‘s data comes back, it is passed through to the Location component as a prop.

const GET_LOCATION = gql`
  query getLocation($id: ID!) {
    location(id: $id, idType: DATABASE_ID) {
      name
      description
      photo {
        sourceUrl
        altText
      }
    }
  }
`;

export async function getStaticProps(context) {
  const { id } = context.params;
  const response = await client.query({
    query: GET_LOCATION,
    variables: { id },
  });

  const location = response?.data?.location;

  if (!location) {
    return { notFound: true };
  }

  return {
    props: { location },
    revalidate: 600,
  };
}

Studying GET_LOCATION gives you yet another opportunity to see how we can query for one of your ACM model’s individual posts.

Side-Note on Bi-Directional Relationship Queries

On this page, it would also be cool to be able to query for all Providers who practice at this Location and provide links to each. That would involve querying the Provider => Location relationship in the opposite direction– starting with the Location and finding Providers related to it. At the time of this writing, ACM has not added the ability to do bi-directional relationship field queries like that, but the team is planning to add it in the near future.

The Location component that’s responsible for rendering the page’s content looks like this:

export default function Location({ location }) {
  const { photo, name, description } = location;

  return (
    <Layout>
      <Link href="/providers">
        <a className="providers-link">&#8592; View Providers</a>
      </Link>
      <article className="location">
        {photo ? <img src={photo.sourceUrl} alt={photo.altText} /> : null}
        <h1>{name}</h1>
        <div dangerouslySetInnerHTML={{ __html: description }} />
      </article>
    </Layout>
  );
}

Here, we render the Location‘s photo if it has one, the name of the location, and the description.

That concludes the Next.js app walkthrough.

Summary

At this point, we’ve learned how to:

  • Create ACM models
  • Create new posts for those models in the WordPress admin
  • Run test queries to get ACM model data using the GraphiQL IDE
  • Fire off GraphQL queries to fetch ACM model data from our decoupled Next.js app
  • Use the fetched ACM model data to render our React components

I hope all of that is helpful to you as you dive into working with Atlas Content Modeler! The team working on it has put a lot of work into producing a single, unified tool for creating content models, with support for taxonomies and relationships being recent additions. They’re not done, however! A number of other field types will be added in the near future, along with other features. Stay tuned!

How Can I Help?

I’m glad you asked! We would love for you to try out ACM on your own projects. You can provide us with feedback on it in the following ways:

  • Click ”Send Feedback” at the top of the Content Modeler page in your WordPress admin area to share your thoughts with us.
  • File bugs or feature requests in GitHub.

Thanks for your interest in the project! 🙌