diff --git a/gulpfile.js b/gulpfile.js
index d15b01e..be7cd52 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -6,6 +6,7 @@ var sourcemaps = require('gulp-sourcemaps');
 
 var config = {
   'sassDirectories': [
+    { src: './modules/label/sass/**/*.scss', dest: './modules/label/css'},
     { src: './custom-sass/**/*.scss', dest: './css' }
   ]
 };
diff --git a/js/commerce_pos.transaction.js b/js/commerce_pos.transaction.js
index b085094..33af95a 100644
--- a/js/commerce_pos.transaction.js
+++ b/js/commerce_pos.transaction.js
@@ -78,7 +78,14 @@
             ui.item.element.addClass('selected');
           },
           select: function(event, ui){
-            addProductSku(ui.item.element.find('.btn-add').attr('data-product-sku'));
+            var sku = ui.item.element.find('.btn-add').attr('data-product-sku');
+
+            if (Drupal.settings.commercePosSale.autoCompleteCallback) {
+              Drupal[Drupal.settings.commercePosSale.autoCompleteNamespace][Drupal.settings.commercePosSale.autoCompleteCallback](sku);
+            }
+            else {
+              addProductSku(sku);
+            }
           },
           context: this
         });
@@ -97,7 +104,15 @@
               e.preventDefault();
               e.stopPropagation(); // prevent product from being added via bubbling
 
-              addProductSku($(this).attr('data-product-sku'));
+              var sku = $(this).attr('data-product-sku');
+
+              if (Drupal.settings.commercePosSale.autoCompleteCallback) {
+                Drupal[Drupal.settings.commercePosSale.autoCompleteNamespace][Drupal.settings.commercePosSale.autoCompleteCallback](sku);
+              }
+              else {
+                addProductSku(sku);
+              }
+
               element.data('ui-autocomplete').close();
 
           });
