diff --git a/hls-plugin-api.cabal b/hls-plugin-api.cabal
--- a/hls-plugin-api.cabal
+++ b/hls-plugin-api.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name:          hls-plugin-api
-version:       1.1.0.0
+version:       1.1.0.1
 synopsis:      Haskell Language Server API for plugin communication
 description:
   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
@@ -50,7 +50,7 @@
     , opentelemetry
     , process
     , regex-tdfa            >=1.3.1.0
-    , shake                 >=0.17.5
+    , hls-graph             ^>=1.3
     , text
     , unordered-containers
 
diff --git a/src/Ide/Plugin/ConfigUtils.hs b/src/Ide/Plugin/ConfigUtils.hs
--- a/src/Ide/Plugin/ConfigUtils.hs
+++ b/src/Ide/Plugin/ConfigUtils.hs
@@ -11,6 +11,7 @@
 import qualified Data.Dependent.Map    as DMap
 import qualified Data.Dependent.Sum    as DSum
 import qualified Data.HashMap.Lazy     as HMap
+import           Data.List             (nub)
 import           Ide.Plugin.Config
 import           Ide.Plugin.Properties (toDefaultJSON, toVSCodeExtensionSchema)
 import           Ide.Types
@@ -49,7 +50,7 @@
     --     }
     --   }
     -- }
-    singlePlugin PluginDescriptor {..} =
+    singlePlugin PluginDescriptor {pluginConfigDescriptor = ConfigDescriptor {..}, ..} =
       let x = genericDefaultConfig <> dedicatedDefaultConfig
        in [pId A..= A.object x | not $ null x]
       where
@@ -58,20 +59,17 @@
         -- Example:
         --
         -- {
-        --   "globalOn": true,
         --   "codeActionsOn": true,
         --   "codeLensOn": true
         -- }
         --
-        -- we don't generate the config section if the plugin doesn't register any of the following six methods,
-        -- which avoids producing trivial configuration for formatters:
-        --
-        -- "stylish-haskell": {
-        --    "globalOn": true
-        -- }
         genericDefaultConfig =
-          let x = mconcat (handlersToGenericDefaultConfig <$> handlers)
-           in ["globalOn" A..= True | not $ null x] <> x
+          let x = ["diagnosticsOn" A..= True | configHasDiagnostics] <> nub (mconcat (handlersToGenericDefaultConfig <$> handlers))
+           in case x of
+                -- if the plugin has only one capability, we produce globalOn instead of the specific one;
+                -- otherwise we don't produce globalOn at all
+                [_] -> ["globalOn" A..= True]
+                _   -> x
         -- Example:
         --
         -- {
@@ -80,7 +78,7 @@
         --   }
         --}
         dedicatedDefaultConfig =
-          let x = customConfigToDedicatedDefaultConfig pluginCustomConfig
+          let x = customConfigToDedicatedDefaultConfig configCustomConfig
            in ["config" A..= A.object x | not $ null x]
 
         (PluginId pId) = pluginId
@@ -101,13 +99,21 @@
 pluginsToVSCodeExtensionSchema :: IdePlugins a -> A.Value
 pluginsToVSCodeExtensionSchema IdePlugins {..} = A.object $ mconcat $ singlePlugin <$> map snd ipMap
   where
-    singlePlugin PluginDescriptor {..} = genericSchema <> dedicatedSchema
+    singlePlugin PluginDescriptor {pluginConfigDescriptor = ConfigDescriptor {..}, ..} = genericSchema <> dedicatedSchema
       where
         (PluginHandlers (DMap.toList -> handlers)) = pluginHandlers
         customConfigToDedicatedSchema (CustomConfig p) = toVSCodeExtensionSchema (withIdPrefix "config.") p
         (PluginId pId) = pluginId
