diff --git a/src/Controller/UwDownloadCsvController.php b/src/Controller/UwDownloadCsvController.php
new file mode 100644
index 0000000000000000000000000000000000000000..54876fdd70360bb69ec4e8ee50b5866e461ec1a4
--- /dev/null
+++ b/src/Controller/UwDownloadCsvController.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace Drupal\uw_cfg_common\Controller;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\path_alias\AliasManager;
+use Drupal\transliterate_filenames\SanitizeName;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Provides CSV files for specific UW sites.
+ */
+class UwDownloadCsvController extends ControllerBase {
+
+  /**
+   * Entity type manager from the core.
+   *
+   * @var Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The path alias.
+   *
+   * @var \Drupal\path_alias\AliasManager
+   */
+  protected $pathAlias;
+
+  /**
+   * Sanitize name.
+   *
+   * @var Drupal\transliterate_filenames\SanitizeName
+   */
+  protected $sanitizeName;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * CSV Report constructor.
+   *
+   * @param Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
+   * @param Drupal\path_alias\AliasManager $pathAlias
+   *   The path alias.
+   * @param Drupal\transliterate_filenames\SanitizeName $sanitizeName
+   *   The sanitize name.
+   * @param Drupal\Core\Config\ConfigFactory $configFactory
+   *   The config factory.
+   */
+  public function __construct(
+    EntityTypeManagerInterface $entityTypeManager,
+    AliasManager $pathAlias,
+    SanitizeName $sanitizeName,
+    ConfigFactory $configFactory
+  ) {
+
+    $this->entityTypeManager = $entityTypeManager;
+    $this->pathAlias = $pathAlias;
+    $this->sanitizeName = $sanitizeName;
+    $this->configFactory = $configFactory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('path_alias.manager'),
+      $container->get('transliterate_filenames.sanitize_name'),
+      $container->get('config.factory')
+    );
+  }
+
+  /**
+   * Provides a CSV file for content on a site.
+   *
+   * @return Symfony\Component\HttpFoundation\Response
+   *   The page response.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function uwContentReport() {
+
+    // Get all the nodes.
+    $nodes = $this->entityTypeManager->getStorage('node')->loadMultiple();
+
+    // Just in case there are no nodes, there should always
+    // be a home page though, this is just to ensure no errors.
+    $response = NULL;
+
+    if ($nodes) {
+
+      // The header of the CSV.
+      $data = [
+        $this->t('Node ID'),
+        $this->t('Title'),
+        $this->t('Content type'),
+        $this->t('Path'),
+        $this->t('Published'),
+        $this->t('Created'),
+        $this->t('Last updated'),
+      ];
+
+      // Put header for CSV into format to be used in CSV.
+      $rows[] = implode(',', $data);
+
+      // Step through each of the nodes and get the info.
+      foreach ($nodes as $node) {
+
+        // Pull out the info about the node, making sure the title is CSV-safe.
+        $data = [
+          'nid' => $node->id(),
+          'title' => '"' . str_replace('"', '""', $node->getTitle()) . '"',
+          'type' => $node->type->entity->label(),
+          'path' => $this->pathAlias->getAliasByPath('/node/' . $node->id()),
+          'published' => $node->status->value == 1 ? 'yes' : 'no',
+          'created' => date('Y-d-m G:i', $node->getCreatedTime()),
+          'updated' => date('Y-d-m G:i', $node->getChangedTime()),
+        ];
+
+        // Store the node info for use later in the CSV.
+        $rows[] = implode(',', $data);
+      }
+
+      // Put the info into a large CSV format.
+      $content = implode("\n", $rows);
+
+      // Create the response for the CSV page.
+      $response = new Response($content);
+
+      // Determine the filename for the CSV.
+      $sitename = $this->configFactory->get('system.site')->get('name');
+      $filename = $sitename . '_content_report.csv';
+
+      // Make sure we are using a safe filename.
+      $filename = $this->sanitizeName->sanitizeFilename($filename);
+
+      // Set the headers for the CSV.
+      $response->headers->set('Content-Type', 'text/csv');
+      $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
+    }
+
+    return $response;
+  }
+
+}
diff --git a/src/UwRoles/user.role.uw_role_site_owner.yml b/src/UwRoles/user.role.uw_role_site_owner.yml
index ee49d4aa5833d8215c841475e0d6d1e9a00decd6..2371c6f69fb9ab70c969ca85aa3190bb6bf2a68a 100644
--- a/src/UwRoles/user.role.uw_role_site_owner.yml
+++ b/src/UwRoles/user.role.uw_role_site_owner.yml
@@ -49,3 +49,4 @@ permissions:
   - 'customize shortcut links'
   - 'edit users role expire'
   - 'enable disable ofis profiles'
+  - 'view UW CSV reports'
diff --git a/uw_cfg_common.info.yml b/uw_cfg_common.info.yml
index ea720085afd19f76786c950cd873f3a1c2cc0789..d81086c08f18df03b1e29d58efc51d1a2234d33f 100644
--- a/uw_cfg_common.info.yml
+++ b/uw_cfg_common.info.yml
@@ -55,4 +55,5 @@ dependencies:
   - 'scheduler:scheduler'
   - 'simple_sitemap:simple_sitemap'
   - 'subpathauto:subpathauto'
+  - 'transliterate_filenames:transliterate_filenames'
   - 'uw_media:uw_media'
diff --git a/uw_cfg_common.links.menu.yml b/uw_cfg_common.links.menu.yml
index 259c7b09d4bcb87117b1282693faf9df3f6345ff..a57a10d9b6fd4ecaeff80374d411ff808fdbe768 100644
--- a/uw_cfg_common.links.menu.yml
+++ b/uw_cfg_common.links.menu.yml
@@ -389,3 +389,8 @@ uw_site_management.wcms_how_to_documents:
   menu_name: uw-menu-site-management
   url: https://uwaterloo.ca/web-resources/wcms-users/training-and-support/wcms-how-documents
   weight: 0
+uw_site_management.uw_content_report_csv:
+  title: 'Content report (CSV)'
+  menu_name: uw-menu-site-management
+  route_name: uw_cfg_common.uw_content_report_csv
+  weight: 0
diff --git a/uw_cfg_common.permissions.yml b/uw_cfg_common.permissions.yml
index f9097d5af81d1f2b39cf5d82e9ee44f4ff4fe9db..dd2f5116870c2115ebad7e85a56844be2e735767 100644
--- a/uw_cfg_common.permissions.yml
+++ b/uw_cfg_common.permissions.yml
@@ -21,3 +21,6 @@
 'rearrange profiles':
   title: 'Rearrange profiles'
   description: 'Allows access to the rearrange profiles page.'
+'view UW CSV reports':
+  title: 'View UW CSV reports'
+  description: 'Allows access to UW CSV reports.'
diff --git a/uw_cfg_common.routing.yml b/uw_cfg_common.routing.yml
index 28c3b4ca13ae22f53f9bcd0a1a28276cfc422290..5055c6b06eb9a492d212e88ad5694741a19e8435 100644
--- a/uw_cfg_common.routing.yml
+++ b/uw_cfg_common.routing.yml
@@ -34,3 +34,10 @@ uw_cfg_common.content_type_usage_page:
     _title: 'Content type Use'
   requirements:
     _permission: 'access content'
+uw_cfg_common.uw_content_report_csv:
+  path: '/admin/reports/uw_content_report_csv'
+  defaults:
+    _title: 'Download UW content report (CSV)'
+    _controller: \Drupal\uw_cfg_common\Controller\UwDownloadCsvController::uwContentReport
+  requirements:
+    _permission: 'view UW CSV reports'