How to create in Drupal 8 a Google Rich Snippet Resume using Structured data with Schema.org

data analytics statistics

It is not very complicated to understand it, if you follow the simple rules then you will be able to understand my 3 simple steps on how I did implement in Drupal 8 my Resume Profile Page. Source code can be downloaded for free from my Github repo, to do so please refers to my Drupal 8 Installation Guide.

Go to /admin/config/people/accounts/fields and add all the fields that you want to appears inside the Snippets such as:

LABEL MACHINE NAME FIELD TYPE
Body field_body Text (formatted, long)
First name field_first_name Text (plain)
Job Title field_job_title Text (plain)
Last name field_last_name Text (plain)
Meta Tags field_meta_tags Text (plain)
Picture user_picture Image
Right Body field_right_body Text (formatted, long)
Telephone field_telephone Text (plain)
Work For field_work_for Text (plain)
Web site field_web_site Link

When we display the User page, we want to generate this HTML:

<article typeof="schema:Person" about="/company" class="user-full-view clearfix">
   <section class="col-sm-7">
      <div class="region region-content">
      <h1 class="page-header">Senior Drupal 8 Developer</h1>
      <div class="block-3 clearfix">
         <figure class="img-polaroid"><img src="/sites/default/files/pictures/pasquale-laudonio.jpg" width="256" height="207" alt="Senior Drupal 8 Developer" title="Freelancer Senior Drupal 8 Developer in London" property="schema:image" class="img-responsive"></figure>
         <div class="block-3 personal-details clearfix">
            <div class="col-sm-12 col-md-6">
               <div class="field field--label-inline">
                  <div class="field--label">First name</div>
                  <div property="schema:givenName" class="field--item">Pasquale</div>
               </div>
               <div class="field field--label-inline">
                  <div class="field--label">Last name</div>
                  <div property="schema:familyName" class="field--item">Laudonio</div>
               </div>
               <div class="field field--label-inline">
                  <div class="field--label">Job Title</div>
                  <div property="schema:jobTitle" class="field--item">Senior Drupal 8 Developer</div>
               </div>
            </div>
            <div class="col-sm-12 col-md-6">
               <div class="field field--label-inline">
                  <div class="field--label">Telephone</div>
                  <div property="schema:telephone" class="field--item">+44 (0)7414982245</div>
               </div>
               <div class="field field--label-inline">
                  <div class="field--label">Work for</div>
                  <div property="schema:worksFor">
                     <span property="schema:name" typeof="schema:Organization" about="http://maria-consulting.co.uk/">Maria Consulting LTD</span>
                  </div>
               </div>
               <div class="field field--label-inline">
                  <div class="field--label">Web site</div>
                  <div class="field--item">
                     <a href="http://maria-consulting.co.uk/">http://maria-consulting.co.uk/</a>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </section>
   <aside class="col-sm-5" role="complementary">
      <h2 class="block-title">Pasquale Laudonio</h2>
      <div class="block-5">
         <p class="lead" property="schema:description">Reliable, organised, hard working, highly skilled, enthusiastic and self-motivated; good communication skills; system constantly improved; eager to learn and keep up to date with new technologies.</p>
         <p property="schema:knowsAbout">20 years experience in IT industry as Senior Full Stack Developer with great expertise in Drupal 7 and 8, Custom Modules, Linux, Apache, MySQL, PHP, REST APIs, VueJS, Angular, jquery, Ajax and HTML/CSS.</p>
         <p property="schema:alumniOf" typeof="schema:OrganizationRole"><span property="schema:alumniOf" typeof="schema:CollegeOrUniversity"> Computer Science Degree at <span property="schema:name">University of Sannio</span> <link property="schema:sameAs" href="https://en.wikipedia.org/wiki/University_of_Sannio">
