diff --git a/README.rst b/README.rst
index ad339411b7239aa459187b73719c29a55166119f..7c5e2bf41bfb7776ba1a101c3cefab74149c1f23 100644
--- a/README.rst
+++ b/README.rst
@@ -47,6 +47,9 @@ tools:
 * `flake8 <https://www.reviewboard.org/docs/reviewbot/latest/tools/flake8/>`_
   – A wrapper around several Python code quality tools
 
+* `Istanbul (nyc)` <https://www.reviewboard.org/docs/reviewbot/latest/tools/nyc/>
+  – A code coverage analysis tool for JavaScript codebases
+
 * `PMD <https://www.reviewboard.org/docs/reviewbot/latest/tools/pmd/>`_
   – A static analysis tool that provides checkers for many languages
 
diff --git a/bot/README.rst b/bot/README.rst
index c5b19d6da87c74e51b9fe88228282fa3cde305a4..cac8e7e6e3fd716005a2f02e26d1706382ab551a 100644
--- a/bot/README.rst
+++ b/bot/README.rst
@@ -41,6 +41,9 @@ following tools:
 * `flake8 <https://www.reviewboard.org/docs/reviewbot/latest/tools/flake8/>`_
   – A wrapper around several Python code quality tools
 
+* `Istanbul (nyc)` <https://www.reviewboard.org/docs/reviewbot/latest/tools/nyc/>
+  – A code coverage analysis tool for JavaScript codebases
+
 * `PMD <https://www.reviewboard.org/docs/reviewbot/latest/tools/pmd/>`_
   – A static analysis tool that provides checkers for many languages
 
