diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
index 0ab85a16cf..70760624c6 100644
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -249,6 +249,7 @@ TEMPLATES = [
             os.path.join(BASE_DIR, 'templates'),
             # Allow templates in the reporting directory to be accessed
             os.path.join(MEDIA_ROOT, 'report'),
+            os.path.join(MEDIA_ROOT, 'label'),
         ],
         'APP_DIRS': True,
         'OPTIONS': {
diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py
index b2bfe9164f..899b00b0ec 100644
--- a/InvenTree/label/api.py
+++ b/InvenTree/label/api.py
@@ -6,6 +6,7 @@ import sys
 from django.utils.translation import ugettext as _
 from django.conf.urls import url, include
 from django.core.exceptions import ValidationError, FieldError
+from django.http import HttpResponse
 
 from django_filters.rest_framework import DjangoFilterBackend
 
@@ -13,6 +14,7 @@ from rest_framework import generics, filters
 from rest_framework.response import Response
 
 import InvenTree.helpers
+import common.models
 
 from stock.models import StockItem, StockLocation
 
@@ -40,6 +42,74 @@ class LabelListView(generics.ListAPIView):
     ]
 
 
+class LabelPrintMixin:
+    """
+    Mixin for printing labels
+    """
+
+    def print(self, request, items_to_print):
+        """
+        Print this label template against a number of pre-validated items
+        """
+
+        if len(items_to_print) == 0:
+            # No valid items provided, return an error message
+            data = {
+                'error': _('No valid objects provided to template'),
+            }
+
+            return Response(data, status=400)
+
+        outputs = []
+
+        # In debug mode, generate single HTML output, rather than PDF
+        debug_mode = common.models.InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
+
+        # Merge one or more PDF files into a single download
+        for item in items_to_print:
+            label = self.get_object()
+            label.object_to_print = item
+
+            if debug_mode:
+                outputs.append(label.render_as_string(request))
+            else:
+                outputs.append(label.render(request))
+
+        if debug_mode:
+            """
+            Contatenate all rendered templates into a single HTML string,
+            and return the string as a HTML response.
+            """
+
+            html = "\n".join(outputs)
+
+            return HttpResponse(html)
+        else:
+            """
+            Concatenate all rendered pages into a single PDF object,
+            and return the resulting document!
+            """
+
+            pages = []
+
+            if len(outputs) > 1:
+                # If more than one output is generated, merge them into a single file
+                for output in outputs:
+                    doc = output.get_document()
+                    for page in doc.pages:
+                        pages.append(page)
+
+                pdf = outputs[0].get_document().copy(pages).write_pdf()
+            else:
+                pdf = outputs[0].get_document().write_pdf()
+
+            return InvenTree.helpers.DownloadFile(
+                pdf,
+                'inventree_label.pdf',
+                content_type='application/pdf'
+            )
+
+
 class StockItemLabelMixin:
     """
     Mixin for extracting stock items from query params
@@ -158,7 +228,7 @@ class StockItemLabelDetail(generics.RetrieveUpdateDestroyAPIView):
     serializer_class = StockItemLabelSerializer
 
 
-class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin):
+class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin, LabelPrintMixin):
     """
     API endpoint for printing a StockItemLabel object
     """
@@ -173,34 +243,7 @@ class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin):
 
         items = self.get_items()
 
-        if len(items) == 0:
-            # No valid items provided, return an error message
-            data = {
-                'error': _('Must provide valid StockItem(s)'),
-            }
-
-            return Response(data, status=400)
-
-        label = self.get_object()
-
-        try:
-            pdf = label.render(items)
-        except:
-
-            e = sys.exc_info()[1]
-            
-            data = {
-                'error': _('Error during label rendering'),
-                'message': str(e),
-            }
-
-            return Response(data, status=400)
-
-        return InvenTree.helpers.DownloadFile(
-            pdf.getbuffer(),
-            'stock_item_label.pdf',
-            content_type='application/pdf'
-        )
+        return self.print(request, items)
 
 
 class StockLocationLabelMixin:
@@ -320,7 +363,7 @@ class StockLocationLabelDetail(generics.RetrieveUpdateDestroyAPIView):
     serializer_class = StockLocationLabelSerializer
 
 
-class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin):
+class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin, LabelPrintMixin):
     """
     API endpoint for printing a StockLocationLabel object
     """
@@ -332,35 +375,7 @@ class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin)
 
         locations = self.get_locations()
 
-        if len(locations) == 0:
-            # No valid locations provided - return an error message
-
-            return Response(
-                {
-                    'error': _('Must provide valid StockLocation(s)'),
-                },
-                status=400,
-            )
-
-        label = self.get_object()
-
-        try:
-            pdf = label.render(locations)
-        except:
-            e = sys.exc_info()[1]
-
-            data = {
-                'error': _('Error during label rendering'),
-                'message': str(e),
-            }
-
-            return Response(data, status=400)
-
-        return InvenTree.helpers.DownloadFile(
-            pdf.getbuffer(),
-            'stock_location_label.pdf',
-            content_type='application/pdf'
-        )
+        return self.print(request, locations)
 
 
 label_api_urls = [
diff --git a/InvenTree/label/migrations/0006_auto_20210222_0952.py b/InvenTree/label/migrations/0006_auto_20210222_1535.py
similarity index 67%
rename from InvenTree/label/migrations/0006_auto_20210222_0952.py
rename to InvenTree/label/migrations/0006_auto_20210222_1535.py
index 62e9d1a7f5..ea3441b64f 100644
--- a/InvenTree/label/migrations/0006_auto_20210222_0952.py
+++ b/InvenTree/label/migrations/0006_auto_20210222_1535.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.7 on 2021-02-21 22:52
+# Generated by Django 3.0.7 on 2021-02-22 04:35
 
 import django.core.validators
 from django.db import migrations, models
@@ -13,22 +13,22 @@ class Migration(migrations.Migration):
     operations = [
         migrations.AddField(
             model_name='stockitemlabel',
-            name='length',
-            field=models.FloatField(default=20, help_text='Label length, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Length [mm]'),
+            name='height',
+            field=models.FloatField(default=20, help_text='Label height, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Height [mm]'),
         ),
         migrations.AddField(
             model_name='stockitemlabel',
             name='width',
-            field=models.FloatField(default=10, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]'),
+            field=models.FloatField(default=50, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]'),
         ),
         migrations.AddField(
             model_name='stocklocationlabel',
-            name='length',
-            field=models.FloatField(default=20, help_text='Label length, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Length [mm]'),
+            name='height',
+            field=models.FloatField(default=20, help_text='Label height, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Height [mm]'),
         ),
         migrations.AddField(
             model_name='stocklocationlabel',
             name='width',
-            field=models.FloatField(default=10, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]'),
+            field=models.FloatField(default=50, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]'),
         ),
     ]
diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py
index ae00bc0dc1..d0962655d5 100644
--- a/InvenTree/label/models.py
+++ b/InvenTree/label/models.py
@@ -7,19 +7,33 @@ from __future__ import unicode_literals
 
 import os
 import io
+import logging
+import datetime
 
-from blabel import LabelWriter
-
+from django.conf import settings
 from django.db import models
 from django.core.validators import FileExtensionValidator, MinValueValidator
 from django.core.exceptions import ValidationError, FieldError
 
+from django.template.loader import render_to_string
+
 from django.utils.translation import gettext_lazy as _
 
 from InvenTree.helpers import validateFilterString, normalize
 
+import common.models
 import stock.models
 
+try:
+    from django_weasyprint import WeasyTemplateResponseMixin
+except OSError as err:
+    print("OSError: {e}".format(e=err))
+    print("You may require some further system packages to be installed.")
+    sys.exit(1)
+
+
+logger = logging.getLogger(__name__)
+
 
 def rename_label(instance, filename):
     """ Place the label file into the correct subdirectory """
@@ -43,6 +57,21 @@ def validate_stock_location_filters(filters):
     return filters
 
 
+class WeasyprintLabelMixin(WeasyTemplateResponseMixin):
+    """
+    Class for rendering a label to a PDF
+    """
+
+    pdf_filename = 'label.pdf'
+    pdf_attachment = True
+
+    def __init__(self, request, template, **kwargs):
+
+        self.request = request
+        self.template_name = template
+        self.pdf_filename = kwargs.get('filename', 'label.pdf')
+
+
 class LabelTemplate(models.Model):
     """
     Base class for generic, filterable labels.
