diff --git a/lib/toaster/tests/views/test_views.py b/lib/toaster/tests/views/test_views.py
index e1adfcf86..bed572c76 100644
--- a/lib/toaster/tests/views/test_views.py
+++ b/lib/toaster/tests/views/test_views.py
@@ -10,14 +10,16 @@
 """Test cases for Toaster GUI and ReST."""
 
 import os
+import zlib
 import pytest
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.urls import reverse
 from django.db.models import Q
 
-from orm.models import Project, Package
+from orm.models import Build, Project, Package
 from orm.models import Layer_Version, Recipe
+from orm.models import RecipeVariable
 from orm.models import CustomImageRecipe
 from orm.models import CustomImagePackage
 
@@ -61,6 +63,14 @@ class ViewTests(TestCase):
         if BuildEnvironment.objects.count() == 0:
             BuildEnvironment.objects.create(betype=BuildEnvironment.TYPE_LOCAL)
 
+    @staticmethod
+    def _recipe_variables(build, recipe, values):
+        return RecipeVariable(
+            build=build, recipe=recipe,
+            variable_count=len(values),
+            variables=zlib.compress(json.dumps(
+                values, separators=(',', ':')).encode('utf-8')))
+
 
     def test_get_base_call_returns_html(self):
         """Basic test for all-projects view"""
@@ -90,6 +100,149 @@ class ViewTests(TestCase):
         self.assertTrue(name_found,
                         "project name not found in projects table")
 