diff --git a/modules/label/commerce_pos_label.api.php b/modules/label/commerce_pos_label.api.php
new file mode 100644
index 0000000..1b227f3c
--- /dev/null
+++ b/modules/label/commerce_pos_label.api.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * API documentation for Commerce POS labels.
+ */
+
+/**
+ * Allows modules to specify their own label formats.
+ *
+ * Modules should return an associative array of all formats:
+ *
+ * @return array
+ *   An array whose keys are format machine names and whose values identify
+ *   properties for the format:
+ *
+ *   - title: The human-readable name of the format.
+ *   - css: (optional) An absolute URL to a CSS to instead when printing labels
+ *     in this format. If not provided, the default CSS file will be used.
+ *   - barcode: An associative array of information needed to generate the
+ *     label's barcode. Elements:
+ *     - type: The type of barcode format to generate. This should be one of the
+ *       constants defined in the BarcodeGenerator class.
+ *     - widthFactor: The width of a single bar element in pixels
+ *     - totalHeight: The height of the barcode in pixels.
+ *     - color: An array of RGB values for the color of the barcode.
+ *   - dimensions: An associative array of dimension information for label.
+ *     Elements:
+ *     - width: The width of the label in inches.
+ *     - height: The height of the label in inches.
+ */
+function hook_commerce_pos_label_format_info() {
+  $formats = array();
+
+  return $formats;
+}
+
+/**
+ * Allows modules to modify the label format information.
+ *
+ * @param array $formats
+ *   An array of label formats.
+ */
+function hook_commerce_pos_label_format_info_alter(&$formats) {
+  // No example.
+}
diff --git a/modules/label/commerce_pos_label.info b/modules/label/commerce_pos_label.info
new file mode 100644
index 0000000..2b899b8
--- /dev/null
+++ b/modules/label/commerce_pos_label.info
@@ -0,0 +1,11 @@
+name = Commerce POS Labels
+description = Provides the ability to generate and print product labels.
+core = 7.x
+package = Commerce POS
+
+dependencies[] = commerce_pos
+dependencies[] = commerce_cart
+dependencies[] = jquery_print
+dependencies[] = composer_manager
+
+files[] = tests/commerce_pos_label.test
diff --git a/modules/label/commerce_pos_label.install b/modules/label/commerce_pos_label.install
new file mode 100644
index 0000000..096a0ab
--- /dev/null
+++ b/modules/label/commerce_pos_label.install
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Install hooks and update functions for Commerce POS Labels.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+/*function commerce_pos_label_schema() {
+  $schema['commerce_pos_label_job'] = array(
+    'fields' => array(
+      'job_id' => array(
+        'description' => 'The primary identifier for a label job.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+
+    ),
+  );
+
+  return $schema;
+}*/
diff --git a/modules/label/commerce_pos_label.module b/modules/label/commerce_pos_label.module
new file mode 100644
index 0000000..10d557d
--- /dev/null
+++ b/modules/label/commerce_pos_label.module
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * @file
+ * Hook implementations and utility functions for Commerce POS Labels.
+ */
+
+use Picqer\Barcode\BarcodeGeneratorPNG;
+use Picqer\Barcode\Exceptions\BarcodeException;
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_pos_label_menu() {
+  $items['admin/commerce/pos/labels'] = array(
+    'title' => 'Print labels',
+    'page callback' => 'commerce_pos_label_labels_form_page',
+    'access arguments' => array('commerce pos print labels'),
+    'file' => 'includes/commerce_pos_label.admin.inc',
+  );
+  $items['admin/commerce/config/pos/labels'] = array(
+    'title' => 'Product labels',
+    'page arguments' => array('commerce_pos_label_settings_form'),
+    'page callback' => 'drupal_get_form',
+    'access arguments' => array('administer commerce pos labels'),
+    'file' => 'includes/commerce_pos_label.admin.inc',
+  );
+  $items['admin/commerce/pos/labels/autocomplete'] = array(
+    'title' => 'Search autocomplete',
+    'page callback' => 'commerce_pos_label_search_autocomplete',
+    'access arguments' => array('commerce pos print labels'),
+    'file' => 'includes/commerce_pos_label.ajax.inc',
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/commerce/products/%commerce_product/labels'] = array(
+    'title' => 'Labels',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('commerce_pos_label_labels_form', 3),
+    'access arguments' => array('commerce pos print labels'),
+    'file' => 'includes/commerce_pos_label.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function commerce_pos_label_permission() {
+  return array(
+    'commerce pos print labels' =>  array(
+      'title' => t('Print product labels'),
+    ),
+    'administer commerce pos labels' => array(
+      'title' => t('Administer Commerce POS Labels'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function commerce_pos_label_theme($existing, $type, $theme, $path) {
+  return array(
+    'commerce_pos_label_labels_form' => array(
+      'render element' => 'form',
+      'path' => $path . '/theme',
+      'file' => 'commerce_pos_label.theme.inc',
+    ),
+    'commerce_pos_label_print' => array(
+      'path' => $path . '/theme',
+      'file' => 'commerce_pos_label.theme.inc',
+      'variables' => array(
+        'product_ids' => array(),
+        'format' => NULL,
+      ),
+    ),
+    'commerce_pos_label' => array(
+      'template' => 'commerce-pos-label',
+      'file' => 'commerce_pos_label.theme.inc',
+      'path' => $path . '/theme',
+      'variables' => array(
+        'format' => NULL,
+        'barcode' => NULL,
+        'product' => NULL,
+        'price' => 0,
+        'description' => '',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function commerce_pos_label_form_alter(&$form, &$form_state, $form_id) {
+  if (in_array($form_id, array('commerce_pos_sale', 'commerce_pos_return'))) {
+    $form['#attached']['library'][] = array('system', 'drupal.ajax');
+    $form['#attached']['js'][] = drupal_get_path('module', 'commerce_pos_label') . '/js/commerce_pos_label.js';
+  }
+}
+
+/**
+ * Implements hook_commerce_pos_header_links_alter().
+ */
+function commerce_pos_label_commerce_pos_header_links_alter(&$links) {
+  $links['admin/commerce/pos/labels'] = t('Print labels');
+}
+
+/**
+ * Retrieves a list of possible formats that labels can be printed in.
+ *
+ * @TODO: Make sure $reset is set to FALSE
+ */
+function commerce_pos_label_get_label_formats($reset = TRUE) {
+  if ($reset) {
+    $cache = cache_clear_all(__FUNCTION__, 'cache');
+  }
+  else {
+    $cache = cache_get(__FUNCTION__, 'cache');
+  }
+
+  if ($cache === NULL) {
+    $formats = module_invoke_all('commerce_pos_label_format_info');
+
+    // Allow other modules to alter the label format info.
+    drupal_alter('commerce_pos_label_format_info_alter', $formats);
+
+    cache_set(__FUNCTION__, $formats);
+  }
+  else {
+    $formats = $cache->data;
+  }
+
+  return $formats;
+}
+
+/**
+ * Implements hook_commerce_pos_label_format_info().
+ */
+function commerce_pos_label_commerce_pos_label_format_info() {
+  return array(
+    'commerce_pos_label_30334' => array(
+      'title' => t('Dymo 30334 - 1 1/4" x 2 1/4"'),
+      'css' => FALSE,
+      'barcode' => array(
+        'type' => BarcodeGeneratorPNG::TYPE_UPC_A,
+        'widthFactor' => 2,
+        'totalHeight' => 40,
+        'color' => array(0, 0, 0),
+      ),
+      'dimensions' => array(
+        'width' => 2.25,
+        'height' => 1.0,
+      ),
+    ),
+  );
+}
+
+/**
+ * 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);
+}
+
+/**
+ * Generates a barcode image for a product.
+ *
+ * @param stdClass $product
+ *   The Commerce Product to generate a barcode for.
+ *
+ * @param array $format
+ *   A label format array, as provided by hook_commerce_pos_label_format_info().
+ *
+ * @return bool|string
+ *   A string with the image data for a barcode, or FALSE if the bar code could
+ *   not be generated.
+ */
+function commerce_pos_label_generate_barcode($product, $format) {
+  $generator = new BarcodeGeneratorPNG();
+
+  if ($field_upc = field_get_items('commerce_product', $product, 'field_upc')) {
+    try {
+      $barcode_settings = $format['barcode'];
+
+      $barcode =  $generator->getBarcode($field_upc[0]['value'], $barcode_settings['type'], $barcode_settings['widthFactor'], $barcode_settings['totalHeight'], $barcode_settings['color']);
+      return $barcode;
+    }
+    catch (BarcodeException $e) {
+      watchdog('commerce_pos_label', 'Unable to generate barcode for product @sku. @exception: @message', array(
+        '@exception' => get_class($e),
+        '@sku' => $product->sku,
+        '@message' => $e->getMessage(),
+      ), WATCHDOG_ERROR);
+    }
+  }
+
+  return FALSE;
+}
+
+/**
+ * Retrieve a list of fields on a product type that are for attributes.
+ */
+function commerce_pos_label_attribute_fields($type, $reset = FALSE) {
+  $attribute_fields = &drupal_static(__FUNCTION__, NULL);
+
+  if ($reset) {
+    $attribute_fields = NULL;
+  }
+
+  if ($attribute_fields === NULL) {
+    $attribute_fields = array();
+
+    foreach (field_info_instances('commerce_product', $type) as $field_name => $instance) {
+      if (commerce_cart_field_instance_is_attribute($instance)) {
+        $attribute_fields[$type][] = $field_name;
+      }
+    }
+  }
+
+  return isset($attribute_fields[$type]) ? $attribute_fields[$type] : array();
+}
+
+/**
+ * Builds a string of product attributes for display in a label.
+ *
+ * @param object $product
+ *   The product to generate the attribute string for.
+ *
+ * @return string
+ */
+function commerce_pos_label_attributes_string($product) {
+  $attribute_fields = commerce_pos_label_attribute_fields($product->type);
+  $attribute_string = '';
+
+  if (!empty($attribute_fields)) {
+    $attributes = array();
+    $wrapper = entity_metadata_wrapper('commerce_product', $product);
+
+    foreach ($attribute_fields as $field_name) {
+      $field_info = field_info_instance('commerce_product', $field_name, $product->type);
+
+      if (isset($wrapper->{$field_name}) && $attribute_value = $wrapper->{$field_name}->value()) {
+        if (is_array($attribute_value)) {
+          $attribute_value = implode(', ', $attribute_value);
+        }
+
+        $attributes[] = $field_info['label'] . ': ' . $attribute_value;
+      }
+    }
+
+    $attribute_string = implode(', ', $attributes);
+  }
+
+  return $attribute_string;
+}
diff --git a/modules/label/composer.json b/modules/label/composer.json
new file mode 100644
index 0000000..c432abc
--- /dev/null
+++ b/modules/label/composer.json
@@ -0,0 +1,5 @@
+{
+    "require": {
+        "picqer/php-barcode-generator": "^0.2.0"
+    }
+}
diff --git a/modules/label/css/commerce_pos_label.css b/modules/label/css/commerce_pos_label.css
new file mode 100644
index 0000000..481ed14
--- /dev/null
+++ b/modules/label/css/commerce_pos_label.css
@@ -0,0 +1,3 @@
+@page{margin:2mm 0 0 0;padding:0}.commerce-pos-label{font-family:Arial, sans-serif;margin:0 auto;text-align:center;font-size:12px;text-transform:uppercase}@media print{.commerce-pos-label{page-break-after:always}}.commerce-pos-label .barcode{margin:0.5em 0}.commerce-pos-label .description{font-size:0.85em}.commerce-pos-label.no-description .barcode{margin:1em 0}.commerce-pos-label div{white-space:nowrap;width:100%;overflow:hidden;-ms-text-overflow:ellipsis;text-overflow:ellipsis}.commerce-pos-label div.price{font-size:2em}
+
+/*# sourceMappingURL=commerce_pos_label.css.map */
diff --git a/modules/label/css/commerce_pos_label.css.map b/modules/label/css/commerce_pos_label.css.map
new file mode 100644
index 0000000..d2ee728
--- /dev/null
+++ b/modules/label/css/commerce_pos_label.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"commerce_pos_label.css","sources":["commerce_pos_label.scss"],"sourcesContent":["@page {\n  margin: 2mm 0 0 0;\n  padding: 0;\n}\n\n.commerce-pos-label {\n  @media print {\n    page-break-after: always;\n  }\n\n  font-family: Arial, sans-serif;\n  margin: 0 auto;\n  text-align: center;\n  font-size: 12px;\n  text-transform: uppercase;\n\n  .barcode {\n    margin: 0.5em 0;\n  }\n\n  .description {\n    font-size: 0.85em;\n  }\n\n  &.no-description {\n    .barcode {\n      margin: 1em 0 ;\n    }\n  }\n\n  div {\n    white-space: nowrap;\n    width: 100%;\n    overflow: hidden;\n    -ms-text-overflow: ellipsis;\n    text-overflow: ellipsis;\n\n    &.price {\n      font-size: 2em;\n    }\n  }\n}\n"],"mappings":"AAAA,KAAK,CACH,MAAM,CAAE,SAAU,CAClB,OAAO,CAAE,CAAE,CAGb,AAAA,mBAAmB,AAAC,CAKlB,WAAW,CAAE,iBAAkB,CAC/B,MAAM,CAAE,MAAO,CACf,UAAU,CAAE,MAAO,CACnB,SAAS,CAAE,IAAK,CAChB,cAAc,CAAE,SAAU,CA2B3B,AAnCC,MAAM,CAAN,KAAK,CADP,AAAA,mBAAmB,AAAC,CAEhB,gBAAgB,CAAE,MAAO,CAkC5B,CApCD,AAWE,mBAXiB,CAWjB,QAAQ,AAAC,CACP,MAAM,CAAE,OAAQ,CACjB,AAbH,AAeE,mBAfiB,CAejB,YAAY,AAAC,CACX,SAAS,CAAE,MAAO,CACnB,AAjBH,AAoBI,mBApBe,AAmBhB,eAAe,CACd,QAAQ,AAAC,CACP,MAAM,CAAE,KAAO,CAChB,AAtBL,AAyBE,mBAzBiB,CAyBjB,GAAG,AAAC,CACF,WAAW,CAAE,MAAO,CACpB,KAAK,CAAE,IAAK,CACZ,QAAQ,CAAE,MAAO,CACjB,iBAAiB,CAAE,QAAS,CAC5B,aAAa,CAAE,QAAS,CAKzB,AAnCH,AAyBE,mBAzBiB,CAyBjB,GAAG,AAOA,MAAM,AAAC,CACN,SAAS,CAAE,GAAI,CAChB","names":[],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/modules/label/includes/commerce_pos_label.admin.inc b/modules/label/includes/commerce_pos_label.admin.inc
new file mode 100644
index 0000000..33b39da
--- /dev/null
+++ b/modules/label/includes/commerce_pos_label.admin.inc
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * @file
+ * Admin form and page callbacks for Commerce POS Labels.
+ */
+
+function commerce_pos_label_labels_form_page() {
+  global $user;
+  $page = array();
+
+  $page['#attached']['library'][] = array(
+    'system',
+    'ui.autocomplete',
+  );
+
+  $page['#attached']['js'][] = array(
+    'type' => 'setting',
+    'data' => array(
+      'commercePosSale' => array(
+        'productAutoCompleteUrl' => url('admin/commerce/pos/product/autocomplete'),
+        'autoCompleteCallback' => 'autocompleteCallback',
+        'autoCompleteNamespace' => 'commercePosLabel',
+      ),
+    ),
+  );
+
+  $page['#attached']['js'][] = drupal_get_path('module', 'commerce_pos') . '/js/keynavigator.js';
+  $page['#attached']['css'][] = drupal_get_path('module', 'commerce_pos') . '/css/commerce_pos_style.css';
+  $page['#attached']['js'][] = drupal_get_path('module', 'commerce_pos') . '/js/commerce_pos.transaction.js';
+
+  $page['header'] = array(
+    '#markup' => theme('commerce_pos_header', array('account' => $user)),
+  );
+  $page['form'] = drupal_get_form('commerce_pos_label_labels_form');
+
+  return $page;
+}
+
+/**
+ * Callback for the print labels form.
+ */
+function commerce_pos_label_labels_form($form, &$form_state, $product = FALSE) {
+  $format_options = array();
+
+  foreach (commerce_pos_label_get_label_formats() as $name => $format) {
+    $format_options[$name] = $format['title'];
+  }
+
+  // We need at least 1 label format to proceed.
+  if (empty($format_options)) {
+    drupal_set_message(t('There are no available label formats. Please enable at least one POS label format module.'), 'error');
+    return $form;
+  }
+
+  if (!isset($form_state['labels_to_create'])) {
+    $form_state['labels_to_create'] = array();
+
+    if ($product !== FALSE) {
+      $form_state['labels_to_create'][$product->product_id] = commerce_pos_label_labels_form_build_info_array($product);
+    }
+  }
+
+  $form_wrapper_id = 'commerce-pos-label-form-container';
+
+  $form['#prefix'] = '<div id="' . $form_wrapper_id . '">';
+  $form['#suffix'] = '</div>';
+
+  $form['label_format'] = array(
+    '#type' => 'select',
+    '#title' => t('Label format'),
+    '#options' => $format_options,
+    '#required' => TRUE,
+    '#default_value' => variable_get('commerce_pos_label_default_format', NULL),
+  );
+
+  if ($product === FALSE) {
+    $form['product_search'] = array(
+      '#type' => 'textfield',
+      '#title' => 'product search',
+      '#title_display' => 'invisible',
+      '#size' => 60,
+      '#description' => 'Search by SKU or product title, start typing to begin your search.',
+      '#attributes' => array(
+        'class' => array(
+          'commerce-pos-product-autocomplete',
+          'commerce-pos-product-search',
+        ),
+        'placeholder' => 'Product Search',
+      ),
+    );
+
+    $form['product_search_add'] = array(
+      '#type' => 'submit',
+      '#value' => t('Add'),
+      '#validate' => array('commerce_pos_label_labels_form_add_validate'),
+      '#submit' => array('commerce_pos_label_labels_form_add_submit'),
+      '#attributes' => array(
+        'class' => array('commerce-pos-label-btn-add'),
+      ),
+      '#ajax' => array(
+        'wrapper' => $form_wrapper_id,
+        'callback' => 'commerce_pos_label_labels_form_add_js',
+      ),
+    );
+  }
+
+  $form['label_options'] = array(
+    '#type' => 'container',
+    '#id' => 'commerce-pos-label-label-options-container',
+  );
+
+  $form['label_options']['label_list'] = array(
+    '#type' => 'container',
+    '#tree' => TRUE,
+  );
+
+  if (!empty($form_state['labels_to_create'])) {
+    foreach ($form_state['labels_to_create'] as $product_id => $label_info) {
+      $form['label_options']['label_list'][$product_id]['title'] = array(
+        '#title' => t('Title'),
+        '#type' => 'textfield',
+        '#required' => TRUE,
+        '#default_value' => $label_info['title'],
+      );
+
+      $form['label_options']['label_list'][$product_id]['description'] = array(
+        '#title' => t('Description'),
+        '#type' => 'textfield',
+        '#default_value' => $label_info['description'],
+      );
+
+      $form['label_options']['label_list'][$product_id]['quantity'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Quantity'),
+        '#default_value' => $label_info['quantity'],
+        '#required' => TRUE,
+        '#size' => 5,
+      );
+
+      $form['label_options']['label_list'][$product_id]['price'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Price'),
+        '#default_value' => $label_info['price'],
+        '#required' => TRUE,
+        '#size' => 5,
+      );
+
+      if ($product === FALSE) {
+        $form['label_options']['label_list'][$product_id]['remove'] = array(
+          '#type' => 'submit',
+          '#value' => t('Remove'),
+          '#name' => 'remove-' . $product_id,
+          '#product_id' => $product_id,
+          '#submit' => array('commerce_pos_label_labels_form_remove_submit'),
+          '#ajax' => array(
+            'callback' => 'commerce_pos_labels_remove_js',
+            'wrapper' => 'commerce-pos-label-label-options-container',
+          ),
+        );
+      }
+    }
+
+    $form['label_options']['print_labels'] = array(
+      '#type' => 'submit',
+      '#value' => t('Print'),
+      '#submit' => array('commerce_pos_label_labels_form_print_submit'),
+      '#ajax' => array(
+        'wrapper' => $form_wrapper_id,
+        'callback' => 'commerce_pos_labels_labels_print_js',
+      ),
+    );
+  }
+
+  // Attach our CSS and JS
+  $form['#attached']['library'][] = array(
+    'system',
+    'ui.autocomplete',
+  );
+
+  $form['#attached']['libraries_load'][] = array('jquery-print');
+  $form['#attached']['js'][] = drupal_get_path('module', 'commerce_pos_label') . '/js/commerce_pos_label.js';
+
+  return $form;
+}
+
+/**
+ * Validation handler for the Label form's "Add" button.
+ */
+function commerce_pos_label_labels_form_add_validate($form, &$form_state) {
+  $valid_product_id = FALSE;
+
+  if (!empty($form_state['input']['product_search'])) {
+    $searched_sku = $form_state['input']['product_search'];
+    // Do a quick check and make sure the product ID exists.
+    $product_id = db_query('SELECT product_id FROM {commerce_product} WHERE sku = :sku', array(':sku' => $searched_sku))->fetchField();
+
+    if ($product_id) {
+      $valid_product_id = TRUE;
+      $form_state['product_id_to_add'] = $product_id;
+      form_set_value($form['product_search'], '', $form_state);
+      unset($form_state['input']['product_search']);
+    }
+  }
+
+  if (!$valid_product_id) {
+    form_error($form['product_search'], t('Invalid product.'));
+  }
+}
+
+/**
+ * Submit handler for the label form's "Remove' buttons.
+ */
+function commerce_pos_label_labels_form_remove_submit($form, &$form_state) {
+  $product_id = $form_state['triggering_element']['#product_id'];
+  unset($form_state['labels_to_create'][$product_id]);
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * AJAX callback for the label form's "Add" button.
+ */
+function commerce_pos_label_labels_form_add_js($form, &$form_state) {
+  return $form;
+}
+
+/**
+ * Submit handler for the label form's "Add" button.
+ */
+function commerce_pos_label_labels_form_add_submit($form, &$form_state) {
+  if (!empty($form_state['product_id_to_add'])) {
+    $product = commerce_product_load($form_state['product_id_to_add']);
+    $form_state['labels_to_create'][$form_state['product_id_to_add']] = commerce_pos_label_labels_form_build_info_array($product);
+    unset($form_state['product_id_to_add']);
+    $form_state['rebuild'] = TRUE;
+  }
+}
+
+/**
+ * Build an array of product info used for printing labels via the labels form.
+ *
+ * @param object $product
+ *   The product to create the array for.
+ *
+ * @return array
+ *   The info array.
+ */
+function commerce_pos_label_labels_form_build_info_array($product) {
+  $default_price = 0;
+
+  if ($price = field_get_items('commerce_product', $product, 'commerce_price')) {
+    if (!empty($price[0]['amount'])) {
+      $default_price = commerce_currency_amount_to_decimal($price[0]['amount'], $price[0]['currency_code']);
+    }
+  }
+
+  return array(
+    'title' => $product->title,
+    'quantity' => 1,
+    'price' => $default_price,
+    'description' => commerce_pos_label_attributes_string($product),
+  );
+}
+
+/**
+ * Submit handler for the label form's "Print" button.
+ */
+function commerce_pos_label_labels_form_print_submit($form, &$form_state) {
+  $format = $form_state['values']['label_format'];
+
+  $form_state['storage']['labels_to_print'] = array(
+    'format' => $format,
+    'products' => array(),
+  );
+
+  foreach ($form_state['values']['label_list'] as $product_id => $values) {
+    $form_state['storage']['labels_to_print']['products'][$product_id] = $values;
+  }
+}
+
+/**
+ * AJAX handler for the label form's "Remove" buttons.
+ */
+function commerce_pos_labels_remove_js($form, &$form_state) {
+  return $form['label_options'];
+}
+
+/**
+ * AJAX handler for the label form's "Print" button.
+ */
+function commerce_pos_labels_labels_print_js($form, &$form_state) {
+  $commands = array();
+
+  if (!form_get_errors()) {
+    if (!empty($form_state['storage']['labels_to_print'])) {
+      $format = &$form_state['storage']['labels_to_print']['format'];
+
+      if (!empty($format['css'])) {
+        $css_url = $format['css'];
+
+      }
+      else {
+        $css_url = url(drupal_get_path('module', 'commerce_pos_label') . '/css/commerce_pos_label.css', array(
+          'absolute' => TRUE,
+        ));
+      }
+
+      $commands[] = array(
+        'command' => 'printLabels',
+        'cssUrl' => $css_url,
+        'content' => theme('commerce_pos_label_print', array(
+          'product_ids' => $form_state['storage']['labels_to_print']['products'],
+          'format' => $form_state['values']['label_format'],
+        )),
+      );
+    }
+  }
+
+  unset($form_state['storage']['labels_to_print']);
+
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+
+/**
+ * Callback for the label settings form.
+ */
+function commerce_pos_label_settings_form($form, &$form_state) {
+  $format_options = array();
+
+  foreach (commerce_pos_label_get_label_formats() as $name => $format) {
+    $format_options[$name] = $format['title'];
+  }
+
+  $form['commerce_pos_label_default_format'] = array(
+    '#type' => 'select',
+    '#title' => t('Default format'),
+    '#required' => TRUE,
+    '#options' => $format_options,
+    '#default_value' => variable_get('commerce_pos_label_default_format', NULL),
+  );
+
+  return system_settings_form($form);
+}
+
diff --git a/modules/label/includes/commerce_pos_label.ajax.inc b/modules/label/includes/commerce_pos_label.ajax.inc
new file mode 100644
index 0000000..4efde64
--- /dev/null
+++ b/modules/label/includes/commerce_pos_label.ajax.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * AJAX callbacks for Commerce POS Labels.
+ */
+
+/**
+ * Callback for the label product search.
+ */
+function commerce_pos_label_search_autocomplete($keywords) {
+  $products = array();
+
+  module_load_include('inc', 'commerce_pos', 'includes/commerce_pos.common');
+
+  if ($search_results = commerce_pos_product_search_basic($keywords)) {
+    foreach ($search_results as $product_id) {
+      // @TODO: try to not load the products
+      if ($product = commerce_product_load($product_id)) {
+        $products[$product_id] = $product->title;
+      }
+    }
+  }
+
+  drupal_json_output($products);
+}
diff --git a/modules/label/js/commerce_pos_label.js b/modules/label/js/commerce_pos_label.js
new file mode 100644
index 0000000..13c565e
--- /dev/null
+++ b/modules/label/js/commerce_pos_label.js
@@ -0,0 +1,18 @@
+(function ($) {
+  Drupal.commercePosLabel = Drupal.commercePosLabel || {};
+
+  Drupal.commercePosLabel.autocompleteCallback = function(sku) {
+    $('.commerce-pos-product-autocomplete').val(sku);
+    $('.commerce-pos-label-btn-add').mousedown();
+  };
+
+  if (Drupal.ajax) {
+    Drupal.ajax.prototype.commands.printLabels = function (ajax, response, status) {
+      $(response.content).print({
+        globalStyles: false,
+        mediaPrint: true,
+        stylesheet: response.cssUrl
+      });
+    };
+  }
+}(jQuery));
diff --git a/modules/label/sass/commerce_pos_label.scss b/modules/label/sass/commerce_pos_label.scss
new file mode 100644
index 0000000..58d7443
--- /dev/null
+++ b/modules/label/sass/commerce_pos_label.scss
@@ -0,0 +1,42 @@
+@page {
+  margin: 2mm 0 0 0;
+  padding: 0;
+}
+
+.commerce-pos-label {
+  @media print {
+    page-break-after: always;
+  }
+
+  font-family: Arial, sans-serif;
+  margin: 0 auto;
+  text-align: center;
+  font-size: 12px;
+  text-transform: uppercase;
+
+  .barcode {
+    margin: 0.5em 0;
+  }
+
+  .description {
+    font-size: 0.85em;
+  }
+
+  &.no-description {
+    .barcode {
+      margin: 1em 0 ;
+    }
+  }
+
+  div {
+    white-space: nowrap;
+    width: 100%;
+    overflow: hidden;
+    -ms-text-overflow: ellipsis;
+    text-overflow: ellipsis;
+
+    &.price {
+      font-size: 2em;
+    }
+  }
+}
diff --git a/modules/label/tests/commerce_pos_label.test b/modules/label/tests/commerce_pos_label.test
new file mode 100644
index 0000000..e3a5d11
--- /dev/null
+++ b/modules/label/tests/commerce_pos_label.test
@@ -0,0 +1,106 @@
+<?php
+
+class CommercePosLabelTestCase extends CommercePosBaseTestCase {
+
+  protected $adminUser;
+  protected $privilegedUser;
+
+  /**
+   * GetInfo().
+   *
+   * @{inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Commerce POS Label Tests',
+      'description' => 'Ensure Commerce POS Label functions properly',
+      'group' => 'Commerce POS',
+    );
+  }
+
+  /**
+   * Setup().
+   *
+   * @{inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp(array('commerce_pos_label'));
+
+    $this->adminUser = $this->drupalCreateUser(array(
+      'commerce pos print labels',
+      'administer commerce pos labels',
+    ));
+
+    $this->privilegedUser = $this->drupalCreateUser(array(
+      'commerce pos print labels',
+    ));
+  }
+
+  public function testLabelFormMenu() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/commerce/pos/labels');
+    $this->assertResponse(200, 'Admin user should be able to access labels tab.');
+
+    $this->drupalLogin($this->privilegedUser);
+    $this->drupalGet('admin/commerce/pos/labels');
+    $this->assertResponse(200, 'Privileged user should be able to access labels tab.');
+  }
+
+  public function testLabelFormErrors() {
+    $this->drupalLogin($this->privilegedUser);
+    $this->drupalGet('admin/commerce/pos/labels');
+
+    $this->assertFieldByName('product_search', '', 'Should have a product search input.');
+    $this->assertFieldByName('label_format', '', 'Should have a label format select box.');
+
+    $this->drupalPost('admin/commerce/pos/labels', array(), t('Add'));
+
+    $this->assertText(t('Label format field is required.'), 'Should have label format error.');
+    $this->assertText(t('Invalid product.'), 'Should have invalid product error.');
+  }
+
+  public function testLabelFormAdd() {
+    $this->drupalLogin($this->privilegedUser);
+    $product = $this->createDummyProduct();
+
+    $formats = commerce_pos_label_get_label_formats();
+
+    $this->assertTrue(!empty($formats), 'Should have label formats available.');
+
+    $edit = array(
+      'label_format' => key($formats),
+      'product_search' => $product->sku,
+    );
+
+    $this->drupalPost('admin/commerce/pos/labels', $edit, t('Add'));
+
+    $this->assertFieldByName('label_list[' . $product->product_id . '][title]', $product->title, 'Should have a title field with product title.');
+    $this->assertFieldByName('label_list[' . $product->product_id . '][quantity]', 1, 'Should have a quantity field.');
+
+    $expected_price = commerce_currency_amount_to_decimal($product->commerce_price[LANGUAGE_NONE][0]['amount'], $product->commerce_price[LANGUAGE_NONE][0]['currency_code']);
+    $this->assertFieldByName('label_list[' . $product->product_id . '][price]', $expected_price, 'Should have a price field.');
+
+    $this->assertFieldById('edit-print-labels', t('Print'), 'Should have a print button.');
+  }
+
+  public function testLabelTab() {
+    $product = $this->createDummyProduct();
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/commerce/products/' . $product->product_id . '/labels');
+    $this->assertResponse(200, 'Admin user should be able to access labels tab.');
+
+    $this->drupalLogin($this->privilegedUser);
+    $this->drupalGet('admin/commerce/products/' . $product->product_id . '/labels');
+    $this->assertResponse(200, 'Privileged user should be able to access labels tab.');
+
+    $this->assertNoFieldByName('product_search', '', 'Should not have a product search field.');
+
+    $this->assertFieldByName('label_list[' . $product->product_id . '][title]', $product->title, 'Should have a title field with product title.');
+    $this->assertFieldByName('label_list[' . $product->product_id . '][quantity]', 1, 'Should have a quantity field.');
+
+    $expected_price = commerce_currency_amount_to_decimal($product->commerce_price[LANGUAGE_NONE][0]['amount'], $product->commerce_price[LANGUAGE_NONE][0]['currency_code']);
+    $this->assertFieldByName('label_list[' . $product->product_id . '][price]', $expected_price, 'Should have a price field.');
+
+    $this->assertFieldById('edit-print-labels', t('Print'), 'Should have a print button.');
+  }
+}
diff --git a/modules/label/theme/commerce-pos-label.tpl.php b/modules/label/theme/commerce-pos-label.tpl.php
new file mode 100644
index 0000000..e435546
--- /dev/null
+++ b/modules/label/theme/commerce-pos-label.tpl.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Default template POS labels.
+ */
+?>
+<div class="<?php print $classes; ?>" <?php print $attributes; ?>>
+  <div class="title">
+    <?php print $title; ?>
+  </div>
+  <?php if ($description) { ?>
+  <div class="description">
+    <?php print $description; ?>
+  </div>
+  <?php } ?>
+  <?php if ($barcode) { ?>
+    <div class="barcode">
+      <img src="data:image/png;base64, <?php print $barcode; ?>">
+    </div>
+  <?php } ?>
+  <div class="price">
+    <?php print $price; ?>
+  </div>
+</div>
diff --git a/modules/label/theme/commerce_pos_label.theme.inc b/modules/label/theme/commerce_pos_label.theme.inc
new file mode 100644
index 0000000..2aaab59
--- /dev/null
+++ b/modules/label/theme/commerce_pos_label.theme.inc
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Theme callbacks for Commerce POS Labels.
+ */
+
+/**
+ * Theme callback for printing labels.
+ */
+function theme_commerce_pos_label_print(&$variables) {
+  $output = '<div>';
+  $format = commerce_pos_label_format_load($variables['format']);
+
+  foreach ($variables['product_ids'] as $product_id => $info) {
+    $product = commerce_product_load($product_id);
+    $barcode = commerce_pos_label_generate_barcode($product, $format);
+
+    if ($barcode) {
+      $barcode = base64_encode($barcode);
+    }
+
+    $label = theme('commerce_pos_label', array(
+      'product' => $product,
+      'title' => $info['title'],
+      'price' => $info['price'],
+      'barcode' => $barcode,
+      'format' => $format,
+      'description' => $info['description'],
+    ));
+
+    for ($i = 0; $i < $info['quantity']; $i ++) {
+      $output .= $label;
+    }
+  }
+
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Theme callback for the POS labels form.
+ */
+function theme_commerce_pos_label_labels_form(&$variables) {
+  return drupal_render_children($variables['form']);
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ */
+function commerce_pos_label_preprocess_commerce_pos_label(&$variables) {
+  $variables['theme_hook_suggestions'][] = $variables['format']['name'];
+  $format = &$variables['format'];
+
+  $variables['attributes_array']['style'] = "width: {$format['dimensions']['width']}in; max-height: {$format['dimensions']['height']};";
+
+  $product = &$variables['product'];
+  if ($price = field_get_items('commerce_product', $product, 'commerce_price')) {
+    $variables['price'] = commerce_currency_format($variables['price'], $price[0]['currency_code'], NULL, FALSE);
+  }
+
+  if (empty($variables['description'])) {
+    $variables['classes_array'][] = 'no-description';
+  }
+
+  if (empty($variables['barcode'])) {
+    $variables['classes_array'][] = 'no-barcode';
+  }
+}
+
+/**
+ * Implements template_preprocess_commerce_pos_label_labels_form().
+ */
+function commerce_pos_label_preprocess_commerce_pos_label_labels_form(&$variables) {
+  $form = &$variables['form'];
+
+  if (!empty($form['label_options']['label_list'])) {
+    $rows = array();
+    $header = array();
+
+    foreach (element_children($form['label_options']['label_list']) as $key) {
+      $elements = &$form['label_options']['label_list'][$key];
+      $row = array();
+      $build_header = empty($header);
+
+      foreach (element_children($elements) as $element_key) {
+        $elements[$element_key]['#title_display'] = 'invisible';
+        $row[] = render($elements[$element_key]);
+
+        if ($build_header) {
+          $header[] = isset($elements[$element_key]['#title']) ? $elements[$element_key]['#title'] : '';
+        }
+      }
+
+      $rows[] = $row;
+    }
+
+    $form['label_options']['label_list']['table'] = array(
+      '#theme' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+    );
+  }
+}
diff --git a/modules/receipt/commerce_pos_receipt.info b/modules/receipt/commerce_pos_receipt.info
index 572cef6..18ff18d 100644
--- a/modules/receipt/commerce_pos_receipt.info
+++ b/modules/receipt/commerce_pos_receipt.info
@@ -9,7 +9,7 @@ dependencies[] = commerce_pos
 dependencies[] = libraries
 dependencies[] = message
 dependencies[] = message_notify
-
+dependencies[] = jquery_print
 
 ; Information added by Drupal.org packaging script on 2016-08-08
 version = "7.x-2.0-beta6+2-dev"
diff --git a/modules/receipt/commerce_pos_receipt.module b/modules/receipt/commerce_pos_receipt.module
index 55b8248..02995ed 100644
--- a/modules/receipt/commerce_pos_receipt.module
+++ b/modules/receipt/commerce_pos_receipt.module
@@ -6,25 +6,6 @@
  */
 
 /**
- * Implements hook_libraries_info().
- */
-function commerce_pos_receipt_libraries_info() {
-  $libraries['jquery-print'] = array(
-    'name' => 'jQuery Print Plugin',
-    'vendor url' => 'https://github.com/DoersGuild/jQuery.print',
-    'version arguments' => array(
-      'file' => 'jQuery.print.js',
-      'pattern' => '/version\s+([0-9a-zA-Z\.-]+)/',
-    ),
-    'files' => array(
-      'js' => array('jQuery.print.js'),
-    ),
-  );
-
-  return $libraries;
-}
-
-/**
  * Implements hook_theme().
  */
 function commerce_pos_receipt_theme($existing, $type, $theme, $path) {