</span> from <span property="schema:startDate">1995</span> to <span property="schema:startDate">2001</span></p>
      </div>
      <h2 class="block-title">Most recent roles</h2>
      <div class="organization-role" property="schema:alumniOf" typeof="schema:OrganizationRole">
         <span property="schema:alumniOf" typeof="schema:Organization">
         <span class="company-name" property="schema:name">Atelier7</span>
         <span property="schema:address" typeof="schema:PostalAddress">
         <span property="schema:streetAddress"> 19 Hatfields</span>
         <span property="schema:addressLocality"> London</span>
         <span property="schema:postalCode"> SE1 8DJ</span>
         </span>
         </span>
         <span class="job-period">
         From <span property="schema:startDate">2020-01-07</span>
         to <span property="schema:endDate">2020-04-07</span>
         </span>
         <span property="schema:roleName" class="hidden">Senior Drupal 8 Developer</span>
         <span property="schema:sameAs" class="hidden">/drupal-8-developer-april-20-atelier7</span>
      </div>
   </aside>
</article>

 

Please do not worry too much about the complexity of the HTML because Drupal adds some extra DIVs inside the Output, in general to create a Resume Snippet in any language and in any CMS rather than Drupal you must simply focus on the most important HTML elements. For example try to understand Basic elements such as:

<article typeof="schema:Person" about="/company">
   <section class="col-sm-7">
      <h1 class="page-header">Senior Drupal 8 Developer</h1>
      <div class="block-3 clearfix">
         <figure class="img-polaroid"><img src="/sites/default/files/pictures/pasquale-laudonio.jpg" width="256" height="207" alt="Senior Drupal 8 Developer" title="Freelancer Senior Drupal 8 Developer in London" property="schema:image" class="img-responsive"></figure>
         <div class="block-3 personal-details clearfix">
            <div class="col-sm-12 col-md-6">
            First name: <span property="schema:givenName">Pasquale</span>
            Last name: <span property="schema:familyName">Laudonio</span>
            Job Title: <span property="schema:jobTitle">Senior Drupal 8 Developer</span>
            Telephone: <span property="schema:telephone">+44 (0)7414982245</span>
            Work for: <span property="schema:worksFor">Maria Consulting LTD</span>
            Web site: <a href="http://maria-consulting.co.uk/">http://maria-consulting.co.uk/</a>
         </div>
      </div>
   </section>
</article>

The concept is very simple, first you have to define the Type of Person as main container. Then inside this container you can add all the Properties which are allowed for this Particular Type: Person.

In my case I added my Photo, Given Name, Family Name, Job title, Telephone, Work For. As you can see all properties as defined as: property="schema:image", do not forget the add "schema:" otherwise the property will no be recognised.

For the interest of Drupal Themers this is how it looks like the code inside my custom Twig: user--full.html.twig, please note there are different way in Drupal 8 to add a Schema property to a field, in my case I preferred to use the "Twig Field Value" contributed module to extra label and value using the field_label and field_value provided from this module which allows Drupal Themers to print field labels and field values individually.

For people who are not used to created custom Twig Templates, you can also use the rdf_ui module which allows to associate a schema property to a field by creating a Mapping into the Entity Definition, but obviously if you do not implement your own twig then you got the standard output printed by Drupal which has got some limitations in what you can print unless you use extra Tools such as Display Suite or Panels. For me the best way I assume would be to use both modules (twig_field_value and drf_ui) plus implementing a custom template such as mine for example:

