diff --git a/assets/javascript/ajax.js b/assets/javascript/ajax.js
index 8c927afbd..53746a358 100644
--- a/assets/javascript/ajax.js
+++ b/assets/javascript/ajax.js
@@ -141,6 +141,17 @@
 		});
 	};
 
+	phpbb.addAjaxCallback('titania.package.builder.add', function(result) {
+		var $revision = $(this).data('revision-id');
+
+		// Hide the other buttons
+		$('.package-builder-add').has('a:not([data-revision-id="' + $revision + '"])').hide();
+
+		// Change the button that was clicked
+		$('.package-builder-add > a').remove();
+		$('.package-builder-add').append('<span class="package-builder-download-prompt">' + result.message + '</span>');
+	});
+
 	phpbb.addAjaxCallback('titania.rate', function(res) {
 		if (res.rating) {
 			var $rating = $(res.rating);
diff --git a/config/controllers.yml b/config/controllers.yml
index a8eb6d821..8fc923385 100644
--- a/config/controllers.yml
+++ b/config/controllers.yml
@@ -15,6 +15,20 @@ services:
             - '@phpbb.titania.config'
             - '@phpbb.titania.tracking'
 
+    phpbb.titania.controller.package_builder:
+        class: phpbb\titania\controller\package_builder
+        arguments:
+            - '@dbal.conn'
+            - '@template'
+            - '@config'
+            - '@user'
+            - '@language'
+            - '@phpbb.titania.controller.helper'
+            - '@phpbb.titania.cache'
+            - '@request'
+            - '@phpbb.titania.config'
+            - '%phpbb.titania.root_path%'
+
     phpbb.titania.controller.faq:
         class: phpbb\titania\controller\faq
         arguments:
diff --git a/config/routes/general.yml b/config/routes/general.yml
index 3b9c76178..57e892edd 100644
--- a/config/routes/general.yml
+++ b/config/routes/general.yml
@@ -16,6 +16,18 @@ phpbb.titania.faq:
     path: /faq
     defaults: { _controller: phpbb.titania.controller.faq:handle }
 
+phpbb.titania.package_builder.add:
+    path: /package-builder/add/{contrib}/{revision}
+    defaults: { _controller: phpbb.titania.controller.package_builder:add }
+
+phpbb.titania.package_builder.reset:
+    path: /package-builder/reset
+    defaults: { _controller: phpbb.titania.controller.package_builder:reset }
+
+phpbb.titania.package_builder.download:
+    path: /package-builder/download
+    defaults: { _controller: phpbb.titania.controller.package_builder:download }
+
 phpbb.titania.queue_stats:
     path: /queue-stats/{contrib_type}
     defaults: { _controller: phpbb.titania.controller.queue_stats:display_stats }
diff --git a/controller/package_builder.php b/controller/package_builder.php
new file mode 100644
index 000000000..52ca5bfbb
--- /dev/null
+++ b/controller/package_builder.php
@@ -0,0 +1,266 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Customisation Database package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\titania\controller;
+
+use phpbb\titania\ext;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class package_builder
+{
+	const COOKIE_NAME = 'cdb_package_builder';
+
+	/** @var \phpbb\db\driver\driver_interface */
+	protected $db;
+
+	/** @var \phpbb\template\template */
+	protected $template;
+
+	/** @var \phpbb\config\config */
+	protected $config;
+
+	/** @var \phpbb\user */
+	protected $user;
+
+	/** @var \phpbb\language\language */
+	protected $language;
+
+	/** @var \phpbb\titania\controller\helper */
+	protected $helper;
+
+	/** @var \phpbb\titania\cache\service */
+	protected $cache;
+
+	/** @var \phpbb\request\request_interface */
+	protected $request;
+
+	/** @var \phpbb\titania\config\config */
+	protected $ext_config;
+
+	/** @var string */
+	protected $ext_root_path;
+
+	/**
+	 * Constructor
+	 *
+	 * @param \phpbb\db\driver\driver_interface $db
+	 * @param \phpbb\template\template $template
+	 * @param \phpbb\config\config $config
+	 * @param \phpbb\user $user
+	 * @param \phpbb\language\language $language
+	 * @param \phpbb\titania\controller\helper $helper
+	 * @param \phpbb\titania\cache\service $cache
+	 * @param \phpbb\request\request_interface $request
+	 * @param \phpbb\titania\config\config $ext_config
+	 * @param $ext_root_path
+	 */
+	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\template\template $template, \phpbb\config\config $config, \phpbb\user $user, \phpbb\language\language $language, \phpbb\titania\controller\helper $helper, \phpbb\titania\cache\service $cache, \phpbb\request\request_interface $request, \phpbb\titania\config\config $ext_config, $ext_root_path)
+	{
+		$this->db = $db;
+		$this->template = $template;
+		$this->config = $config;
+		$this->user = $user;
+		$this->language = $language;
+		$this->helper = $helper;
+		$this->cache = $cache;
+		$this->request = $request;
+		$this->ext_config = $ext_config;
+		$this->ext_root_path = $ext_root_path;
+	}
+
+	/**
+	 * Get the cookie name
+	 * @return string
+	 */
+	private function get_cookie_name()
+	{
+		return $this->config['cookie_name'] . '_' . self::COOKIE_NAME;
+	}
+
+	/**
+	 * Save the cookie
+	 * @param $cookie_value
+	 */
+	private function save_cookie($cookie_value)
+	{
+		// One hour expiry
+		$this->user->set_cookie(self::COOKIE_NAME, $cookie_value, time() + (60 * 60));
+	}
+
+	/**
+	 * Get the revisions and contributions already selected out of the cookie value
+	 * @param $value
+	 * @return array
+	 */
+	public static function split_cookie_values($value)
+	{
+		$results = [
+			'contribs' => [],
+			'revisions' => [],
+		];
+
+		$values = explode(',', $value);
+
+		foreach ($values as $item)
+		{
+			if (strlen($item) > 0)
+			{
+				$split = explode('|', $item);
+
+				$results['contribs'][] = $split[0];
+				$results['revisions'][] = $split[1];
+			}
+		}
+
+		return $results;
+	}
+
+	/**
+	 * Create a json response
+	 * @param $success
+	 * @param $values
+	 * @param string $message
+	 * @return JsonResponse
+	 */
+	private function create_json_response($success, $values, $message = '')
+	{
+		$json = [
+			'success' => $success,
+			'values' => $values,
+		];
+
+		if (!empty($message))
+		{
+			$json['message'] = $message;
+		}
+
+		return new JsonResponse($json);
+	}
+
+	/**
+	 * Clear the existing values saved in the cookie
+	 */
+	public function reset()
+	{
+		$this->save_cookie('');
+
+		return $this->create_json_response(true, '');
+	}
+
+	/**
+	 * Download the completed package
+	 * @throws \phpbb\titania\entity\UnknownPropertyException
+	 */
+	public function download()
+	{
+		$versions = $this->cache->get_phpbb_versions();
+		$latest_version = reset($versions);
+
+		$phpbb_package = $this->ext_root_path . 'includes/phpbb_packages/phpBB-' . $latest_version . '.zip';
+		$extract_path = $this->ext_config->__get('contrib_temp_path') . 'tmp/package_' . $this->user->data['user_id'];
+
+		$zip = new \ZipArchive();
+
+		if ($zip->open($phpbb_package))
+		{
+			// Unzip the revision to a temporary folder
+			$zip->extractTo($extract_path);
+			$zip->close();
+
+			// Take the revisions from the cookies, and plug them into the phpBB3 instance
+			// extensions to ext/, styles to style/ and language packs to language/
+			$existing_cookie = $this->request->variable($this->get_cookie_name(), '', false, \phpbb\request\request_interface::COOKIE);
+			$existing_values = self::split_cookie_values($existing_cookie);
+
+			// Get the attachments
+			$sql = 'SELECT r.revision_id, c.contrib_id, a.attachment_directory, a.physical_filename, c.contrib_type
+					FROM ' . TITANIA_REVISIONS_TABLE . ' r, ' . TITANIA_ATTACHMENTS_TABLE . ' a, ' . TITANIA_CONTRIBS_TABLE . ' c
+					WHERE r.attachment_id = a.attachment_id
+						AND c.contrib_id = r.contrib_id
+					AND ' . $this->db->sql_in_set('r.revision_id', $existing_values['revisions']);
+
+			$result = $this->db->sql_query($sql);
+
+			while ($row = $this->db->sql_fetchrow($result))
+			{
+				// TODO: Check that the revision ID hasn't been manipulated and this user has access to download the revision
+
+				$contribution_archive = $this->ext_config->upload_path . $row['attachment_directory'] . '/' . $row['physical_filename'];
+
+				$zip = new \ZipArchive();
+
+				if ($zip->open($contribution_archive))
+				{
+					if ($row['contrib_type'] == ext::TITANIA_TYPE_EXTENSION)
+					{
+						$zip->extractTo($extract_path . '/phpBB3/ext');
+					}
+
+					if ($row['contrib_type'] == ext::TITANIA_TYPE_STYLE)
+					{
+						$zip->extractTo($extract_path . '/phpBB3/style');
+					}
+
+					if ($row['contrib_type'] == ext::TITANIA_TYPE_TRANSLATION)
+					{
+						// TODO: talk to Crizzo about where these need to end up being moved to
+						$zip->extractTo($extract_path . '/phpBB3/language');
+					}
+
+					$zip->close();
+				}
+			}
+
+			// TODO: Zip up the final package and send it to the browser
+		}
+	}
+
+	/**
+	 * Add a revision ID
+	 * @param $contrib
+	 * @param $revision
+	 * @return JsonResponse
+	 */
+	public function add($contrib, $revision)
+	{
+		$json = null;
+
+		$existing_cookie = $this->request->variable($this->get_cookie_name(), '', false, \phpbb\request\request_interface::COOKIE);
+
+		// Validate whether it's already added
+		$existing_values = self::split_cookie_values($existing_cookie);
+
+		if (in_array($contrib, $existing_values['contribs']) || in_array($revision, $existing_values['revisions']))
+		{
+			// This contribution (or revision) has already been added
+			$json = $this->create_json_response(false, $existing_values, $this->language->lang('PACKAGE_ALREADY_ADDED'));
+		}
+
+		else
+		{
+			// Cookies are my favourite food group, cookies taste nice. And in this case they are lightweight :D
+			$value = $contrib . '|' . $revision;
+			$cookie_value = (!empty($existing_cookie)) ? sprintf('%s,%s', $existing_cookie, $value) : $value;
+
+			// Set the cookie value; expire after an hour (plenty of time for the user to download the package)
+			$this->save_cookie($cookie_value);
+
+			$updated_values = self::split_cookie_values($cookie_value);
+			$download_link = sprintf('<a href="%s">%s</a>', $this->helper->route('phpbb.titania.package_builder.download'), $this->language->lang('PACKAGE_ADDED', count($updated_values['contribs'])));
+
+			$json = $this->create_json_response(true, $updated_values, $download_link);
+		}
+
+		return $json;
+	}
+}
diff --git a/includes/objects/revision.php b/includes/objects/revision.php
index c7497913c..aa6d27430 100644
--- a/includes/objects/revision.php
+++ b/includes/objects/revision.php
@@ -81,6 +81,9 @@ class titania_revision extends \phpbb\titania\entity\database_base
 	/** @var config */
 	protected $config;
 
+	/** @var \phpbb\request\request_interface */
+	protected $request;
+
 	public function __construct($contrib, $revision_id = false)
 	{
 		// Configure object properties
@@ -120,6 +123,7 @@ public function __construct($contrib, $revision_id = false)
 		$this->translations = phpbb::$container->get('phpbb.titania.attachment.operator');
 		$this->cache = phpbb::$container->get('phpbb.titania.cache');
 		$this->config = phpbb::$container->get('config');
+		$this->request = phpbb::$container->get('request');
 	}
 
 	/**
@@ -262,7 +266,12 @@ public function display($tpl_block = 'revisions', $show_queue = false, $all_vers
 			'U_DOWNLOAD'			=> $this->get_url(),
 			'U_COLORIZEIT'			=> $url_colorizeit,
 			'U_EDIT'				=> ($this->contrib && ($this->contrib->is_author || $this->contrib->is_active_coauthor || $this->contrib->type->acl_get('moderate'))) ? $this->contrib->get_url('revision', array('page' => 'edit', 'id' => $this->revision_id)) : '',
+			'U_PACKAGE_ADD'			=> $this->controller_helper->route('phpbb.titania.package_builder.add', [
+				'contrib' => $this->contrib_id,
+				'revision' => $this->revision_id,
+			]),
 
+			'S_PACKAGE'				=> $this->check_package_builder_link(),
 			'S_USE_QUEUE'			=> (titania::$config->use_queue && $this->contrib->type->use_queue) ? true : false,
 			'S_NEW'					=> ($this->revision_status == ext::TITANIA_REVISION_NEW) ? true : false,
 			'S_APPROVED'			=> ($this->revision_status == ext::TITANIA_REVISION_APPROVED) ? true : false,
@@ -292,6 +301,49 @@ public function display($tpl_block = 'revisions', $show_queue = false, $all_vers
 		}
 	}
 
+	/**
+	 * Check if the package builder link should be displayed
+	 */
+	private function check_package_builder_link()
+	{
+		// Check if the types are acceptable for the package manager
+		$show_package_builder = false;
+
+		if ($this->contrib->type instanceof \phpbb\titania\contribution\extension\type
+			|| $this->contrib->type instanceof \phpbb\titania\contribution\translation\type
+			|| $this->contrib->type instanceof \phpbb\titania\contribution\style\type)
+		{
+			// If it's an extension, style or language pack we can show it if...
+			foreach ($this->phpbb_versions as $supported_version)
+			{
+				if ($supported_version['phpbb_version_branch'] === '32')
+				{
+					$show_package_builder = true;
+					break;
+				}
+			}
+		}
+
+		if ($show_package_builder)
+		{
+			// Look at the existing package values
+			$existing_cookie = $this->request->variable($this->config['cookie_name'] . '_' . \phpbb\titania\controller\package_builder::COOKIE_NAME, '', false, \phpbb\request\request_interface::COOKIE);
+
+			if (!empty($existing_cookie))
+			{
+				$split_values = \phpbb\titania\controller\package_builder::split_cookie_values($existing_cookie);
+
+				if (in_array($this->contrib_id, $split_values['contribs']))
+				{
+					// No need to show the link if a revision from this contribution has already been added to the package
+					$show_package_builder = false;
+				}
+			}
+		}
+
+		return $show_package_builder;
+	}
+
 	/**
 	 * Handle some stuff we need when submitting a revision
 	 */
diff --git a/language/en/common.php b/language/en/common.php
index cf9f50661..e8bb1311d 100644
--- a/language/en/common.php
+++ b/language/en/common.php
@@ -194,6 +194,13 @@
 
 	'ORDER'						=> 'Order',
 
+	'PACKAGE_ADDED'				=> array(
+		1	=> 'Download package with 1 customisation',
+		2	=> 'Download package with %d customisations',
+	),
+
+	'PACKAGE_ALREADY_ADDED'		=> 'This contribution has already been added to the package',
+
 	'PAGE_REQUEST_INVALID'		=> 'The page request is invalid. Please try again.',
 	'PARENT_CATEGORY'			=> 'Parent Category',
 	'PARENT_CONTRIBUTION'		=> 'Parent Contribution',
diff --git a/styles/prosilver/template/common/revision_list.html b/styles/prosilver/template/common/revision_list.html
index 5dab5ede5..12882a719 100644
--- a/styles/prosilver/template/common/revision_list.html
+++ b/styles/prosilver/template/common/revision_list.html
@@ -19,7 +19,7 @@
 						<div class="list-inner">
 							{% if revisions.U_DOWNLOAD %}<a href="{{ revisions.U_DOWNLOAD }}" title="{{ lang('DOWNLOAD') }}">{% endif %}
 								{% if revisions.NAME %}{{ revisions.NAME }}{% else %}<em>{{ lang('NO_REVISION_NAME') }}</em>{% endif %}
-							{% if revisions.U_DOWNLOAD %}</a>{% endif %}
+							{% if revisions.U_DOWNLOAD %}</a>{% if revisions.S_PACKAGE %} <span class="package-builder-add"><a href="{{ revisions.U_PACKAGE_ADD }}" data-ajax="titania.package.builder.add" data-revision-id="{{ revisions.REVISION_ID }}">[+]</a></span>{% endif %}{% endif %}
 						</div>
 					</dt>
 					<dd class="general revision-status">
diff --git a/styles/prosilver/theme/common.css b/styles/prosilver/theme/common.css
index 632097e2f..595c6496f 100644
--- a/styles/prosilver/theme/common.css
+++ b/styles/prosilver/theme/common.css
@@ -65,6 +65,9 @@ h3.section-name {
 	text-align: left;
 }
 
+.package-builder-download-prompt {
+	font-size: 0.8em;
+}
 
 a.download-button, a.colorizeit-button {
 	box-sizing: border-box;