diff --git a/composer.json b/composer.json
index fac6c53..9b1cc88 100644
--- a/composer.json
+++ b/composer.json
@@ -28,7 +28,8 @@
           "h5p-development.class.php",
           "h5p-file-storage.interface.php",
           "h5p-default-storage.class.php",
-          "h5p-event-base.class.php"
+          "h5p-event-base.class.php",
+          "h5p-metadata.class.php"
         ]
     }
 }
diff --git a/h5p-development.class.php b/h5p-development.class.php
index 6c891f9..4188d88 100644
--- a/h5p-development.class.php
+++ b/h5p-development.class.php
@@ -86,6 +86,9 @@ class H5PDevelopment {
 
       // Save/update library.
       $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
+      if (!isset($library['metadata'])) {
+        $library['metadata'] = 0;
+      }
       $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
 
       $library['path'] = 'development/' . $contents[$i];
diff --git a/h5p-metadata.class.php b/h5p-metadata.class.php
new file mode 100644
index 0000000..efff0cf
--- /dev/null
+++ b/h5p-metadata.class.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Utility class for handling metadata
+ */
+abstract class H5PMetadata {
+
+  const FIELDS = array(
+    'title' => array(
+      'type' => 'text',
+      'maxLength' => 255
+    ),
+    'authors' => array(
+      'type' => 'json'
+    ),
+    'changes' => array(
+      'type' => 'json'
+    ),
+    'source' => array(
+      'type' => 'text',
+      'maxLength' => 255
+    ),
+    'license' => array(
+      'type' => 'text',
+      'maxLength' => 32
+    ),
+    'licenseVersion' => array(
+      'type' => 'text',
+      'maxLength' => 10
+    ),
+    'licenseExtras' => array(
+      'type' => 'text',
+      'maxLength' => 5000
+    ),
+    'authorComments' => array(
+      'type' => 'text',
+      'maxLength' => 5000
+    ),
+    'yearFrom' => array(
+      'type' => 'int'
+    ),
+    'yearTo' => array(
+      'type' => 'int'
+    )
+  );
+
+  /**
+   * JSON encode metadata
+   *
+   * @param object $content
+   * @return string
+   */
+  public static function toJSON($content) {
+    // Note: deliberatly creating JSON string "manually" to improve performance
+    return
+      '{"title":' . (isset($content->title) ? json_encode($content->title) : 'null') .
+      ',"authors":' . (isset($content->authors) ? $content->authors : 'null') .
+      ',"source":' . (isset($content->source) ? '"' . $content->source . '"' : 'null') .
+      ',"license":' . (isset($content->license) ? '"' . $content->license . '"' : 'null') .
+      ',"licenseVersion":' . (isset($content->license_version) ? '"' . $content->license_version . '"' : 'null') .
+      ',"licenseExtras":' . (isset($content->license_extras) ? json_encode($content->license_extras) : 'null') .
+      ',"yearFrom":' . (isset($content->year_from) ? $content->year_from : 'null') .
+      ',"yearTo":' .  (isset($content->year_to) ? $content->year_to : 'null') .
+      ',"changes":' . (isset($content->changes) ? $content->changes : 'null') .
+      ',"authorComments":' . (isset($content->author_comments) ? json_encode($content->author_comments) : 'null') . '}';
+  }
+
+
+  /**
+   * Make the metadata into an associative array keyed by the property names
+   * @param mixed $metadata Array or object containing metadata
+   * @param bool $include_title
+   * @param array $types
+   * @return array
+   */
+  public static function toDBArray($metadata, $include_title = true, &$types = array()) {
+    $fields = array();
+
+    if (!is_array($metadata)) {
+      $metadata = (array) $metadata;
+    }
+
+    foreach (self::FIELDS as $key => $config) {
+
+      if ($key === 'title' && !$include_title) {
+        continue;
+      }
+
+      if (isset($metadata[$key])) {
+        $value = $metadata[$key];
+        $db_field_name = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $key));
+
+        switch ($config['type']) {
+          case 'text':
+            if (strlen($value) > $config['maxLength']) {
+              $value = mb_substr($value, 0, $config['maxLength']);
+            }
+            $types[] = '%s';
+            break;
+
+          case 'int':
+            $value = ($value !== null) ? intval($value): null;
+            $types[] = '%i';
+            break;
+
+          case 'json':
+            $value = json_encode($value);
+            $types[] = '%s';
+            break;
+        }
+
+        $fields[$db_field_name] = $value;
+      }
+    }
+
+    return $fields;
+  }
+}
diff --git a/h5p.classes.php b/h5p.classes.php
index d9b4e4b..659f224 100644
--- a/h5p.classes.php
+++ b/h5p.classes.php
@@ -101,6 +101,21 @@ interface H5PFrameworkInterface {
    */
   public function getUploadedH5pPath();
 
+  /**
+   * Load addon libraries
+   *
+   * @return array
+   */
+  public function loadAddons();
+
+  /**
+   * Load config for libraries
+   *
+   * @param array $libraries
+   * @return array
+   */
+  public function getLibraryConfig($libraries = NULL);
+
   /**
    * Get a list of the current installed libraries
    *
@@ -195,6 +210,7 @@ interface H5PFrameworkInterface {
    *   - minorVersion: The library's minorVersion
    *   - patchVersion: The library's patchVersion
    *   - runnable: 1 if the library is a content type, 0 otherwise
+   *   - metadata: 1 if the library should support setting metadata (copyright etc)
    *   - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise
    *   - embedTypes(optional): list of supported embed types
    *   - preloadedJs(optional): list of associative arrays containing:
@@ -665,6 +681,7 @@ class H5PValidator {
     'author' => '/^.{1,255}$/',
     'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT|GPL1|GPL2|GPL3|MPL|MPL2)$/',
     'description' => '/^.{1,}$/',
+    'metadata' => '/^(0|1)$/',
     'dynamicDependencies' => array(
       'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
       'majorVersion' => '/^[0-9]{1,5}$/',
@@ -1424,6 +1441,9 @@ class H5PStorage {
       $library['saveDependencies'] = TRUE;
 
       // Save library meta data
+      if (!isset($library['metadata'])) {
+        $library['metadata'] = 0;
+      }
       $this->h5pF->saveLibraryData($library, $new);
 
       // Save library folder
@@ -1642,7 +1662,7 @@ Class H5PExport {
               $library['minorVersion']
           );
 
-          if ($isDevLibrary !== NULL) {
+          if ($isDevLibrary !== NULL && isset($library['path'])) {
             $exportFolder = "/" . $library['path'];
           }
         }
@@ -1806,7 +1826,7 @@ class H5PCore {
 
   public static $coreApi = array(
     'majorVersion' => 1,
-    'minorVersion' => 16
+    'minorVersion' => 19
   );
   public static $styles = array(
     'styles/h5p.css',
@@ -1919,6 +1939,10 @@ class H5PCore {
     $content = $this->h5pF->loadContent($id);
 
     if ($content !== NULL) {
+      // Validate main content's metadata
+      $validator = new H5PContentValidator($this->h5pF, $this);
+      $validator->validateMetadata($content['metadata']);
+
       $content['library'] = array(
         'id' => $content['libraryId'],
         'name' => $content['libraryName'],
@@ -1971,6 +1995,25 @@ class H5PCore {
     }
     $validator->validateLibrary($params, (object) array('options' => array($params->library)));
 
+    // Handle addons:
+    $addons = $this->h5pF->loadAddons();
+    foreach ($addons as $addon) {
+      $add_to = json_decode($addon['addTo']);
+
+      if (isset($add_to->content->types)) {
+        foreach($add_to->content->types as $type) {
+
+          if (isset($type->text->regex) &&
+              $this->textAddonMatches($params->params, $type->text->regex)) {
+            $validator->addon($addon);
+
+            // An addon shall only be added once
+            break;
+          }
+        }
+      }
+    }
+
     $params = json_encode($params->params);
 
     // Update content dependencies.
@@ -2003,6 +2046,75 @@ class H5PCore {
     return $params;
   }
 
+  /**
+   * Retrieve a value from a nested mixed array structure.
+   *
+   * @param Array $params Array to be looked in.
+   * @param String $path Supposed path to the value.
+   * @param String [$delimiter='.'] Property delimiter within the path.
+   * @return Object|NULL The object found or NULL.
+   */
+  private function retrieveValue ($params, $path, $delimiter='.') {
+    $path = explode($delimiter, $path);
+
+    // Property not found
+    if (!isset($params[$path[0]])) {
+      return NULL;
+    }
+
+    $first = $params[$path[0]];
+
+    // End of path, done
+    if (sizeof($path) === 1) {
+      return $first;
+    }
+
+    // We cannot go deeper
+    if (!is_array($first)) {
+      return NULL;
+    }
+
+    // Regular Array
+    if (isset($first[0])) {
+      foreach($first as $number => $object) {
+        $found = $this->retrieveValue($object, implode($delimiter, array_slice($path, 1)));
+        if (isset($found)) {
+          return $found;
+        }
+      }
+      return NULL;
+    }
+
+    // Associative Array
+    return $this->retrieveValue($first, implode('.', array_slice($path, 1)));
+  }
+
+  /**
+   * Determine if params contain any match.
+   *
+   * @param {object} params - Parameters.
+   * @param {string} [pattern] - Regular expression to identify pattern.
+   * @param {boolean} [found] - Used for recursion.
+   * @return {boolean} True, if params matches pattern.
+   */
+  private function textAddonMatches($params, $pattern, $found = false) {
+    $type = gettype($params);
+    if ($type === 'string') {
+      if (preg_match($pattern, $params) === 1) {
+        return true;
+      }
+    }
+    elseif ($type === 'array' || $type === 'object') {
+      foreach ($params as $value) {
+        $found = $this->textAddonMatches($value, $pattern, $found);
+        if ($found === true) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   /**
    * Generate content slug
    *
@@ -3237,6 +3349,19 @@ class H5PContentValidator {
     $this->dependencies = array();
   }
 
+  /**
+   * Add Addon library.
+   */
+  public function addon($library) {
+    $depKey = 'preloaded-' . $library['machineName'];
+    $this->dependencies[$depKey] = array(
+      'library' => $library,
+      'type' => 'preloaded'
+    );
+    $this->nextWeight = $this->h5pC->findLibraryDependencies($this->dependencies, $library, $this->nextWeight);
+    $this->dependencies[$depKey]['weight'] = $this->nextWeight++;
+  }
+
   /**
    * Get the flat dependency tree.
    *
@@ -3246,6 +3371,21 @@ class H5PContentValidator {
     return $this->dependencies;
   }
 
+  /**
+   * Validate metadata
+   *
+   * @param array $metadata
+   */
+  public function validateMetadata(&$metadata) {
+    $semantics = $this->getMetadataSemantics();
+
+    $group = (object)$metadata;
+    $this->validateGroup($group, (object) array(
+      'type' => 'group',
+      'fields' => $semantics,
+    ), FALSE);
+  }
+
   /**
    * Validate given text value against text semantics.
    * @param $text
@@ -3434,8 +3574,17 @@ class H5PContentValidator {
       // We have a strict set of options to choose from.
       $strict = TRUE;
       $options = array();
+
       foreach ($semantics->options as $option) {
-        $options[$option->value] = TRUE;
+        // Support optgroup - just flatten options into one
+        if (isset($option->type) && $option->type === 'optgroup') {
+          foreach ($option->options as $suboption) {
+            $options[$suboption->value] = TRUE;
+          }
+        }
+        elseif (isset($option->value)) {
+          $options[$option->value] = TRUE;
+        }
       }
     }
 
@@ -3566,7 +3715,6 @@ class H5PContentValidator {
 
     if (isset($file->copyright)) {
       $this->validateGroup($file->copyright, $this->getCopyrightSemantics());
-      // TODO: We'll need to do something here about getMetadataSemantics() if we change the widgets
     }
   }
 
@@ -3754,10 +3902,17 @@ class H5PContentValidator {
       $library = $this->libraries[$value->library];
     }
 
+    // Validate parameters
     $this->validateGroup($value->params, (object) array(
       'type' => 'group',
       'fields' => $library['semantics'],
     ), FALSE);
+
+    // Validate subcontent's metadata
+    if (isset($value->metadata)) {
+      $this->validateMetadata($value->metadata);
+    }
+
     $validKeys = array('library', 'params', 'subContentId', 'metadata');
     if (isset($semantics->extraAttributes)) {
       $validKeys = array_merge($validKeys, $semantics->extraAttributes);
@@ -4155,155 +4310,152 @@ class H5PContentValidator {
       )
     );
 
-    $semantics = (object) array(
+    $semantics = array(
       (object) array(
-        'name' => 'copyright',
-        'type' => 'group',
-        'label' => $this->h5pF->t('Copyright information'),
-        'fields' => array(
+        'name' => 'title',
+        'type' => 'text',
+        'label' => $this->h5pF->t('Title'),
+        'placeholder' => 'La Gioconda'
+      ),
+      (object) array(
+        'name' => 'license',
+        'type' => 'select',
+        'label' => $this->h5pF->t('License'),
+        'default' => 'U',
+        'options' => array(
           (object) array(
-            'name' => 'title',
-            'type' => 'text',
-            'label' => $this->h5pF->t('Title'),
-            'placeholder' => 'La Gioconda'
+            'value' => 'U',
+            'label' => $this->h5pF->t('Undisclosed')
           ),
           (object) array(
-            'name' => 'license',
-            'type' => 'select',
-            'label' => $this->h5pF->t('License'),
-            'default' => 'U',
-            'options' => array(
+            'type' => 'optgroup',
+            'label' => $this->h5pF->t('Creative Commons'),
+            'options' => [
               (object) array(
-                'value' => 'U',
-                'label' => $this->h5pF->t('Undisclosed')
+                'value' => 'CC BY',
+                'label' => $this->h5pF->t('Attribution (CC BY)'),
+                'versions' => $cc_versions
               ),
               (object) array(
-                'type' => 'optgroup',
-                'label' => $this->h5pF->t('Creative Commons'),
-                'options' => [
-                  (object) array(
-                    'value' => 'CC BY',
-                    'label' => $this->h5pF->t('Attribution (CC BY)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC BY-SA',
-                    'label' => $this->h5pF->t('Attribution-ShareAlike (CC BY-SA)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC BY-ND',
-                    'label' => $this->h5pF->t('Attribution-NoDerivs (CC BY-ND)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC BY-NC',
-                    'label' => $this->h5pF->t('Attribution-NonCommercial (CC BY-NC)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC BY-NC-SA',
-                    'label' => $this->h5pF->t('Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC BY-NC-ND',
-                    'label' => $this->h5pF->t('Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)'),
-                    'versions' => $cc_versions
-                  ),
-                  (object) array(
-                    'value' => 'CC0 1.0',
-                    'label' => $this->h5pF->t('Public Domain Dedication (CC0)')
-                  ),
-                  (object) array(
-                    'value' => 'CC PDM',
-                    'label' => $this->h5pF->t('Public Domain Mark (PDM)')
-                  ),
-                ]
+                'value' => 'CC BY-SA',
+                'label' => $this->h5pF->t('Attribution-ShareAlike (CC BY-SA)'),
+                'versions' => $cc_versions
               ),
               (object) array(
-                'value' => 'GNU GPL',
-                'label' => $this->h5pF->t('General Public License v3')
+                'value' => 'CC BY-ND',
+                'label' => $this->h5pF->t('Attribution-NoDerivs (CC BY-ND)'),
+                'versions' => $cc_versions
               ),
               (object) array(
-                'value' => 'PD',
-                'label' => $this->h5pF->t('Public Domain')
+                'value' => 'CC BY-NC',
+                'label' => $this->h5pF->t('Attribution-NonCommercial (CC BY-NC)'),
+                'versions' => $cc_versions
               ),
               (object) array(
-                'value' => 'ODC PDDL',
-                'label' => $this->h5pF->t('Public Domain Dedication and Licence')
+                'value' => 'CC BY-NC-SA',
+                'label' => $this->h5pF->t('Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)'),
+                'versions' => $cc_versions
               ),
               (object) array(
-                'value' => 'C',
-                'label' => $this->h5pF->t('Copyright')
-              )
-            )
+                'value' => 'CC BY-NC-ND',
+                'label' => $this->h5pF->t('Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)'),
+                'versions' => $cc_versions
+              ),
+              (object) array(
+                'value' => 'CC0 1.0',
+                'label' => $this->h5pF->t('Public Domain Dedication (CC0)')
+              ),
+              (object) array(
+                'value' => 'CC PDM',
+                'label' => $this->h5pF->t('Public Domain Mark (PDM)')
+              ),
+            ]
           ),
           (object) array(
-            'name' => 'licenseVersion',
-            'type' => 'select',
-            'label' => $this->h5pF->t('License Version'),
-            'options' => array(),
-            'optional' => TRUE
+            'value' => 'GNU GPL',
+            'label' => $this->h5pF->t('General Public License v3')
           ),
           (object) array(
-            'name' => 'yearFrom',
-            'type' => 'number',
-            'label' => $this->h5pF->t('Years (from)'),
-            'placeholder' => '1991',
-            'min' => '-9999',
-            'max' => '9999',
-            'optional' => TRUE
+            'value' => 'PD',
+            'label' => $this->h5pF->t('Public Domain')
           ),
           (object) array(
-            'name' => 'yearTo',
-            'type' => 'number',
-            'label' => $this->h5pF->t('Years (to)'),
-            'placeholder' => '1992',
-            'min' => '-9999',
-            'max' => '9999',
-            'optional' => TRUE
+            'value' => 'ODC PDDL',
+            'label' => $this->h5pF->t('Public Domain Dedication and Licence')
           ),
           (object) array(
-            'name' => 'source',
-            'type' => 'text',
-            'label' => $this->h5pF->t('Source'),
-            'placeholder' => 'https://',
-            'optional' => TRUE
+            'value' => 'C',
+            'label' => $this->h5pF->t('Copyright')
           )
         )
       ),
       (object) array(
-        'name' => 'authorWidget',
-        'type' => 'group',
-        'fields'=> array(
-          (object) array(
-            'label' => $this->h5pF->t("Author's name"),
-            'name' => "name",
-            'optional' => TRUE,
-            'type' => "text"
-          ),
-          (object) array(
-            'name' => 'role',
-            'type' => 'select',
-            'label' => $this->h5pF->t("Author's role"),
-            'default' => 'Author',
-            'options' => array(
-              (object) array(
-                'value' => 'Author',
-                'label' => $this->h5pF->t('Author')
-              ),
-              (object) array(
-                'value' => 'Editor',
-                'label' => $this->h5pF->t('Editor')
-              ),
-              (object) array(
-                'value' => 'Licensee',
-                'label' => $this->h5pF->t('Licensee')
-              ),
-              (object) array(
-                'value' => 'Originator',
-                'label' => $this->h5pF->t('Originator')
+        'name' => 'licenseVersion',
+        'type' => 'select',
+        'label' => $this->h5pF->t('License Version'),
+        'options' => $cc_versions,
+        'optional' => TRUE
+      ),
+      (object) array(
+        'name' => 'yearFrom',
+        'type' => 'number',
+        'label' => $this->h5pF->t('Years (from)'),
+        'placeholder' => '1991',
+        'min' => '-9999',
+        'max' => '9999',
+        'optional' => TRUE
+      ),
+      (object) array(
+        'name' => 'yearTo',
+        'type' => 'number',
+        'label' => $this->h5pF->t('Years (to)'),
+        'placeholder' => '1992',
+        'min' => '-9999',
+        'max' => '9999',
+        'optional' => TRUE
+      ),
+      (object) array(
+        'name' => 'source',
+        'type' => 'text',
+        'label' => $this->h5pF->t('Source'),
+        'placeholder' => 'https://',
+        'optional' => TRUE
+      ),
+      (object) array(
+        'name' => 'authors',
+        'type' => 'list',
+        'field' => (object) array (
+          'name' => 'author',
+          'type' => 'group',
+          'fields'=> array(
+            (object) array(
+              'label' => $this->h5pF->t("Author's name"),
+              'name' => 'name',
+              'optional' => TRUE,
+              'type' => 'text'
+            ),
+            (object) array(
+              'name' => 'role',
+              'type' => 'select',
+              'label' => $this->h5pF->t("Author's role"),
+              'default' => 'Author',
+              'options' => array(
+                (object) array(
+                  'value' => 'Author',
+                  'label' => $this->h5pF->t('Author')
+                ),
+                (object) array(
+                  'value' => 'Editor',
+                  'label' => $this->h5pF->t('Editor')
+                ),
+                (object) array(
+                  'value' => 'Licensee',
+                  'label' => $this->h5pF->t('Licensee')
+                ),
+                (object) array(
+                  'value' => 'Originator',
+                  'label' => $this->h5pF->t('Originator')
+                )
               )
             )
           )
@@ -4311,60 +4463,50 @@ class H5PContentValidator {
       ),
       (object) array(
         'name' => 'licenseExtras',
-        'type' => 'textarea',
+        'type' => 'text',
+        'widget' => 'textarea',
         'label' => $this->h5pF->t('License Extras'),
         'optional' => TRUE,
         'description' => $this->h5pF->t('Any additional information about the license')
       ),
       (object) array(
-        'name' => 'changeLog',
-        'type' => 'group',
-        'expanded' => FALSE,
-        'label' => $this->h5pF->t('Change Log'),
-        'fields' => array(
-          (object) array (
-            'name' => 'changeLogForm',
-            'type' => 'group',
-            'label' => $this->h5pF->t('Question'),
-            'expanded' => TRUE,
-            'fields' => array(
-              (object) array(
-                'name' => 'date',
-                'type' => 'text',
-                'label' => $this->h5pF->t('Date'),
-                'optional' => TRUE
-              ),
-              (object) array(
-                'name' => 'author',
-                'type' => 'text',
-                'label' => $this->h5pF->t('Changed by'),
-                'optional' => TRUE
-              ),
-              (object) array(
-                'name' => 'log',
-                'type' => 'textarea',
-                'label' => $this->h5pF->t('Description of change'),
-                'placeholder' => $this->h5pF->t('Photo cropped, text changed, etc.'),
-                'optional' => TRUE
-              )
+        'name' => 'changes',
+        'type' => 'list',
+        'field' => (object) array(
+          'name' => 'change',
+          'type' => 'group',
+          'label' => $this->h5pF->t('Change Log'),
+          'fields' => array(
+            (object) array(
+              'name' => 'date',
+              'type' => 'text',
+              'label' => $this->h5pF->t('Date'),
+              'optional' => TRUE
+            ),
+            (object) array(
+              'name' => 'author',
+              'type' => 'text',
+              'label' => $this->h5pF->t('Changed by'),
+              'optional' => TRUE
+            ),
+            (object) array(
+              'name' => 'log',
+              'type' => 'text',
+              'widget' => 'textarea',
+              'label' => $this->h5pF->t('Description of change'),
+              'placeholder' => $this->h5pF->t('Photo cropped, text changed, etc.'),
+              'optional' => TRUE
             )
           )
         )
       ),
-      (object) array(
+      (object) array (
         'name' => 'authorComments',
-        'label' => $this->h5pF->t('Additional Information'),
-        'type' => 'group',
-        'expanded' => FALSE,
-        'fields' => array(
-          (object) array (
-            'name' => 'authorComments',
-            'type' => 'textarea',
-            'label' => $this->h5pF->t('Author comments'),
-            'description' => $this->h5pF->t('Comments for the editor of the content (This text will not be published as a part of copyright info)'),
-            'optional' => TRUE
-          )
-        )
+        'type' => 'text',
+        'widget' => 'textarea',
+        'label' => $this->h5pF->t('Author comments'),
+        'description' => $this->h5pF->t('Comments for the editor of the content (This text will not be published as a part of copyright info)'),
+        'optional' => TRUE
       )
     );
 
diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js
index 81e55de..d50a09c 100644
--- a/js/h5p-content-upgrade-process.js
+++ b/js/h5p-content-upgrade-process.js
@@ -7,7 +7,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
    * @class
    * @namespace H5P
    */
-  function ContentUpgradeProcess(name, oldVersion, newVersion, params, extras, id, loadLibrary, done) {
+  function ContentUpgradeProcess(name, oldVersion, newVersion, params, id, loadLibrary, done) {
     var self = this;
 
     // Make params possible to work with
@@ -24,38 +24,28 @@ H5P.ContentUpgradeProcess = (function (Version) {
       });
     }
 
-    // Make extras possible to work with
-    for (var element in extras) {
-      if (extras.hasOwnProperty(element)) {
-        try {
-          extras[element] = JSON.parse(extras[element]);
-          if (!(extras[element] instanceof Object)) {
-            throw true;
-          }
-        }
-        catch (event) {
-          return done({
-            type: 'errorExtrasBroken',
-            id: id
-          });
-        }
-      }
-    }
-
     self.loadLibrary = loadLibrary;
-    self.upgrade(name, oldVersion, newVersion, params, extras, function (err, result) {
+    self.upgrade(name, oldVersion, newVersion, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
       if (err) {
         return done(err);
       }
 
-      done(null, JSON.stringify(params), JSON.stringify(extras));
+      done(null, JSON.stringify({params: upgradedParams, metadata: upgradedMetadata}));
     });
   }
 
   /**
+   * Run content upgrade.
    *
+   * @public
+   * @param {string} name
+   * @param {Version} oldVersion
+   * @param {Version} newVersion
+   * @param {Object} params
+   * @param {Object} metadata
+   * @param {Function} done
    */
-  ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, extras, done) {
+  ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, metadata, done) {
     var self = this;
 
     // Load library details and upgrade routines
@@ -65,7 +55,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
       }
 
       // Run upgrade routines on params
-      self.processParams(library, oldVersion, newVersion, params, extras, function (err, params, extras) {
+      self.processParams(library, oldVersion, newVersion, params, metadata, function (err, params, metadata) {
         if (err) {
           return done(err);
         }
@@ -79,7 +69,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
             next(err);
           });
         }, function (err) {
-          done(err, params, extras);
+          done(err, params, metadata);
         });
       });
     });