{% if content.field_body %}
    {%- for key, field_body_item in content.field_body['#items'] -%}
        {%- if key == 0 -%}
            <div class="block-3 clearfix">
                <figure class="img-polaroid">
                    {{- content.user_picture -}}
                </figure>
                <div class="field--item">
                    {{- content.field_body[key] -}}
                </div>
            </div>
            <div class="block-3 personal-details clearfix">
                <div class="col-sm-12 col-md-6">
                    <div class="field field--label-inline">
                        <div class="field--label">{{- content.field_first_name|field_label -}}</div>
                        <div property="schema:givenName"
                             class="field--item">{{- content.field_first_name|field_value -}}</div>
                    </div>
                    <div class="field field--label-inline">
                        <div class="field--label">{{- content.field_last_name|field_label -}}</div>
                        <div property="schema:familyName"
                             class="field--item">{{- content.field_last_name|field_value -}}</div>
                    </div>
                    <div class="field field--label-inline">
                        <div class="field--label">{{- content.field_job_title|field_label -}}</div>
                        <div property="schema:jobTitle"
                             class="field--item">{{- content.field_job_title|field_value -}}</div>
                    </div>
                </div>
                <div class="col-sm-12 col-md-6">
                    <div class="field field--label-inline">
                        <div class="field--label">{{- content.field_telephone|field_label -}}</div>
                        <div property="schema:telephone"
                             class="field--item">{{- content.field_telephone|field_value -}}</div>
                    </div>
                    <div class="field field--label-inline">
                        <div class="field--label">Work for</div>
                        <div property="schema:worksFor">
                            <span property="schema:name" typeof="schema:Organization"
                                  about="{{- content.field_web_site[0]['#url'] -}}">{{- content.field_web_site[0]['#title'] -}}</span>
                        </div>
                        <div about="{{- content.field_web_site[0]['#url'] -}}">
                            <span property="schema:address" class="field PostalAddress"
                                  typeof="schema:PostalAddress">
                                <span property="schema:streetAddress">1 Chelsfield House</span>,
                                <span property="schema:addressLocality">London UK</span>
                                <span property="schema:postalCode">SE17 1SX</span>
                            </span>
                        </div>
                    </div>

                    <div class="field" property="schema:alumniOf" typeof="schema:OrganizationRole">
                        <span property="schema:alumniOf" typeof="schema:CollegeOrUniversity">
                            Computer Science Degree at <span property="schema:name">University of Sannio</span>
                            <link property="schema:sameAs" href="https://en.wikipedia.org/wiki/University_of_Sannio">
                        </span> from <span property="schema:startDate">1995</span> to <span property="schema:startDate">2001</span>
                    </div>

                    <div class="field field--label-inline">
                        <div class="field--label">{{- content.field_web_site|field_label -}}</div>
                        <div class="field--item">
                            <a href="{{- content.field_web_site[0]['#url'] -}}">{{- content.field_web_site[0]['#url'] -}}</a>
                        </div>
                    </div>
                </div>
            </div>
        {%- else -%}
            <div class="field--item">
                {{- content.field_body[key] -}}
            </div>
        {%- endif -%}
    {%- endfor -%}
{% endif %}

As you can see, the most interesting things inside this Twig are:

  1. I created a relationship between the Company Type ("Work for") and the Company Postal Address Type:
    The property address does not belong to the Person but to the Company, to do that I did use about="<company_utl>", and you can see the Company has got the typeOf="schema:Organization". This means the HTML element with property workfor inside contains another element which is a typeOf rather than a simple atomic value.
  2. I created also a relationship between the Person (me) and the University where I did study using the property="schema:alumniOf". This property can contains inside a  typeof="schema:OrganizationRole" which can be an property="schema:alumniOf" typeof="schema:CollegeOrUniversity". Which means in few words: I was student (role) in University of Sannio during that Role period (property="schema:startDate" and property="schema:endtDate").

By Using the Google Structured Data Testing Tool, we can see Google Recognised successfully all the elements inside my Resume:

@type
Person
@id
name
Pasquale Laudonio
image
givenName
Pasquale
familyName
Laudonio
jobTitle
Senior Drupal 8 Developer
telephone
+44 (0)7414982245
description
Reliable, organised, hard working, highly skilled, enthusiastic and self-motivated; good communication skills; system constantly improved; eager to learn and keep up to date with new technologies.
knowsAbout
20 years experience in IT industry as Senior Full Stack Developer with great expertise in Drupal 7 and 8, Custom Modules, Linux, Apache, MySQL, PHP, REST APIs, VueJS, Angular, jquery, Ajax and HTML/CSS.
worksFor
 
@type
Organization
name
Maria Consulting LTD
alumniOf
 
@type
OrganizationRole
startDate
1995
startDate
2001
alumniOf
 
@type
CollegeOrUniversity
name
University of Sannio
sameAs
alumniOf
 