@@ -53,6 +82,9 @@ class LabelTemplate(models.Model):
 
     # Each class of label files will be stored in a separate subdirectory
     SUBDIR = "label"
+    
+    # Object we will be printing against (will be filled out later)
+    object_to_print = None
 
     @property
     def template(self):
@@ -92,52 +124,90 @@ class LabelTemplate(models.Model):
         help_text=_('Label template is enabled'),
     )
 
-    length = models.FloatField(
-        default=20,
-        verbose_name=_('Length [mm]'),
-        help_text=_('Label length, specified in mm'),
-        validators=[MinValueValidator(2)]
-    )
-
     width = models.FloatField(
-        default=10,
+        default=50,
         verbose_name=('Width [mm]'),
         help_text=_('Label width, specified in mm'),
         validators=[MinValueValidator(2)]
     )
 
-    def get_record_data(self, items):
+    height = models.FloatField(
+        default=20,
+        verbose_name=_('Height [mm]'),
+        help_text=_('Label height, specified in mm'),
+        validators=[MinValueValidator(2)]
+    )
+
+    @property
+    def template_name(self):
         """
-        Return a list of dict objects, one for each item.
+        Returns the file system path to the template file.
+        Required for passing the file to an external process
         """
 
-        return []
+        template = self.label.name
+        template = template.replace('/', os.path.sep)
+        template = template.replace('\\', os.path.sep)
 
-    def render_to_file(self, filename, items, **kwargs):
+        template = os.path.join(settings.MEDIA_ROOT, template)
+
+        return template
+
+    def get_context_data(self, request):
         """
-        Render labels to a PDF file
+        Supply custom context data to the template for rendering.
+
+        Note: Override this in any subclass
         """
 