@@ -95,7 +85,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
    * @param {Object} params
    * @param {Function} next
    */
-  ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, extras, next) {
+  ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, metadata, next) {
     if (H5PUpgrades[library.name] === undefined) {
       if (library.upgradesScript) {
         // Upgrades script should be loaded so the upgrades should be here.
@@ -130,17 +120,11 @@ H5P.ContentUpgradeProcess = (function (Version) {
             try {
               unnecessaryWrapper(params, function (err, upgradedParams, upgradedExtras) {
                 params = upgradedParams;
-                /**
-                 * "params" (and "extras", e.g. metadata) flow through each update function and are changed
-                 * if necessary. Since "extras" was added later and old update functions don't return
-                 * it, we need to ignore undefined values here -- or change every single update function
-                 * in all content types.
-                 */
-                if (upgradedExtras) {
-                  extras = upgradedExtras;
+                if (upgradedExtras && upgradedExtras.metadata) { // Optional
+                  metadata = upgradedExtras.metadata;
                 }
                 nextMinor(err);
-              }, extras);
+              }, {metadata: metadata});
             }
             catch (err) {
               if (console && console.log) {
@@ -154,7 +138,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
         }, nextMajor);
       }
     }, function (err) {
-      next(err, params, extras);
+      next(err, params, metadata);
     });
   };
 