@type
OrganizationRole
startDate
2020-01-07
endDate
2020-04-07
roleName
Senior Drupal 8 Developer
sameAs
maria-consulting.co.uk/drupal-8-developer-april-20-atelier7
alumniOf
 
@type
Organization
name
Atelier7
address
 
@type
PostalAddress
streetAddress
19 Hatfields
addressLocality
London
postalCode
SE1 8DJ

Understanding how to create Relationship between a Person and OrganizationRole / Organization is essential to create a complete Resume Snippet. The most important part of the CV, usually are the work Experiences and the Education displayed in a chronological order showing date periods.

It is very rewarding to see that when you put all your meta data in the correct way, then Google can recognise all your Roles in all your different Organisations, Addresses and also all the Date Periods. To see how I did implement these complex Relationships please let's see more details in the section 3.

I added a new Entity called Work Experience with the following fields:

LABEL MACHINE NAME FIELD TYPE
Body body Text (formatted, long, with summary)
Company details field_company_details Text (formatted)
Job title field_job_title Text (plain)
Period field_period Date range
Project Description field_project_description Text (formatted, long)
Responsibility field_responsibility Text (formatted, long)
Technologies field_technologies Text (formatted, long

I added all my Work Experience into the Drupal CMS and then I created a custom View to show all of them. In order to create the relationship I created this Custom Twig Template:

{% if title %}
  <h3>{{ title }}</h3>
{% endif %}
{% for row in rows %}
  {%
    set row_classes = [
      default_row_class ? 'views-row',
    ]
  %}
  <div{{ row.attributes.addClass(row_classes) }}>
    {% if row.full_name %}
    <div class="organization-role" property="schema:alumniOf" typeof="schema:OrganizationRole">
      <span property="schema:alumniOf" typeof="schema:Organization">
          <span class="company-name" property="schema:name">{{ row.company_details.company }}</span>
          <span property="schema:address" typeof="schema:PostalAddress">
          <span property="schema:streetAddress">{{ row.company_details.address }}</span>
          <span property="schema:addressLocality">{{ row.company_details.city }}</span>
          <span property="schema:postalCode">{{ row.company_details.post_code }}</span>
          </span>
      </span>
      <span class="job-period">
      From <span property="schema:startDate">{{ row.company_details.start }}</span>
      to <span property="schema:endDate">{{ row.company_details.end }}</span>
      </span>
      <span property="schema:roleName" class="hidden">{{ row.company_details.job_title }}</span>
      <span property="schema:sameAs" class="hidden">{{ row.company_details.company_url }}</span>
    </div>
    {% endif %}
    {{- row.content -}}
  </div>
{% endfor %}

As I said in the previous section, when I implemented my web site I was a big fan of custom templates because it gives the possibility to output whatever you like and I avoided the more complex but more "configurable" approach of using Panel and Display Suite because as Drupal Themer and super administrator I do not have any limitation on what can be the HTML output of my web pages.

Obviously if I was building this site for someone less technical than me then I had to spend more time and create something more generic and easier to maintain. I believe this code is a fantastic starting point if you wish to extend it and make a custom mode more re-usable and generic to create a Resume snippet.

Here, Im creating hard coded relationships between Person and OrganizationRole using property="schema:alumniOf" then inside the Role you have the Organization, and the Organization has got inside a type of PostalAddress. Role has got inside also other properties for startDate, endDate, RoleName. SameAs means that this is a preview to a Page where I put all the Details about this role.

1. SEO

seo search engine optimisation

Drupal 8 has got extremely SEO Friendly functionalities such as url alias, metaTags, entities, nodes, taxonomies, views, redirects, google analytics.

2. Analysis

data analysis for business and finance

Collect visitor statistics for your web site with Google Analytics. We offer free consultation for installing GA and Metatags modules on your Drupal web site.

3. Keywords

seo search engine optimization

Read our article on how to use and configure Drupal to use Simple XML sitemap, Meta Tags, Keywords, ALT and titles on images.

4. Drupal

develop coding web design coding web template

Create your own Theme from hundreds of existing themes and then customise it. This is a fantastic feature in Drupal to implement any kind..