+    def test_recipe_variables_tab_scopes_searches_and_escapes(self):
+        build = Build.objects.get(pk=1)
+        other_build = Build.objects.get(pk=2)
+        other_recipe = Recipe.objects.exclude(pk=self.recipe1.pk).first()
+        RecipeVariable.objects.bulk_create([
+            self._recipe_variables(build, self.recipe1, {
+                'SRC_URI': 'git://example.invalid/src',
+                'SPECIAL': '<script>alert(1)</script>',
+            }),
+            self._recipe_variables(other_build, self.recipe1, {
+                'OTHER_BUILD': 'hidden',
+            }),
+            self._recipe_variables(build, other_recipe, {
+                'OTHER_RECIPE': 'hidden',
+            }),
+        ])
+        url = reverse('recipe', args=(build.pk, self.recipe1.pk, '5'))
+
+        response = self.client.get(url, {
+            'count': 100,
+            'page': 1,
+            'orderby': 'variable_name:+',
+            'search': 'SRC_URI',
+        })
+
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'recipe.html')
+        self.assertContains(response, 'SRC_URI')
+        self.assertContains(response, 'git://example.invalid/src')
+        self.assertNotContains(response, 'OTHER_BUILD')
+        self.assertNotContains(response, 'OTHER_RECIPE')
+
+        response = self.client.get(url, {
+            'count': 100,
+            'page': 1,
+            'orderby': 'variable_name:+',
+        })
+        self.assertContains(response, 'Variables (2)')
+        self.assertContains(response, '&lt;script&gt;alert(1)&lt;/script&gt;')
+        self.assertNotContains(response, '<script>alert(1)</script>')
+
+    def test_recipe_variables_tab_redirects_paginates_and_handles_empty(self):
+        build = Build.objects.get(pk=1)
+        url = reverse('recipe', args=(build.pk, self.recipe1.pk, '5'))
+
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 302)
+        self.assertIn('count=100', response.url)
+        self.assertIn('orderby=variable_name%3A%2B', response.url)
+
+        RecipeVariable.objects.bulk_create([
+            self._recipe_variables(build, self.recipe1, {
+                'VAR_%02d' % index: str(index) for index in range(12)
+            })
+        ])
+        response = self.client.get(url, {
+            'count': 10,
+            'page': 2,
+            'orderby': 'variable_name:+',
+        })
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['objects'].paginator.count, 12)
+        self.assertEqual(response.context['objects'].number, 2)
+        self.assertContains(response, 'VAR_10')
+        self.assertContains(response, 'VAR_11')
+        self.assertNotContains(response, 'VAR_00')
+
+        RecipeVariable.objects.filter(build=build, recipe=self.recipe1).delete()
+        response = self.client.get(url, {
+            'count': 100,
+            'page': 1,
+            'orderby': 'variable_name:+',
+        })
+        self.assertContains(response, 'No resolved recipe datastore variables are available.')
+
+    def test_recipe_variables_tab_bounds_query_parameters(self):
+        build = Build.objects.get(pk=1)
+        RecipeVariable.objects.bulk_create([
+            self._recipe_variables(build, self.recipe1, {'SRC_URI': 'value'})
+        ])
+        url = reverse('recipe', args=(build.pk, self.recipe1.pk, '5'))
+
+        response = self.client.get(url, {
+            'count': 'alert(document.domain)',
+            'page': 1,
+            'orderby': 'variable_value:+',
+            'filter': 'variable_value__regex:(a+)+$',
+        })
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['objects'].paginator.per_page, 100)
+        self.assertNotContains(response, 'alert(document.domain)')
+        self.assertEqual(
+            [variable['variable_name']
+             for variable in response.context['objects'].object_list],
+            ['SRC_URI'])
+
+        response = self.client.get(url, {
+            'count': 100,
+            'page': 1,
+            'orderby': 'variable_name:+',
+            'search': ('SRC_URI ' * 20) + ('x' * 300),
+        })
+        self.assertLessEqual(len(response.context['search_term']), 256)
+        self.assertLessEqual(len(response.context['search_term'].split()), 16)
+
+    def test_built_recipes_table_shows_variable_count(self):
+        build = Build.objects.get(pk=1)
+        self._recipe_variables(build, self.recipe1, {
+            'FOO': 'one',
+            'BAR': 'two',
+        }).save()
+        url = reverse('recipes', args=(build.pk,))
+
+        response = self.client.get(url, {
+            'format': 'json',
+            'limit': 25,
+            'page': 1,
+        })
+        data = json.loads(response.content)
+        row = next(row for row in data['rows']
+                   if self.recipe1.name in row['name'])
+
+        self.assertIn('Variables', [column['title'] for column in data['columns']])
+        self.assertIn('>2</a>', row['variable_count'])
+
+    def test_recipe_packages_tab_shows_variable_count(self):
+        build = Build.objects.get(pk=1)
+        self._recipe_variables(build, self.recipe1, {
+            'FOO': 'one',
+            'BAR': 'two',
+        }).save()
+
+        response = self.client.get(reverse(
+            'recipe_packages', args=(build.pk, self.recipe1.pk)), {
+                'count': 10,
+                'page': 1,
+                'orderby': 'name:+',
+            })
+
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'Variables (2)')
+
     def test_typeaheads(self):
         """Test typeahead ReST API"""
         layers_url = reverse('xhr_layerstypeahead', args=(self.project.id,))
diff --git a/lib/toaster/toastergui/buildtables.py b/lib/toaster/toastergui/buildtables.py
index 327059d00..52c3dd779 100644
--- a/lib/toaster/toastergui/buildtables.py
+++ b/lib/toaster/toastergui/buildtables.py
@@ -239,7 +239,10 @@ class BuiltRecipesTable(BuildTablesMixin):
     def setup_queryset(self, *args, **kwargs):
         build = Build.objects.get(pk=kwargs['build_id'])
         self.static_context_extra['build'] = build
-        self.queryset = build.get_recipes()
+        self.queryset = build.get_recipes().annotate(
+            variable_count=Sum(
+                'recipevariable__variable_count',
+                filter=Q(recipevariable__build=build), default=0))
         self.queryset = self.queryset.order_by(self.default_orderby)
 
     def setup_columns(self, *args, **kwargs):
@@ -323,6 +326,12 @@ class BuiltRecipesTable(BuildTablesMixin):
                         hideable=False,
                         field_name="version")
 