@@ -195,20 +179,13 @@ H5P.ContentUpgradeProcess = (function (Version) {
               return done(); // Larger or same version that's available
             }
 
-            // Currently, we only use metadata as additional things that might need change
-            var extras = {
-              metadata: params.metadata
-            };
-
             // A newer version is available, upgrade params
-            return self.upgrade(availableLib[0], usedVer, availableVer, params.params, extras, function (err, upgraded, extras) {
+            return self.upgrade(availableLib[0], usedVer, availableVer, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
               if (!err) {
                 params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
-                params.params = upgraded;
-                if (extras) {
-                  if (extras.metadata) {
-                    params.metadata = extras.metadata;
-                  }
+                params.params = upgradedParams;
+                if (upgradedMetadata) {
+                  params.metadata = upgradedMetadata;
                 }
               }
               done(err, params);
diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js
index af0be94..26ad038 100644
--- a/js/h5p-content-upgrade-worker.js
+++ b/js/h5p-content-upgrade-worker.js
@@ -9,7 +9,7 @@ var libraryLoadedCallback;
 var messageHandlers = {
   newJob: function (job) {
     // Start new job
-    new H5P.ContentUpgradeProcess(job.name, new H5P.Version(job.oldVersion), new H5P.Version(job.newVersion), job.params, job.extras, job.id, function loadLibrary(name, version, next) {
+    new H5P.ContentUpgradeProcess(job.name, new H5P.Version(job.oldVersion), new H5P.Version(job.newVersion), job.params, job.id, function loadLibrary(name, version, next) {
       // TODO: Cache?
       postMessage({
         action: 'loadLibrary',
@@ -17,7 +17,7 @@ var messageHandlers = {
         version: version.toString()
       });
       libraryLoadedCallback = next;
-    }, function done(err, result, extras) {
+    }, function done(err, result) {
       if (err) {
         // Return error
         postMessage({
@@ -33,8 +33,7 @@ var messageHandlers = {
       postMessage({
         action: 'done',
         id: job.id,
-        params: result,
-        extras: extras
+        params: result
       });
     });
   },
diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js
index 7841be7..e0535a8 100644
--- a/js/h5p-content-upgrade.js
+++ b/js/h5p-content-upgrade.js
@@ -128,7 +128,7 @@
     // Register message handlers
     var messageHandlers = {
       done: function (result) {
-        self.workDone(result.id, result.params, result.extras, this);
+        self.workDone(result.id, result.params, this);
       },
       error: function (error) {
         self.printError(error.err);
@@ -196,7 +196,7 @@
       self.token = inData.token;
 
       // Start processing
-      self.processBatch(inData.params, {metadata: inData.metadata});
+      self.processBatch(inData.params);
     });
   };
 
@@ -217,7 +217,7 @@
    *
    * @param {Object} parameters
    */
-  ContentUpgrade.prototype.processBatch = function (parameters, extras) {
+  ContentUpgrade.prototype.processBatch = function (parameters) {
     var self = this;
 
     // Track upgraded params
@@ -225,7 +225,6 @@
 
     // Track current batch
     self.parameters = parameters;
-    self.extras = extras;
 
     // Create id mapping
     self.ids = [];
@@ -279,12 +278,11 @@
         name: info.library.name,
         oldVersion: info.library.version,
         newVersion: self.version.toString(),
-        params: self.parameters[id],
-        extras: self.extras
+        params: self.parameters[id]
       });
     }
     else {
-      new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], self.extras, id, function loadLibrary(name, version, next) {
+      new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) {
         self.loadLibrary(name, version, function (err, library) {
           if (library.upgradesScript) {
             self.loadScript(library.upgradesScript, function (err) {
@@ -299,13 +297,13 @@
           }
         });
 
-      }, function done(err, result, extras) {
+      }, function done(err, result) {
         if (err) {
           self.printError(err);
           return ;
         }
 
-        self.workDone(id, result, extras);
+        self.workDone(id, result);
       });
     }
   };
@@ -313,7 +311,7 @@
   /**
    *
    */
-  ContentUpgrade.prototype.workDone = function (id, result, extras, worker) {
+  ContentUpgrade.prototype.workDone = function (id, result, worker) {
     var self = this;
 
     self.working--;
@@ -335,8 +333,7 @@
       self.nextBatch({
         libraryId: self.version.libraryId,
         token: self.token,
-        params: JSON.stringify(self.upgraded),
-        extras: extras
+        params: JSON.stringify(self.upgraded)
       });
     }
   };
diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js
index c1d6c66..4cec937 100644
--- a/js/h5p-x-api-event.js
+++ b/js/h5p-x-api-event.js
@@ -133,9 +133,10 @@ H5P.XAPIEvent.prototype.setObject = function (instance) {
       }
     }
     else {
-      if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) {
+      var content = H5P.getContentForInstance(instance.contentId);
+      if (content && content.metadata && content.metadata.title) {
         this.data.statement.object.definition.name = {
-          "en-US": H5P.createTitle(H5PIntegration.contents['cid-' + instance.contentId].title)
+          "en-US": H5P.createTitle(content.metadata.title)
         };
       }
     }
@@ -150,7 +151,6 @@ H5P.XAPIEvent.prototype.setObject = function (instance) {
  */
 H5P.XAPIEvent.prototype.setContext = function (instance) {
   if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) {
-    var parentId = instance.parent.subContentId === undefined ? instance.parent.contentId : instance.parent.subContentId;
     this.data.statement.context = {
       "contextActivities": {
         "parent": [
diff --git a/js/h5p.js b/js/h5p.js
index f10df8f..42f9519 100644
--- a/js/h5p.js
+++ b/js/h5p.js
@@ -1657,8 +1657,8 @@ H5P.libraryFromString = function (library) {
   if (res !== null) {
     return {
       'machineName': res[1],
-      'majorVersion': res[2],
-      'minorVersion': res[3]
+      'majorVersion': parseInt(res[2]),
+      'minorVersion': parseInt(res[3])
     };
   }
   else {
@@ -2140,6 +2140,20 @@ H5P.createTitle = function (rawTitle, maxLength) {
     contentUserDataAjax(contentId, dataId, subContentId, undefined, null);
   };
 
+  /**
+   * Function for getting content for a certain ID
+   *
+   * @param {number} contentId
+   * @return {Object}
+   */
+  H5P.getContentForInstance = function (contentId) {
+    var key = 'cid-' + contentId;
+    var exists = H5PIntegration && H5PIntegration.contents &&
+                 H5PIntegration.contents[key];
+
+    return exists ? H5PIntegration.contents[key] : undefined;
+  };
+
   /**
    * Prepares the content parameters for storing in the clipboard.
    *
@@ -2242,6 +2256,17 @@ H5P.createTitle = function (rawTitle, maxLength) {
     H5P.externalDispatcher.trigger('datainclipboard', {reset: false});
   };
 
+  /**
+   * Get config for a library
+   *
+   * @param string machineName
+   * @return Object
+   */
+  H5P.getLibraryConfig = function (machineName) {
+    var hasConfig = H5PIntegration.libraryConfig && H5PIntegration.libraryConfig[machineName];
+    return hasConfig ? H5PIntegration.libraryConfig[machineName] : {};
+  };
+
   /**
    * Get item from the H5P Clipboard.
    *