diff --git a/bot/reviewbot/tools/nyc.py b/bot/reviewbot/tools/nyc.py
new file mode 100644
index 0000000000000000000000000000000000000000..65aa62fe9f3bc36d3fa09ca61466108071858464
--- /dev/null
+++ b/bot/reviewbot/tools/nyc.py
@@ -0,0 +1,224 @@
+""""Review Bot tool to run nyc code coverage tool."""
+
+from __future__ import unicode_literals
+
+import json
+import os
+import os.path
+import shlex
+import six
+
+from reviewbot.tools import RepositoryTool
+from reviewbot.utils.process import execute, is_exe_in_path
+
+
+class NYCTool(RepositoryTool):
+    """Review Bot tool to run nyc code coverage tool."""
+
+    name = 'nyc'
+    version = '0.2'
+    description = 'Checks code test coverage of javascript code'
+    timeout = 30
+    options = [
+        {
+            'name': 'min_statement_coverage',
+            'field_type': 'django.forms.IntegerField',
+            'default': 80,
+            'field_options': {
+                'label': 'Required statement coverage %',
+                'help_text': ('If a file has a lower statement coverage than'
+                              'this value, an issue will be opened.'),
+                'required': True,
+            },
+        },
+        {
+            'name': 'min_branch_coverage',
+            'field_type': 'django.forms.IntegerField',
+            'default': 80,
+            'field_options': {
+                'label': 'Required branch coverage %',
+                'help_text': ('If a file has a lower branch coverage than this'
+                              'value, an issue will be opened.'),
+                'required': True,
+            },
+        },
+        {
+            'name': 'min_function_coverage',
+            'field_type': 'django.forms.IntegerField',
+            'default': 80,
+            'field_options': {
+                'label': 'Required function coverage %',
+                'help_text': ('If a file has a lower function coverage than'
+                              'this value, an issue will be opened.'),
+                'required': True,
+            },
+        },
+        {
+            'name': 'min_line_coverage',
+            'field_type': 'django.forms.IntegerField',
+            'default': 80,
+            'field_options': {
+                'label': 'Required line coverage %',
+                'help_text': ('If a file has a lower line coverage than this'
+                              'value, an issue will be opened.'),
+                'required': True,
+            },
+        },
+        {
+            'name': 'install_steps',
+            'field_type': 'django.forms.CharField',
+            'default': 'npm install',
+            'field_options': {
+                'label': 'Install Steps',
+                'help_text': ('List of commands, one per line, to run after'
+                              ' checking out repository in order to run'
+                              ' test suite. (for example, \'npm install\')'),
+                'required': False,
+            },
+            'widget': {
+                'type': 'django.forms.Textarea',
+                'attrs': {
+                    'cols': 80,
+                    'rows': 10,
+                },
+            },
+        },
+        {
+            'name': 'test_command',
+            'field_type': 'django.forms.CharField',
+            'default': 'npm test',
+            'field_options': {
+                'label': 'Test Command',
+                'help_text': 'Command to run the test suite.',
+                'required': True,
+            },
+        },
+        {
+            'name': 'nyc_config',
+            'field_type': 'djblets.db.fields.JSONFormField',
+            'default': '',
+            'field_options': {
+                'label': 'Configuration (.nycrc)',
+                'help_text': 'Configuration file used by nyc.',
+                'required': False,
+            },
+            'widget': {
+                'type': 'django.forms.Textarea',
+                'attrs': {
+                    'cols': 80,
+                    'rows': 10,
+                },
+            },
+        },
+    ]
+
+    def check_dependencies(self):
+        """Verify that the tool's dependencies are installed.
+
+        Returns:
+            bool:
+            True if all dependencies for the tool are satisfied. If this
+            returns False, the worker will not be listed for this Tool's queue,
+            and a warning will be logged.
+        """
+        return is_exe_in_path('nyc') and is_exe_in_path('npm')
+
+    def handle_files(self, files, settings):
+        """Runs NYC to generate a report, parses output, and reviews each file.
+
+        Args:
+            files (list of reviewbot.processing.review.File):
+                The files to process.
+
+            settings (dict):
+                Tool-specific settings.
+        """
+        if settings['install_steps']:
+            # Split and filter empty install commands by line.
+            commands = filter(None, settings['install_steps'].split('\n'))
+
+            for command in commands:
+                execute(shlex.split(command))
+
+        # If any configuration was specified, write configuration to ".nycrc".
+        if settings['nyc_config']:
+            with open('.nycrc', 'wb') as fp:
+                fp.write(settings['nyc_config'])
+
+        cmd = ['nyc', '--reporter', 'json-summary', '--all']
+        cmd.extend(shlex.split(settings['test_command']))
+
+        # Generate the per-file reports by running the user's test command.
+        # We ignore errors as test failures are often considered errors.
+        self.output = execute(cmd, ignore_errors=True)
+
+        # Parse the json generated by nyc for handle_file to use.
+        with open('coverage/coverage-summary.json', 'r') as fp:
+            coverage = json.load(fp)
+
+        thresholds = {
+            'statements': int(settings.get('min_statement_coverage')),
+            'branches': int(settings.get('min_branch_coverage')),
+            'functions': int(settings.get('min_function_coverage')),
+            'lines': int(settings.get('min_line_coverage')),
+        }
+
+        working_dir = os.getcwd()
+
+        for i, f in enumerate(files):
+            self.handle_file(f, i, settings, coverage, thresholds, working_dir)
+
+    def handle_file(self, f, index, settings, coverage, thresholds,
+                    working_dir):
+        """Perform a review of a single file.
+
+        Args:
+            f (reviewbot.processing.review.File):
+                The file to process.
+
+            index (int):
+                The index of this file.
+
+            settings (dict):
+                Tool-specific settings.
+
+            coverage (dict):
+                Parsed coverage report from nyc's json-summary reporter.
+
+            thresholds (dict):
+                Code coverage thresholds to enforce.
+
+            working_dir (str):
+                Working directory for the repository.
+        """
+        path = f.get_patched_file_path()
+
+        if not path:
+            return
+
+        abs_path = os.path.join(working_dir, path)
+
+        if abs_path not in coverage:
+            # Ignore files not tested by NYC.
+            return
+
+        file_coverage = coverage[abs_path]
+
+        issues = []
+
+        for element, threshold in six.iteritems(thresholds):
+            coverage = file_coverage[element]['pct']
+
+            if coverage < threshold:
+                issues.append(
+                    '%s: %s%% coverage less than minimum threshold (%s%%)' %
+                    (element, coverage, threshold))
+
+        # Assuming files included in review request diff are modified.
+        if issues:
+            rq_id = f.review.review_request_id
+            f.review.general_comment(
+                '#File: [%s](/r/%s/diff/#%d) does not meet required coverage.'
+                '\n%s' % (path, rq_id, index, '\n'.join(issues)),
+                issue=True,
+                rich_text=True)
diff --git a/bot/setup.py b/bot/setup.py
index 5c3203ba11d0ddfe04216a0c3e0330b1816009d7..f74f3858c4ff2410d020e2855b4e9511ce80f120 100755
--- a/bot/setup.py
+++ b/bot/setup.py
@@ -32,6 +32,7 @@ setup(
             'cpplint = reviewbot.tools.cpplint:CPPLintTool',
             'flake8 = reviewbot.tools.flake8:Flake8Tool',
             'jshint = reviewbot.tools.jshint:JSHintTool',
+            'nyc = reviewbot.tools.nyc:NYCTool',
             'pmd = reviewbot.tools.pmd:PMDTool',
             'pycodestyle = reviewbot.tools.pycodestyle:PycodestyleTool',
             'pyflakes = reviewbot.tools.pyflakes:PyflakesTool',
diff --git a/docs/reviewbot/tools/index.rst b/docs/reviewbot/tools/index.rst
index aba9e78c15ff0cf7bcb087029e1756c7cbeb7a60..1cd54c9ebc2124b162f9be5f548dd4eb77e1c6e4 100644
--- a/docs/reviewbot/tools/index.rst
+++ b/docs/reviewbot/tools/index.rst
@@ -14,6 +14,7 @@ Review Bot Tools
    cpplint
    flake8
    jshint
+   nyc
    pmd
    pycodestyle
    pyflakes
diff --git a/docs/reviewbot/tools/nyc.rst b/docs/reviewbot/tools/nyc.rst
new file mode 100644
index 0000000000000000000000000000000000000000..873269af9e553ed30d79f5e924872f267e74726d
--- /dev/null
+++ b/docs/reviewbot/tools/nyc.rst
@@ -0,0 +1,40 @@
+.. _tool-nyc:
+
+==============
+Istanbul (nyc)
+==============
+
+Istanbul_ is a tool which instruments JavaScript code to analyse code coverage
+using a test suite. ``nyc`` is one of its terminal front-ends.
+
+This Review Bot tool opens an issue for every file not meeting the configured
+code-coverage thresholds, and attaches the output log of the tools (often
+contains the test logs).
+
+.. _Istanbul: https://istanbul.js.org/
+
+Installation
+============
+
+nyc_ is available through ``npm``. This requires ``nodejs`` to be installed on
+the system::
+
+    $ sudo npm install -g nyc
+
+
+Repository Configuration
+========================
+
+All configuration for this plugin is done in the integration configurations
+page for Review Bot.
+
+This plugin must be configured with all steps necessary to run the code in a
+freshly checked-out repository. For example, for ``npm`` repositories, this may
+include running ``npm install``. It is also necessary to provide the command
+used to run the test suite for the repository.
+
+All dependencies including ``nyc`` must be either available globally, or to the
+commands provided in the install steps or test command. For example, ``npm``
+scripts have access to dependencies in :file:`node_modules/` after running
+`npm_install`, so it is not necessary to install these dependencies globally if
+the test command is run as an npm script.
diff --git a/extension/README.rst b/extension/README.rst
index 78c70a5997c33c68451d66f36fa20d314f69f1a4..63e13fedeba9c5a5464bb32cf20ca82c42fae7e1 100644
--- a/extension/README.rst
+++ b/extension/README.rst
@@ -40,6 +40,9 @@ following tools:
 * `flake8 <https://www.reviewboard.org/docs/reviewbot/latest/tools/flake8/>`_
   – A wrapper around several Python code quality tools
 
+* `Istanbul (nyc)` <https://www.reviewboard.org/docs/reviewbot/latest/tools/nyc/>
+  – A code coverage analysis tool for JavaScript codebases
+
 * `PMD <https://www.reviewboard.org/docs/reviewbot/latest/tools/pmd/>`_
   – A static analysis tool that provides checkers for many languages
 