+        self.add_column(
+            title="Variables",
+            field_name="variable_count",
+            static_data_name="variable_count",
+            static_data_template='<a href="{% url "recipe" extra.build.pk data.pk "5" %}">{{data.variable_count|default:0}}</a>')
+
         self.add_column(title="Dependencies",
                         static_data_name="dependencies",
                         static_data_template=depends_on_tmpl)
diff --git a/lib/toaster/toastergui/templates/recipe.html b/lib/toaster/toastergui/templates/recipe.html
index 4b5301b54..be99ebc54 100644
--- a/lib/toaster/toastergui/templates/recipe.html
+++ b/lib/toaster/toastergui/templates/recipe.html
@@ -52,6 +52,13 @@
                 Reverse build dependencies ({{object.r_dependencies_depends.all.count}})
             </a>
         </li>
+        <li class="{{tab_states.5}}">
+            <a href="{% url "recipe" build.pk object.id "5" %}">
+                <span class="glyphicon glyphicon-question-sign get-help" title="Final values
+                    from BitBake's resolved recipe datastore"></span>
+                Variables ({{recipe_variable_count}})
+            </a>
+        </li>
     </ul>
     <div class="tab-content">
         <div class="tab-pane {{tab_states.1}}" id="information">
@@ -277,6 +284,31 @@
             {% endif %}
 
         </div>
+        <div class="tab-pane {{tab_states.5}}" id="variables">
+            {% if not objects and not request.GET.search %}
+            <div class="alert alert-info">
+                No resolved recipe datastore variables are available.
+            </div>
+            {% else %}
+                {% with "variables" as search_what %}
+                {% include "detail_search_header.html" %}
+                {% endwith %}
+                {% if objects %}
+                <table class="table table-bordered table-hover tablesorter" id="otable">
+                    {% include "detail_sorted_header.html" %}
+                    <tbody>
+                    {% for variable in objects %}
+                        <tr>
+                            <td>{{variable.variable_name}}</td>
+                            <td>{{variable.variable_value}}</td>
+                        </tr>
+                    {% endfor %}
+                    </tbody>
+                </table>
+                {% include "detail_pagination_bottom.html" %}
+                {% endif %}
+            {% endif %}
+        </div>
     </div>
 </div>
 
diff --git a/lib/toaster/toastergui/templates/recipe_packages.html b/lib/toaster/toastergui/templates/recipe_packages.html
index 37a586f38..abd64ca7e 100644
--- a/lib/toaster/toastergui/templates/recipe_packages.html
+++ b/lib/toaster/toastergui/templates/recipe_packages.html
@@ -51,6 +51,13 @@
                 Reverse build dependencies ({{recipe.r_dependencies_depends.all.count}})
             </a>
         </li>