-        genericSchema = withIdPrefix "globalOn" A..= schemaEntry "plugin" : mconcat (handlersToGenericSchema <$> handlers)
-        dedicatedSchema = customConfigToDedicatedSchema pluginCustomConfig
+        genericSchema =
+          let x =
+                [withIdPrefix "diagnosticsOn" A..= schemaEntry "diagnostics" | configHasDiagnostics]
+                  <> nub (mconcat (handlersToGenericSchema <$> handlers))
+           in case x of
+                -- If the plugin has only one capability, we produce globalOn instead of the specific one;
+                -- otherwise we don't produce globalOn at all
+                [_] -> [withIdPrefix "globalOn" A..= schemaEntry "plugin"]
+                _   -> x
+        dedicatedSchema = customConfigToDedicatedSchema configCustomConfig
         handlersToGenericSchema (IdeMethod m DSum.:=> _) = case m of
           STextDocumentCodeAction -> [withIdPrefix "codeActionsOn" A..= schemaEntry "code actions"]
           STextDocumentCodeLens -> [withIdPrefix "codeLensOn" A..= schemaEntry "code lenses"]
diff --git a/src/Ide/Types.hs b/src/Ide/Types.hs
--- a/src/Ide/Types.hs
+++ b/src/Ide/Types.hs
@@ -38,7 +38,7 @@
 import           Data.String
 import qualified Data.Text                       as T
 import           Data.Text.Encoding              (encodeUtf8)
-import           Development.Shake               hiding (command)
+import           Development.IDE.Graph
 import           GHC.Generics
 import           Ide.Plugin.Config
 import           Ide.Plugin.Properties
@@ -63,19 +63,47 @@
                    , pluginRules        :: !(Rules ())
                    , pluginCommands     :: ![PluginCommand ideState]
                    , pluginHandlers     :: PluginHandlers ideState
-                   , pluginCustomConfig :: CustomConfig
+                   , pluginConfigDescriptor :: ConfigDescriptor
                    , pluginNotificationHandlers :: PluginNotificationHandlers ideState
                    }
 
--- | An existential wrapper of 'Properties', used only for documenting and generating config templates
+-- | An existential wrapper of 'Properties'
 data CustomConfig = forall r. CustomConfig (Properties r)
 
-emptyCustomConfig :: CustomConfig
-emptyCustomConfig = CustomConfig emptyProperties
+-- | Describes the configuration a plugin.
+-- A plugin may be configurable in such form:
+-- @
+-- {
+--  "plugin-id": {
+--    "globalOn": true,
+--    "codeActionsOn": true,
+--    "codeLensOn": true,
+--    "config": {
+--      "property1": "foo"
+--     }
+--   }
+-- }
+-- @
+-- @globalOn@, @codeActionsOn@, and @codeLensOn@ etc. are called generic configs,
+-- which can be inferred from handlers registered by the plugin.
+-- @config@ is called custom config, which is defined using 'Properties'.
+data ConfigDescriptor = ConfigDescriptor {
+  -- | Whether or not to generate generic configs.
+  configEnableGenericConfig :: Bool,
+  -- | Whether or not to generate @diagnosticsOn@ config.
+  -- Diagnostics emit in arbitrary shake rules,
+  -- so we can't know statically if the plugin produces diagnostics
+  configHasDiagnostics      :: Bool,
+  -- | Custom config.
+  configCustomConfig        :: CustomConfig
+}
 
 mkCustomConfig :: Properties r -> CustomConfig
 mkCustomConfig = CustomConfig
 
+defaultConfigDescriptor :: ConfigDescriptor
+defaultConfigDescriptor = ConfigDescriptor True False (mkCustomConfig emptyProperties)
+
 -- | Methods that can be handled by plugins.
 -- 'ExtraParams' captures any extra data the IDE passes to the handlers for this method
 -- Only methods for which we know how to combine responses can be instances of 'PluginMethod'
@@ -267,7 +295,7 @@
     mempty
     mempty
     mempty
-    emptyCustomConfig
+    defaultConfigDescriptor
     mempty
 
 newtype CommandId = CommandId T.Text
