diff --git a/modules/label/commerce_pos_label.info.yml b/modules/label/commerce_pos_label.info.yml
new file mode 100644
index 0000000..5a0d71d
--- /dev/null
+++ b/modules/label/commerce_pos_label.info.yml
@@ -0,0 +1,7 @@
+name: Commerce Point of Sale Labels
+type: module
+description: Provides the ability to generate and print product labels.
+core: 8.x
+package: Commerce (POS)
+dependencies:
+  - commerce_pos
diff --git a/modules/label/commerce_pos_label.label_formats.yml b/modules/label/commerce_pos_label.label_formats.yml
new file mode 100644
index 0000000..72b7bdb
--- /dev/null
+++ b/modules/label/commerce_pos_label.label_formats.yml
@@ -0,0 +1,6 @@
+commerce_pos_label_30334:
+  title: Dymo 30334 - 1 1/4" x 2 1/4"
+  css: FALSE
+  dimensions:
+    width: 2.25
+    height: 1.0
diff --git a/modules/label/commerce_pos_label.links.menu.yml b/modules/label/commerce_pos_label.links.menu.yml
new file mode 100755
index 0000000..6c60d0b
--- /dev/null
+++ b/modules/label/commerce_pos_label.links.menu.yml
@@ -0,0 +1,5 @@
+commerce_pos_label.print:
+  title: 'Print Labels'
+  parent: 'commerce_pos.main'
+  description: 'Print product labels'
+  route_name: 'commerce_pos_label.print_labels'
diff --git a/modules/label/commerce_pos_label.module b/modules/label/commerce_pos_label.module
new file mode 100644
index 0000000..06e9b32
--- /dev/null
+++ b/modules/label/commerce_pos_label.module
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains commerce_pos_label.module.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Implements hook_help().
+ */
+function commerce_pos_label_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the commerce_pos_label module.
+    case 'help.page.commerce_pos_label':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Provides the ability to generate and print product labels.') . '</p>';
+
+      $label_formats = commerce_pos_label_get_label_formats();
+
+      // Print out our currently defined label formats.
+      if (!empty($label_formats)) {
+        $output .= '<strong>' . t('Defined label format(s):') . '</strong>';
+
+        foreach ($label_formats as $label_format) {
+          $output .= '<br/>' . $label_format['title']->render();
+        }
+      }
+
+      return $output;
+
+    default:
+  }
+}
+
+/**
+ * Retrieves a list of possible formats that labels can be printed in.
+ *
+ * @param bool $reset
+ *   If TRUE, the cache will be rebuilt.
+ *
+ * @return array
+ *   A list of label formats.
+ */
+function commerce_pos_label_get_label_formats($reset = FALSE) {
+  $cid = 'commerce_pos_label_formats';
+
+  if ($reset) {
+    $cache = \Drupal::cache()->delete($cid);
+  }
+  else {
+    $cache = \Drupal::cache()->get($cid);
+  }
+
+  if ($cache) {
+    $formats = $cache->data;
+  }
+  else {
+    $label_manager = \Drupal::service('plugin.manager.label_format');
+    $formats = $label_manager->getDefinitions();
+
+    \Drupal::cache()->set($cid, $formats);
+  }
+
+  return $formats;
+
+}
+
+/**
+ * Retrieves the definition for a specific label format.
+ */
+function commerce_pos_label_format_load($format_name) {
+  $formats = commerce_pos_label_get_label_formats();
+  return (isset($formats[$format_name]) ? $formats[$format_name] : FALSE);
+}
diff --git a/modules/label/commerce_pos_label.permissions.yml b/modules/label/commerce_pos_label.permissions.yml
new file mode 100644
index 0000000..138c859
--- /dev/null
+++ b/modules/label/commerce_pos_label.permissions.yml
@@ -0,0 +1,2 @@
+commerce pos print labels:
+  title: 'Print labels with Commerce Point of Sale'
diff --git a/modules/label/commerce_pos_label.routing.yml b/modules/label/commerce_pos_label.routing.yml
new file mode 100644
index 0000000..c2ee7be
--- /dev/null
+++ b/modules/label/commerce_pos_label.routing.yml
@@ -0,0 +1,15 @@
+commerce_pos_label.print_labels:
+ path: 'admin/commerce/pos/labels'
+ defaults:
+   _title: 'Print labels'
+   _form: '\Drupal\commerce_pos_label\Form\PrintLabelsForm'
+ requirements:
+   _permission: 'commerce pos print labels'
+
+commerce_pos_label.print_label:
+ path: 'admin/commerce/pos/labels/{product_variation_id}'
+ defaults:
+   _title: 'Print labels'
+   _form: '\Drupal\commerce_pos_label\Form\PrintLabelsForm'
+ requirements:
+   _permission: 'commerce pos print labels'
diff --git a/modules/label/commerce_pos_label.services.yml b/modules/label/commerce_pos_label.services.yml
new file mode 100644
index 0000000..dc6bffd
--- /dev/null
+++ b/modules/label/commerce_pos_label.services.yml
@@ -0,0 +1,4 @@
+services:
+  plugin.manager.label_format:
+    class: Drupal\commerce_pos_label\LabelFormatManager
+    arguments: ['@module_handler', '@cache.discovery']
diff --git a/modules/label/composer.json b/modules/label/composer.json
new file mode 100644
index 0000000..94f2adb
--- /dev/null
+++ b/modules/label/composer.json
@@ -0,0 +1,10 @@
+{
+  "name": "drupal/commerce_pos_label",
+  "type": "drupal-module",
+  "description": "Commerce POS label printing.",
+  "license": "GPL-2.0+",
+  "require": {
+    "picqer/php-barcode-generator": "0.2.2"
+  },
+  "minimum-stability": "dev"
+}
diff --git a/modules/label/js/commerce_pos_label.js b/modules/label/js/commerce_pos_label.js
new file mode 100644
index 0000000..31cca75
--- /dev/null
+++ b/modules/label/js/commerce_pos_label.js
@@ -0,0 +1,20 @@
+(function ($, Drupal, drupalSettings) {
+
+  /**
+   * Ajax command to set the toolbar subtrees.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+   * @param {object} response
+   *   JSON response from the Ajax request.
+   * @param {number} [status]
+   *   XMLHttpRequest status.
+   */
+  Drupal.AjaxCommands.prototype.printLabels = function (ajax, response, status) {
+    $(response.content).print({
+      globalStyles: false,
+      stylesheet: drupalSettings.commercePosLabel.cssUrl
+    });
+  };
+
+}(jQuery, Drupal, drupalSettings));
diff --git a/modules/label/src/Ajax/PrintLabelsCommand.php b/modules/label/src/Ajax/PrintLabelsCommand.php
new file mode 100644
index 0000000..4f79504
--- /dev/null
+++ b/modules/label/src/Ajax/PrintLabelsCommand.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\commerce_pos_label\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for retrieving data and printing labels.
+ */
+class PrintLabelsCommand implements CommandInterface {
+
+  protected $labels;
+
+  public function __construct($labels, $format) {
+    $this->labels = $labels;
+  }
+
+  /**
+   * Return an array to be run through json_encode and sent to the client.
+   */
+  public function render() {
+    return [
+      'command' => 'printLabels',
+      'content' => $this->labels,
+    ];
+  }
+
+}
diff --git a/modules/label/src/Controller/PrintController.php b/modules/label/src/Controller/PrintController.php
new file mode 100644
index 0000000..9d2e923
--- /dev/null
+++ b/modules/label/src/Controller/PrintController.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Drupal\commerce_pos_labels\Controller;
+
+use Drupal\commerce_order\Entity\OrderInterface;
+use Drupal\commerce_pos_label\Ajax\PrintLabelsCommand;
+use Drupal\commerce_price\Entity\Currency;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\HtmlCommand;
+use Drupal\Core\Ajax\SettingsCommand;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+
+/**
+ * Class PrintController.
+ */
+class PrintController extends ControllerBase {
+
+  /**
+   * A controller callback.
+   */
+  public function ajaxReceipt(OrderInterface $commerce_order) {
+    $renderer = \Drupal::service('renderer');
+
+    $build = $this->showReceipt($commerce_order);
+    unset($build['#receipt']['print']);
+
+    // TODO How to get CSS for the label format?
+    $module_path = \Drupal::service('module_handler')->getModule('commerce_pos_label')->getPath();
+    $css_url = Url::fromUri('base:' . $module_path . '/css/commerce_pos_receipt_print.css', ['absolute' => TRUE])->toString();
+
+    $response = new AjaxResponse();
+    $response->addCommand(new SettingsCommand(['commercePosLabel' => ['cssUrl' => $css_url]], TRUE));
+    $response->addCommand(new PrintLabelsCommand($renderer->render($build)));
+
+    return $response;
+  }
+
+  /**
+   * A controller callback.
+   */
+  public function showReceipt(OrderInterface $commerce_order) {
+
+    $number_formatter_factory = \Drupal::service('commerce_price.number_formatter_factory');
+    $number_formatter = $number_formatter_factory->createInstance();
+
+    $sub_total_price = $commerce_order->getSubtotalPrice();
+    $currency = Currency::load($sub_total_price->getCurrencyCode());
+    $formatted_amount = $number_formatter->formatCurrency($sub_total_price->getNumber(), $currency);
+
+    $items = $commerce_order->getItems();
+    foreach ($items as $item) {
+      $totals[] = [
+        $item->getTitle(),
+        $number_formatter->formatCurrency($item->getAdjustedTotalPrice()->getNumber(), $currency)
+      ];
+    }
+
+    $totals[] = ['Subtotal', $formatted_amount];
+
+    // Commerce appears to have a bug where if no adjustments exist, it will
+    // return a 0 => null array, which will still trigger a foreach loop.
+    foreach ($commerce_order->collectAdjustments() as $key => $adjustment) {
+      if (!empty($adjustment)) {
+        $amount = $adjustment->getAmount();
+        $currency = Currency::load($amount->getCurrencyCode());
+        $formatted_amount = $number_formatter->formatCurrency($amount->getNumber(), $currency);
+
+        $totals[] = [
+          $adjustment->getLabel(),
+          $formatted_amount,
+        ];
+      }
+    }
+
+    // Collecting the total price on the cart.
+    $total_price = $commerce_order->getTotalPrice();
+    $formatted_amount = $number_formatter->formatCurrency($total_price->getNumber(), $currency);
+    $totals[] = ['Total', $formatted_amount];
+
+    $payment_storage = \Drupal::entityTypeManager()->getStorage('commerce_payment');
+    $payments = $payment_storage->loadMultipleByOrder($commerce_order);
+    foreach ($payments as $payment) {
+      $totals[] = ['Payment', $payment->getState()->getLabel()];
+    }
+    $ajax_url = URL::fromRoute('commerce_pos_receipt.ajax', ['commerce_order' => $commerce_order->id()], [
+      'attributes' => [
+        'class' => ['use-ajax', 'button'],
+      ],
+    ]);
+
+    $config = \Drupal::config('commerce_pos_receipt.settings');
+    $build = ['#theme' => 'commerce_pos_receipt'];
+    $build['#receipt'] = [
+      'header' => [
+        '#markup' => check_markup($config->get('header'), $config->get('header_format')),
+      ],
+      'body' => [
+        '#type' => 'table',
+        '#rows' => $totals,
+      ],
+      'footer' => [
+        '#markup' => check_markup($config->get('footer'), $config->get('footer_format')),
+      ],
+      'print' => [
+        '#title' => t('Print receipt'),
+        '#prefix' => '<div id="commerce-pos-receipt"></div>',
+        '#type' => 'link',
+        '#url' => $ajax_url,
+      ],
+    ];
+    return $build;
+  }
+
+}
diff --git a/modules/label/src/Form/PrintLabelsForm.php b/modules/label/src/Form/PrintLabelsForm.php
new file mode 100644
index 0000000..717c3ea
--- /dev/null
+++ b/modules/label/src/Form/PrintLabelsForm.php
@@ -0,0 +1,313 @@
+<?php
+
+namespace Drupal\commerce_pos_label\Form;
+
+use Drupal\commerce_pos_label\Ajax\PrintLabelsCommand;
+use Drupal\commerce_product\Entity\Product;
+use Drupal\commerce_product\Entity\ProductVariation;
+use Drupal\commerce_product\Entity\ProductVariationInterface;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Picqer\Barcode\BarcodeGeneratorHTML;
+
+class PrintLabelsForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'commerce_pos_label_print_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, ProductVariationInterface $commerce_product_variation = NULL) {
+    $product_variation = $commerce_product_variation;
+    $labels_to_create = $this->getLabelList($form_state);
+    if ($product_variation && !$labels_to_create) {
+      $labels_to_create[$product_variation->id()] = $this->buildInfoArray($product_variation);
+      $form_state->setValue('label_list', $labels_to_create);
+    }
+
+    $format_options = array_map(function ($format) {
+      return $format['title'];
+    }, commerce_pos_label_get_label_formats());
+
+    // We need at least 1 label format to proceed.
+    if (empty($format_options)) {
+      drupal_set_message($this->t('There are no available label formats. Please enable at least one POS label format module.'), 'error');
+      return $form;
+    }
+
+    $form_wrapper_id = 'commerce-pos-label-form-container';
+
+    $form['#prefix'] = '<div id="' . $form_wrapper_id . '" class="commerce-pos-form-container">';
+    $form['#suffix'] = '</div>';
+
+    $form['label_format'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Label format'),
+      '#options' => $format_options,
+      '#required' => TRUE,
+      '#default_value' => key($format_options),
+    ];
+
+    // TODO: product search.
+    $form['product_search'] = [
+      '#type' => 'entity_autocomplete',
+      '#target_type' => 'commerce_product_variation',
+      '#title' => t('Product Search'),
+      '#title_display' => 'invisible',
+      '#size' => 60,
+      '#description' => $this->t('Search by product title.'),
+      '#attributes' => [
+        'class' => [
+          'commerce-pos-product-autocomplete',
+          'commerce-pos-product-search',
+        ],
+        'placeholder' => $this->t('Product Search'),
+      ],
+    ];
+
+    $label_list_id = 'commerce_pos_label_list';
+
+    // TODO: product add submit.
+    $form['product_search_add'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Add'),
+      '#validate' => ['::productAddValidate'],
+      '#submit' => ['::productAddSubmit'],
+      '#attributes' => [
+        'class' => ['commerce-pos-label-btn-add commerce-pos-btn fixed-width btn-success'],
+      ],
+      '#ajax' => [
+        'wrapper' => $label_list_id,
+        'callback' => '::labelsFormAjax',
+      ],
+    ];
+
+    $form['label_options'] = [
+      '#type' => 'container',
+      '#id' => 'commerce-pos-label-label-options-container',
+      '#prefix' => '<br>',
+    ];
+
+    $form['label_options']['label_list'] = [
+      '#type' => 'table',
+      '#header' => [
+        $this->t('Quantity'),
+        $this->t('Title'),
+        $this->t('Description'),
+        $this->t('Price'),
+        $this->t('Remove'),
+      ],
+      '#tree' => TRUE,
+      '#attributes' => [
+        'id' => $label_list_id,
+      ],
+    ];
+
+    foreach ($labels_to_create as $product_variation_id => $label_info) {
+
+      $form['label_options']['label_list'][$product_variation_id]['quantity'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Quantity'),
+        '#value' => $label_info['quantity'],
+        '#required' => TRUE,
+        '#size' => 5,
+      ];
+
+      $form['label_options']['label_list'][$product_variation_id]['title'] = [
+        '#title' => $this->t('Title'),
+        '#type' => 'textfield',
+        '#required' => TRUE,
+        '#value' => $label_info['title'],
+      ];
+
+      $form['label_options']['label_list'][$product_variation_id]['description'] = [
+        '#title' => $this->t('Description'),
+        '#type' => 'textfield',
+        '#value' => $label_info['description'],
+      ];
+
+      $form['label_options']['label_list'][$product_variation_id]['price'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Price'),
+        '#value' => $label_info['price'],
+        '#required' => TRUE,
+        '#size' => 5,
+      ];
+
+      // TODO: remove submit.
+      $form['label_options']['label_list'][$product_variation_id]['remove'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Remove'),
+        '#name' => 'remove-' . $product_variation_id,
+        '#validate' => ['::productRemoveValidate'],
+        '#submit' => ['::productRemoveSubmit'],
+        '#product_id' => $product_variation_id,
+        '#ajax' => [
+          'wrapper' => $label_list_id,
+          'callback' => '::labelsFormAjax',
+        ],
+        '#attributes' => [
+          'class' => ['commerce-pos-btn fixed-width btn-danger'],
+        ],
+      ];
+    }
+
+    if ($labels_to_create) {
+      // TODO: print label submit.
+      $form['label_options']['print_labels'] = [
+        '#type' => 'button',
+        '#value' => $this->t('Print'),
+        '#suffix' => '<div id="product-barcode"></div>',
+        '#ajax' => [
+          'callback' => '::generateBarcode',
+        ],
+        '#attributes' => [
+          'class' => ['commerce-pos-btn fixed-width'],
+        ],
+      ];
+    }
+
+    return $form;
+  }
+
+  /**
+   * Generate barcode from product variation title.
+   */
+  public function generateBarcode($form, FormStateInterface $form_state) {
+    // TODO Add barcode generation to rendering of a label.
+    // TODO Only generate the barcode once per SKU.
+    // TODO Must have a UPC to get the barcode. UPC field?
+    // TODO Barcode as a field formatter?
+    // TODO Support multiple barcode types?
+    $values = $form_state->getValues();
+    $label_list = current($values['label_list']);
+    $label = $label_list['title'];
+
+    $generator = new BarcodeGeneratorHTML();
+    $barcode = $generator->getBarcode($label, $generator::TYPE_CODE_128, 1, 80);
+    $response = new AjaxResponse();
+    $response->addCommand(new ReplaceCommand('#product-barcode', $barcode));
+    return $response;
+  }
+
+  /**
+   * Ajax callback for labels form.
+   */
+  public function labelsFormAjax($form, FormStateInterface $form_state) {
+    $form = $this->buildForm($form, $form_state);
+    return $form['label_options']['label_list'];
+  }
+
+  /**
+   * Validation for adding a product.
+   */
+  public function productAddValidate(array &$form, FormStateInterface $form_state) {
+
+    $product_id = $form_state->getValue('product_search');
+
+    if (empty($product_id) || !ProductVariation::load($product_id)) {
+      $form_state->setError($form['product_search'], $this->t('Invalid product.'));
+    }
+  }
+
+  /**
+   * Submit handler for adding a product.
+   */
+  public function productAddSubmit(array &$form, FormStateInterface $form_state) {
+
+    $product_id = $form_state->getValue('product_search');
+
+    $product = ProductVariation::load($product_id);
+    $labels_to_create = $this->getLabelList($form_state);
+    if (empty($labels_to_create[$product_id])) {
+      $labels_to_create[$product_id] = $this->buildInfoArray($product);
+      $form_state->setValue('label_list', $labels_to_create);
+    }
+    $form_state->setRebuild(TRUE);
+  }
+
+  /**
+   * Validation for removing a product.
+   */
+  public function productRemoveValidate(array &$form, FormStateInterface $form_state) {
+    // TODO validate removing a product (why? what is there to validate?).
+  }
+
+  /**
+   * Submit handler for removing a product.
+   */
+  public function productRemoveSubmit(array &$form, FormStateInterface $form_state) {
+    $trigger = $form_state->getTriggeringElement();
+    if (!empty($trigger['#product_id'])) {
+      $labels = $this->getLabelList($form_state);
+      unset($labels[$trigger['#product_id']]);
+      $form_state->setValue('label_list', $labels);
+    }
+    $form_state->setRebuild(TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    // TODO validate the form.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // TODO print the label.
+    $labels = [];
+    $label_values = $this->getLabelList($form_state);
+    // TODO Should we wait for later to multiply by the desired quantity in case
+    // it's huge?
+    foreach ($label_values as $label) {
+      for ($i = 0; $i < $label['quantity']; $i++) {
+        $labels[] = $label;
+      }
+    }
+    $response = new AjaxResponse();
+    $response->addCommand(new PrintLabelsCommand($labels, $form_state->getValue('label_format')));
+    $form_state->setRebuild(TRUE);
+  }
+
+  /**
+   * Build an array of product info used for printing labels.
+   *
+   * @param \Drupal\commerce_product\Entity\ProductVariationInterface $product_variation
+   *   The product variation to create the array for.
+   *
+   * @return array
+   *   The info array.
+   */
+  protected function buildInfoArray(ProductVariationInterface $product_variation) {
+    return [
+      'title' => $product_variation->getSku(),
+      'quantity' => 1,
+      'price' => round($product_variation->getPrice()->getNumber(), 2),
+      'description' => $product_variation->getTitle(),
+    ];
+  }
+
+  /**
+   * Get the label list value.
+   *
+   * Ensures that when label_list is empty we get an empty array instead of an
+   * empty string.
+   *
+   * @return array
+   *   An array of form elements for the label list.
+   */
+  protected function getLabelList(FormStateInterface $form_state) {
+    return $form_state->getValue('label_list') ?: [];
+  }
+
+}
diff --git a/modules/label/src/LabelFormatManager.php b/modules/label/src/LabelFormatManager.php
new file mode 100644
index 0000000..4c075fe
--- /dev/null
+++ b/modules/label/src/LabelFormatManager.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\commerce_pos_label;
+
+use Drupal\commerce_pos_label\Plugin\LabelFormat\LabelFormat;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\YamlDiscovery;
+
+/**
+ * Manages discovery and instantiation of label_format plugins.
+ *
+ * @see \Drupal\commerce_pos\Plugin\LabelFormat\LabelFormatInterface
+ * @see plugin_api
+ */
+class LabelFormatManager extends DefaultPluginManager {
+
+  /**
+   * Default values for each plugin.
+   *
+   * @var array
+   */
+  protected $defaults = [
+    'id' => '',
+    'title' => '',
+    'css' => TRUE,
+    'dimensions' => [
+      'width' => 0,
+      'height' => 0,
+    ],
+    'class' => LabelFormat::class,
+  ];
+
+  /**
+   * Constructs a new LabelFormatManager object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend) {
+    $this->moduleHandler = $module_handler;
+    $this->setCacheBackend($cache_backend, 'label_format', ['label_format']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDiscovery() {
+    if (!isset($this->discovery)) {
+      $this->discovery = new YamlDiscovery('label_formats', $this->moduleHandler->getModuleDirectories());
+      $this->discovery->addTranslatableProperty('title', 'title_context');
+      $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
+    }
+    return $this->discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+    $definition['id'] = $plugin_id;
+    if (empty($definition['title'])) {
+      throw new PluginException(sprintf('The label format %s must define the title property.', $plugin_id));
+    }
+    if (empty($definition['dimensions']['width'])) {
+      throw new PluginException(sprintf('The label format %s must define the dimension width property.', $plugin_id));
+    }
+    if (empty($definition['dimensions']['height'])) {
+      throw new PluginException(sprintf('The label format %s must define the dimension height property.', $plugin_id));
+    }
+  }
+
+}
diff --git a/modules/label/src/Plugin/LabelFormat/LabelFormat.php b/modules/label/src/Plugin/LabelFormat/LabelFormat.php
new file mode 100644
index 0000000..79bf0c3
--- /dev/null
+++ b/modules/label/src/Plugin/LabelFormat/LabelFormat.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\commerce_pos_label\Plugin\LabelFormat;
+
+use Drupal\Core\Plugin\PluginBase;
+
+/**
+ * Provides the label format class.
+ */
+class LabelFormat extends PluginBase implements LabelFormatInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->pluginDefinition['id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->pluginDefinition['title'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCss() {
+    return $this->pluginDefinition['css'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDimensions() {
+    return $this->pluginDefinition['dimensions'] == TRUE;
+  }
+
+}
diff --git a/modules/label/src/Plugin/LabelFormat/LabelFormatInterface.php b/modules/label/src/Plugin/LabelFormat/LabelFormatInterface.php
new file mode 100644
index 0000000..b2dd95c
--- /dev/null
+++ b/modules/label/src/Plugin/LabelFormat/LabelFormatInterface.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\commerce_pos_label\Plugin\LabelFormat;
+
+/**
+ * Defines the interface for label formats.
+ */
+interface LabelFormatInterface {
+
+  /**
+   * Gets the label format ID.
+   *
+   * @return string
+   *   The label format ID.
+   */
+  public function getId();
+
+  /**
+   * Gets the label format title.
+   *
+   * @return string
+   *   The label format title.
+   */
+  public function getTitle();
+
+  /**
+   * Gets the label format css.
+   *
+   * @return mixed
+   *   The label format css or FALSE if none.
+   */
+  public function getCss();
+
+  /**
+   * Gets the label format dimensions: width and height.
+   *
+   * @return array
+   *   An array containing the width and height.
+   */
+  public function getDimensions();
+
+}
diff --git a/modules/label/templates/commerce-pos-labels.html.twig b/modules/label/templates/commerce-pos-labels.html.twig
new file mode 100644
index 0000000..ed7ce28
--- /dev/null
+++ b/modules/label/templates/commerce-pos-labels.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+* @file
+* Labels document.
+*
+* Available variables:
+* - labels: The labels.
+*
+* @ingroup themeable
+*/
+#}
+
+{{ attach_library('commerce_pos_receipt/receipt') }}
+{{ attach_library('commerce_pos_receipt/jQuery.print') }}
+<div class="commerce-pos-labels">
+    {% for label in labels %}
+        <div class="commerce-pos-label">
+            <div class="price">{{ label.price }}</div>
+            <div class="title">{{ label.title }}</div>
+            <div class="description">{{ label.description }}</div>
+            <div class="barcode">{{ label.barcode }}</div>
+        </div>
+    {% endfor %}
+</div>
