Using a Vue.js app in a D8 Paragraph

I have had been given the task to pull data from a REST API into a Drupal 8 Paragraph, to be rendered on any specific page. The API would provide the data for a profile, showing in a listing, and a full bio. 

I wanted to keep this as simple as possible, and at the same time, make it cool. 

For this task, I decided to use Vue.js inside a template. I felt that this would give me a way to work with Vue in a way that is not as common - inside a Drupal 8 template. 

I created a Paragraph type called a Profile Listing. In that Paragraph, I added some fields that I would be using to control the API call. In this case, a related taxonomy, limit, and offset, and a few other fields.

The Template

Using a template I created for that Paragraph, I did some markup tricks to get twig to render correctly.

{# string wrapper is to prevent Twig processing the Vue vars #}
{{ '<div id="App">
     <div v-for="profile in profiles" class="card">
        <h2 class="name">{{ profile.firstName }} {{ profile.lastName }}</h3>
  </div>' }}

This allows the HTML to not be filtered in the template. This does make it so that ticks ` have to be used instead of single quotes inside that template.

Next I pulled the CDN versions of Vue into the template, as well as Axios, a data management plugin.

<script src=""></script>
<script src=""></script>

Then inside a following script tag, I write the app. Since the app is inside the Paragraph, all field values can be used in the markup, allowing you to filter based on the values. This is a very shortened and modified version, but you get the picture.

const myApp = new Vue({
  el: '#App',
  data () {
    return {
      profiles: [],
      loaded: false
  methods: {
    async getProfiles(id) {

      const = ``;
      let endpoint = `profiles`

Notice the following API call, which includes the field values to filter the result.

      try {
        const resp = await axios.get(url + endpoint + `?args[0]=
          {% if content.field_setting|field_value %}
            {{ content.field_setting|field_value }}
          {% else %}all{% endif %}
          &limit={{ content.field_limit|field_value }}
          &offset={{ content.field_offset|field_value }}`);

        this.profiles =;
        this.loaded = true;
      } catch (err) {
  mounted() {
    profileParams = 'all';

The result is pretty awesome. Now anytime that paragraph is used, the app loads the Vue app, with the REST data.


portrait of