-        records = self.get_record_data(items)
+        return {}
 
-        writer = LabelWriter(self.template)
-
-        writer.write_labels(records, filename)
-
-    def render(self, items, **kwargs):
+    def context(self, request):
         """
-        Render labels to an in-memory PDF object, and return it
+        Provides context data to the template.
         """
 
-        records = self.get_record_data(items)
+        context = self.get_context_data(request)
 
-        writer = LabelWriter(self.template)
+        # Add "basic" context data which gets passed to every label
+        context['base_url'] = common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
+        context['date'] = datetime.datetime.now().date()
+        context['datetime'] = datetime.datetime.now()
+        context['request'] = request
+        context['user'] = request.user
+        context['width'] = self.width
+        context['height'] = self.height
 
-        buffer = io.BytesIO()
+        return context
 
-        writer.write_labels(records, buffer)
+    def render_as_string(self, request, **kwargs):
+        """
+        Render the label to a HTML string
 
-        return buffer
+        Useful for debug mode (viewing generated code)
+        """
+
+        return render_to_string(self.template_name, self.context(request), request)
+
+    def render(self, request, **kwargs):
+        """
+        Render the label template to a PDF file
+
+        Uses django-weasyprint plugin to render HTML template
+        """
+
+        wp = WeasyprintLabelMixin(
+            request,
+            self.template_name,
+            base_url=request.build_absolute_uri("/"),
+            presentational_hints=True,
+            **kwargs
+        )
+
+        return wp.render_to_response(
+            self.context(request),
+            **kwargs
+        )
 
 
 class StockItemLabel(LabelTemplate):
@@ -171,29 +241,24 @@ class StockItemLabel(LabelTemplate):
 
         return items.exists()
 
-    def get_record_data(self, items):
+    def get_context_data(self, request):
         """
         Generate context data for each provided StockItem
         """
-        records = []
-        
-        for item in items:
 
-            # Add some basic information
-            records.append({
-                'item': item,
-                'part': item.part,
-                'name': item.part.name,
-                'ipn': item.part.IPN,
-                'quantity': normalize(item.quantity),
-                'serial': item.serial,
-                'uid': item.uid,
-                'pk': item.pk,
-                'qr_data': item.format_barcode(brief=True),
-                'tests': item.testResultMap()
-            })
+        stock_item = self.object_to_print
 
-        return records
+        return {
+            'item': stock_item,
+            'part': stock_item.part,
+            'name': stock_item.part.full_name,
+            'ipn': stock_item.part.IPN,
+            'quantity': normalize(stock_item.quantity),
+            'serial': stock_item.serial,
+            'uid': stock_item.uid,
+            'qr_data': stock_item.format_barcode(brief=True),
+            'tests': stock_item.testResultMap()
+        }
 
 
 class StockLocationLabel(LabelTemplate):
@@ -226,17 +291,14 @@ class StockLocationLabel(LabelTemplate):
 
         return locs.exists()
 
-    def get_record_data(self, locations):
+    def get_context_data(self, request):
         """
         Generate context data for each provided StockLocation
         """
 
-        records = []
-        
-        for loc in locations:
+        location = self.object_to_print
 
-            records.append({
-                'location': loc,
-            })
-
-        return records
+        return {
+            'location': location,
+            'qr_data': location.format_barcode(brief=True),
+        }
diff --git a/InvenTree/label/templates/label/label_base.html b/InvenTree/label/templates/label/label_base.html
new file mode 100644
index 0000000000..2c564d1132
--- /dev/null
+++ b/InvenTree/label/templates/label/label_base.html
@@ -0,0 +1,28 @@
+{% load report %}
+{% load barcode %}
+
+<head>
+    <style>
+        @page {
+            size: {{ width }}mm {{ height }}mm;
+            {% block margin %}
+            margin: 0mm;
+            {% endblock %}
+        }
+
+        img {
+            display: inline-block;
+            image-rendering: pixelated;
+        }
+
+        {% block style %}
+        {% endblock %}
+
+    </style>
+</head>
+
+<body>
+    {% block content %}
+    <!-- Label data rendered here! -->
+    {% endblock %}
+</body>
diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py
index dd937f0aba..09bd9fde8f 100644
--- a/InvenTree/report/api.py
+++ b/InvenTree/report/api.py
@@ -164,7 +164,7 @@ class ReportPrintMixin:
             report.object_to_print = item
 
             if debug_mode:
-                outputs.append(report.render_to_string(request))
+                outputs.append(report.render_as_string(request))
             else:
                 outputs.append(report.render(request))
 
diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py
index 00777449fc..abbdea9025 100644
--- a/InvenTree/report/models.py
+++ b/InvenTree/report/models.py
@@ -221,7 +221,7 @@ class ReportTemplateBase(ReportBase):
 
         return context
 
-    def render_to_string(self, request, **kwargs):
+    def render_as_string(self, request, **kwargs):
         """
         Render the report to a HTML stiring.