+        <li>
+            <a href="{% url "recipe" build.pk recipe.id "5" %}">
+                <span class="glyphicon glyphicon-question-sign get-help" title="Final values
+                    from BitBake's resolved recipe datastore"></span>
+                Variables ({{variable_count}})
+            </a>
+        </li>
     </ul>
     <div class="tab-content">
 {#        <div class="tab-pane active" id="packages-built" name="packages-built">#}
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 061e6436c..26cf5ca26 100644
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -10,6 +10,7 @@ import ast
 import re
 import subprocess
 import sys
+import zlib
 
 import bb.cooker
 from bb.ui import toasterui
@@ -20,6 +21,7 @@ from django.db import IntegrityError
 from django.shortcuts import render, redirect, get_object_or_404, HttpResponseRedirect
 from django.utils.http import urlencode
 from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
+from orm.models import RecipeVariable
 from orm.models import LogMessage, Variable, Package_Dependency, Package
 from orm.models import Task_Dependency, Package_File
 from orm.models import Target_Installed_Package, Target_File
@@ -619,10 +621,13 @@ def recipe(request, build_id, recipe_id, active_tab="1"):
     layer  = Layer.objects.get(pk=layer_version.layer_id)
     tasks_list  = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
     package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
+    recipe_variable_count = RecipeVariable.objects.filter(
+        build_id=build_id, recipe_id=recipe_id).values_list(
+            'variable_count', flat=True).first() or 0
 
-    if active_tab != '1' and active_tab != '3' and active_tab != '4' :
+    if active_tab not in ('1', '3', '4', '5'):
         active_tab = '1'
-    tab_states = {'1': '', '3': '', '4': ''}
+    tab_states = {'1': '', '3': '', '4': '', '5': ''}
     tab_states[active_tab] = 'active'
 
     context = {
@@ -632,8 +637,63 @@ def recipe(request, build_id, recipe_id, active_tab="1"):
             'layer'   : layer,
             'tasks'   : tasks_list,
             'package_count' : package_count,
+            'recipe_variable_count' : recipe_variable_count,
             'tab_states' : tab_states,
     }
+
+    if active_tab == '5':
+        (requested_pagesize, requested_orderby) = _get_parameters_values(
+            request, 100, 'variable_name:+')
+        pagesize = requested_pagesize \
+            if str(requested_pagesize) in ('10', '25', '50', '100', '150') \
+            else 100
+        orderby = requested_orderby \
+            if requested_orderby in ('variable_name:+', 'variable_name:-') \
+            else 'variable_name:+'
+        mandatory_parameters = {
+            'count': pagesize,
+            'page': 1,
+            'orderby': orderby,
+        }
+        if _verify_parameters(request.GET, mandatory_parameters):
+            return _redirect_parameters(
+                'recipe', request.GET, mandatory_parameters,
+                build_id=build_id, recipe_id=recipe_id, active_tab='5')
+
+        search_term = request.GET.get('search', '')[:256]
+        search_term = ' '.join(search_term.split()[:16])
+        snapshot = RecipeVariable.objects.filter(
+            build_id=build_id, recipe_id=recipe_id).first()
+        variables = []
+        if snapshot:
+            values = json.loads(zlib.decompress(snapshot.variables).decode('utf-8'))
+            variables = [
+                {'variable_name': name, 'variable_value': value}
+                for name, value in values.items()
+                if not search_term or search_term.lower() in name.lower()
+                or search_term.lower() in value.lower()
+            ]
+        variables.sort(
+            key=lambda variable: variable['variable_name'],
+            reverse=orderby.endswith(':-'))
+        context['variable_count'] = len(variables)
+        context['objects'] = _build_page_range(
+            Paginator(variables, pagesize), request.GET.get('page', 1))
+        context['object_count'] = context['variable_count']
+        context['validated_pagesize'] = pagesize
+        context['search_term'] = search_term
+        context['search_orderby'] = 'variable_name:+'
+        context['tablecols'] = [
+            {
+                'name': 'Variable',
+                'orderfield': _get_toggle_order(request, 'variable_name'),
+                'ordericon': _get_toggle_order_icon(request, 'variable_name'),
+                'orderkey': 'variable_name',
+            },
+            {'name': 'Value'},
+        ]
+        _set_parameters_values(pagesize, orderby, request)
+
     return toaster_render(request, template, context)
 
 def recipe_packages(request, build_id, recipe_id):
@@ -651,6 +711,9 @@ def recipe_packages(request, build_id, recipe_id):
     recipe_object = Recipe.objects.get(pk=recipe_id)
     queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
     package_count = queryset.count()
+    variable_count = RecipeVariable.objects.filter(
+        build_id=build_id, recipe_id=recipe_id).values_list(
+            'variable_count', flat=True).first() or 0
     queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
 
     packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
@@ -660,6 +723,7 @@ def recipe_packages(request, build_id, recipe_id):
             'recipe'  : recipe_object,
             'objects'  : packages,
             'object_count' : package_count,
+            'variable_count' : variable_count,
             'tablecols':[
                 {
                     'name':'Package',
