diff --git a/hspec-core.cabal b/hspec-core.cabal
--- a/hspec-core.cabal
+++ b/hspec-core.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.36.0.
+-- This file has been generated from package.yaml by hpack version 0.37.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:             hspec-core
-version:          2.11.9
+version:          2.11.10
 license:          MIT
 license-file:     LICENSE
 copyright:        (c) 2011-2024 Simon Hengel,
@@ -41,6 +41,7 @@
     , array
     , base >=4.9.0.0 && <5
     , call-stack >=0.2.0
+    , containers
     , deepseq
     , directory
     , filepath
@@ -53,6 +54,12 @@
     , time
     , transformers >=0.2.2.0
   exposed-modules:
+      Test.Hspec.Core.Extension
+      Test.Hspec.Core.Extension.Item
+      Test.Hspec.Core.Extension.Spec
+      Test.Hspec.Core.Extension.Tree
+      Test.Hspec.Core.Extension.Option
+      Test.Hspec.Core.Extension.Config
       Test.Hspec.Core.Spec
       Test.Hspec.Core.Hooks
       Test.Hspec.Core.Runner
@@ -69,6 +76,7 @@
       GetOpt.Declarative.Types
       GetOpt.Declarative.Util
       NonEmpty
+      Test.Hspec.Core.Annotations
       Test.Hspec.Core.Clock
       Test.Hspec.Core.Compat
       Test.Hspec.Core.Config
@@ -76,6 +84,7 @@
       Test.Hspec.Core.Config.Options
       Test.Hspec.Core.Example
       Test.Hspec.Core.Example.Location
+      Test.Hspec.Core.Extension.Config.Type
       Test.Hspec.Core.FailureReport
       Test.Hspec.Core.Formatters.Diff
       Test.Hspec.Core.Formatters.Internal
@@ -125,12 +134,13 @@
     , base >=4.9.0.0 && <5
     , base-orphans
     , call-stack >=0.2.0
+    , containers
     , deepseq
     , directory
     , filepath
     , haskell-lexer
     , hspec-expectations ==0.8.4.*
-    , hspec-meta ==2.11.9
+    , hspec-meta ==2.11.10
     , process
     , quickcheck-io >=0.2.0
     , random
@@ -148,6 +158,7 @@
       GetOpt.Declarative.Types
       GetOpt.Declarative.Util
       NonEmpty
+      Test.Hspec.Core.Annotations
       Test.Hspec.Core.Clock
       Test.Hspec.Core.Compat
       Test.Hspec.Core.Config
@@ -155,6 +166,13 @@
       Test.Hspec.Core.Config.Options
       Test.Hspec.Core.Example
       Test.Hspec.Core.Example.Location
+      Test.Hspec.Core.Extension
+      Test.Hspec.Core.Extension.Config
+      Test.Hspec.Core.Extension.Config.Type
+      Test.Hspec.Core.Extension.Item
+      Test.Hspec.Core.Extension.Option
+      Test.Hspec.Core.Extension.Spec
+      Test.Hspec.Core.Extension.Tree
       Test.Hspec.Core.FailureReport
       Test.Hspec.Core.Format
       Test.Hspec.Core.Formatters
@@ -190,6 +208,7 @@
       Helper
       Mock
       SpecHook
+      Test.Hspec.Core.AnnotationsSpec
       Test.Hspec.Core.ClockSpec
       Test.Hspec.Core.CompatSpec
       Test.Hspec.Core.Config.DefinitionSpec
diff --git a/src/Test/Hspec/Core/Annotations.hs b/src/Test/Hspec/Core/Annotations.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Annotations.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Test.Hspec.Core.Annotations (
+  Annotations
+, setValue
+, getValue
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import           Data.Typeable
+import           Data.Dynamic
+import           Data.Map (Map)
+import qualified Data.Map as Map
+
+newtype Annotations = Annotations (Map TypeRep Dynamic)
+  deriving (
+#if MIN_VERSION_base(4,11,0)
+  Semigroup,
+#endif
+  Monoid)
+
+setValue :: Typeable value => value -> Annotations -> Annotations
+setValue value (Annotations values) = Annotations $ Map.insert (typeOf value) (toDyn value) values
+
+getValue :: forall value. Typeable value => Annotations -> Maybe value
+getValue (Annotations values) = Map.lookup (typeOf (undefined :: value)) values >>= fromDynamic
diff --git a/src/Test/Hspec/Core/Compat.hs b/src/Test/Hspec/Core/Compat.hs
--- a/src/Test/Hspec/Core/Compat.hs
+++ b/src/Test/Hspec/Core/Compat.hs
@@ -19,6 +19,7 @@
 import           Data.Maybe as Imports
 import           Data.Foldable as Imports
 import           GHC.Stack as Imports (HasCallStack, withFrozenCallStack)
+import           Data.Typeable as Imports (Typeable)
 
 import           System.IO
 import           System.Exit
@@ -65,7 +66,7 @@
   , sum
   )
 
-import           Data.Typeable (Typeable, typeOf, typeRepTyCon, tyConModule, tyConName)
+import           Data.Typeable (typeOf, typeRepTyCon, tyConModule, tyConName)
 import           Data.IORef as Imports
 
 #if MIN_VERSION_base(4,12,0)
diff --git a/src/Test/Hspec/Core/Config.hs b/src/Test/Hspec/Core/Config.hs
--- a/src/Test/Hspec/Core/Config.hs
+++ b/src/Test/Hspec/Core/Config.hs
@@ -61,7 +61,7 @@
   }
   where
 
-    mSeed = configSeed config <|> deprecatedQuickCheckSeed config <|> (failureReportSeed <$> mFailureReport)
+    mSeed = getSeed config <|> (failureReportSeed <$> mFailureReport)
     mMaxSuccess = configQuickCheckMaxSuccess config <|> (failureReportMaxSuccess <$> mFailureReport)
     mMaxSize = configQuickCheckMaxSize config <|> (failureReportMaxSize <$> mFailureReport)
     mMaxDiscardRatio = configQuickCheckMaxDiscardRatio config <|> (failureReportMaxDiscardRatio <$> mFailureReport)
diff --git a/src/Test/Hspec/Core/Config/Definition.hs b/src/Test/Hspec/Core/Config/Definition.hs
--- a/src/Test/Hspec/Core/Config/Definition.hs
+++ b/src/Test/Hspec/Core/Config/Definition.hs
@@ -3,16 +3,27 @@
   Config(..)
 , ColorMode(..)
 , UnicodeMode(..)
-, filterOr
 , mkDefaultConfig
 
+, filterOr
+
+, setConfigAnnotation
+, getConfigAnnotation
+
+, addExtensionOptions
+, getExtensionOptions
+
+, getSeed
+, getFormatter
+
 , commandLineOnlyOptions
 , formatterOptions
 , smallCheckOptions
 , quickCheckOptions
 , runnerOptions
 
-, deprecatedQuickCheckSeed
+, flag
+, option
 
 #ifdef TEST
 , splitOn
@@ -26,14 +37,40 @@
 import           System.IO (openTempFile, hClose)
 import           System.Process (system)
 
+import           Test.Hspec.Core.Annotations (Annotations)
+import qualified Test.Hspec.Core.Annotations as Annotations
+
 import           Test.Hspec.Core.Format (Format, FormatConfig)
 import           Test.Hspec.Core.Formatters.Pretty (pretty2)
 import qualified Test.Hspec.Core.Formatters.V1.Monad as V1
+import qualified Test.Hspec.Core.Formatters.V1.Internal as V1 (formatterToFormat)
 import           Test.Hspec.Core.Util
 
 import           GetOpt.Declarative
 
+setConfigAnnotation :: Typeable value => value -> Config -> Config
+setConfigAnnotation value config = config { configAnnotations = Annotations.setValue value $ configAnnotations config }
 
+getConfigAnnotation :: Typeable value => Config -> Maybe value
+getConfigAnnotation = Annotations.getValue . configAnnotations
+
+newtype ExtensionOptions = ExtensionOptions { unExtensionOptions :: [(String, [Option Config])] }
+
+addExtensionOptions :: String -> [Option Config] -> Config -> Config
+addExtensionOptions section opts config = setExtensionOptions ((section, opts) : getExtensionOptions config) config
+
+setExtensionOptions :: [(String, [Option Config])] -> Config -> Config
+setExtensionOptions = setConfigAnnotation . ExtensionOptions
+
+getExtensionOptions :: Config -> [(String, [Option Config])]
+getExtensionOptions = maybe [] unExtensionOptions . getConfigAnnotation
+
+getFormatter :: Config -> Maybe (FormatConfig -> IO Format)
+getFormatter config = configFormat config <|> V1.formatterToFormat <$> configFormatter config
+
+getSeed :: Config -> Maybe Integer
+getSeed config = configSeed config <|> configQuickCheckSeed config
+
 data ColorMode = ColorAuto | ColorNever | ColorAlways
   deriving (Eq, Show)
 
@@ -91,13 +128,11 @@
 , configFormatter :: Maybe V1.Formatter
 , configHtmlOutput :: Bool
 , configConcurrentJobs :: Maybe Int
+, configAnnotations :: Annotations
 }
 {-# DEPRECATED configFormatter "Use [@useFormatter@](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V1.html#v:useFormatter) instead." #-}
 {-# DEPRECATED configQuickCheckSeed "Use `configSeed` instead." #-}
 
-deprecatedQuickCheckSeed :: Config -> Maybe Integer
-deprecatedQuickCheckSeed = configQuickCheckSeed
-
 mkDefaultConfig :: [(String, FormatConfig -> IO Format)] -> Config
 mkDefaultConfig formatters = Config {
   configIgnoreConfigFile = False
@@ -138,6 +173,7 @@
 , configFormatter = Nothing
 , configHtmlOutput = False
 , configConcurrentJobs = Nothing
+, configAnnotations = mempty
 }
 
 defaultDiffContext :: Int
@@ -160,7 +196,7 @@
 option :: String -> OptionSetter config -> String -> Option config
 option name arg help = Option name Nothing arg help True
 
-flag :: String -> (Bool -> Config -> Config) -> String -> Option Config
+flag :: String -> (Bool -> config -> config) -> String -> Option config
 flag name setter = option name (Flag setter)
 
 mkOptionNoArg :: String -> Maybe Char -> (Config -> Config) -> String -> Option Config
@@ -172,7 +208,7 @@
 undocumented :: Option config -> Option config
 undocumented opt = opt {optionDocumented = False}
 
-argument :: String -> (String -> Maybe a) -> (a -> Config -> Config) -> OptionSetter Config
+argument :: String -> (String -> Maybe a) -> (a -> config -> config) -> OptionSetter config
 argument name parser setter = Arg name $ \ input c -> flip setter c <$> parser input
 
 formatterOptions :: [(String, FormatConfig -> IO Format)] -> [Option Config]
diff --git a/src/Test/Hspec/Core/Config/Options.hs b/src/Test/Hspec/Core/Config/Options.hs
--- a/src/Test/Hspec/Core/Config/Options.hs
+++ b/src/Test/Hspec/Core/Config/Options.hs
@@ -11,7 +11,6 @@
 
 import           System.Exit
 
-import           Test.Hspec.Core.Format (Format, FormatConfig)
 import           Test.Hspec.Core.Config.Definition
 import qualified GetOpt.Declarative as Declarative
 import           GetOpt.Declarative.Interpret (parse, interpretOptions, ParseResult(..))
@@ -22,18 +21,21 @@
 envVarName :: String
 envVarName = "HSPEC_OPTIONS"
 
-commandLineOptions :: [(String, FormatConfig -> IO Format)] -> [(String, [Declarative.Option Config])]
-commandLineOptions formatters =
+commandLineOptions :: Config -> [(String, [Declarative.Option Config])]
+commandLineOptions config =
     ("OPTIONS", commandLineOnlyOptions)
-  : otherOptions formatters
+  : otherOptions config
 
-otherOptions :: [(String, FormatConfig -> IO Format)] -> [(String, [Declarative.Option Config])]
-otherOptions formatters = [
+otherOptions :: Config -> [(String, [Declarative.Option Config])]
+otherOptions config = [
     ("RUNNER OPTIONS", runnerOptions)
   , ("FORMATTER OPTIONS", formatterOptions formatters)
   , ("OPTIONS FOR QUICKCHECK", quickCheckOptions)
   , ("OPTIONS FOR SMALLCHECK", smallCheckOptions)
-  ]
+  ] ++ extensionOptions
+  where
+    formatters = configAvailableFormatters config
+    extensionOptions = getExtensionOptions config
 
 ignoreConfigFile :: Config -> [String] -> IO Bool
 ignoreConfigFile config args = do
@@ -52,18 +54,15 @@
   >>= traverse (parseCommandLineOptions prog args)
 
 parseCommandLineOptions :: String -> [String] -> Config -> Either (ExitCode, String) Config
-parseCommandLineOptions prog args config = case Declarative.parseCommandLineOptions (commandLineOptions formatters) prog args config of
+parseCommandLineOptions prog args config = case Declarative.parseCommandLineOptions (commandLineOptions config) prog args config of
   Success c -> Right c
   Help message -> Left (ExitSuccess, message)
   Failure message -> Left (ExitFailure 1, message)
-  where
-    formatters = configAvailableFormatters config
 
 parseEnvironmentOptions :: [(String, String)] -> Config -> Either (ExitCode, String) ([String], Config)
-parseEnvironmentOptions env config = case Declarative.parseEnvironmentOptions "HSPEC" env config (concatMap snd $ commandLineOptions formatters) of
+parseEnvironmentOptions env config = case Declarative.parseEnvironmentOptions "HSPEC" env config (concatMap snd $ commandLineOptions config) of
   (warnings, c) -> Right (map formatWarning warnings, c)
   where
-    formatters = configAvailableFormatters config
     formatWarning (Declarative.InvalidValue name value) = "invalid value `" ++ value ++ "' for environment variable " ++ name
 
 parseFileOptions :: String -> Config -> ConfigFile -> Either (ExitCode, String) Config
@@ -80,9 +79,7 @@
   Left err -> failure err
   where
     options :: [Declarative.Option Config]
-    options = filter Declarative.optionDocumented $ concatMap snd (otherOptions formatters)
-
-    formatters = configAvailableFormatters config
+    options = filter Declarative.optionDocumented $ concatMap snd (otherOptions config)
 
     failure err = Left (ExitFailure 1, prog ++ ": " ++ message)
       where
diff --git a/src/Test/Hspec/Core/Extension.hs b/src/Test/Hspec/Core/Extension.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension.hs
@@ -0,0 +1,112 @@
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+-- | Stability: unstable
+module Test.Hspec.Core.Extension {-# WARNING "This API is experimental." #-} (
+-- * Lifecycle of a test run
+{- |
+A test run goes through four distinct phases:
+
+1. A tree of spec items and a default `Config` are constructed in `SpecM`.
+2. Command-line options are parsed.  This transforms the default `Config` that was constructed in phase 1.
+3. The tree of spec items is transformed in various ways, depending on specific `Config` values.
+4. Each spec item is executed and its result is reported to the user.
+
+An extension can directly influence phase 1, and indirectly influence phases 2-4.
+
+When writing extensions the following imports are recommended:
+
+@
+import "Test.Hspec.Core.Extension"
+import "Test.Hspec.Core.Extension.Config" qualified as Config
+import "Test.Hspec.Core.Extension.Option" qualified as Option
+import "Test.Hspec.Core.Extension.Item" qualified as Item
+import "Test.Hspec.Core.Extension.Spec" qualified as Spec
+import "Test.Hspec.Core.Extension.Tree" qualified as Tree
+@
+-}
+
+-- ** Phase 1: Constructing a spec tree
+{- |
+- An extension can use @Spec.`Test.Hspec.Core.Extension.Spec.mapItems`@ to transform spec items in phase 1.
+
+    * @Item.`setAnnotation`@ can be used to add custom metadata to a spec `Item`.
+
+- An extension can use `modifyConfig` to modify the default config.
+
+    * @Config.`Test.Hspec.Core.Extension.Config.setAnnotation`@ can be used to add custom metadata to the default `Config`.
+-}
+  runIO
+, modifyConfig
+
+-- ** Phase 2: Parsing command-line options
+{- |
+An extension can use `registerOptions` during phase 1 to register custom command-line options, and as a consequence indirectly influence this phase.
+
+* Options can use @Config.`Test.Hspec.Core.Extension.Config.setAnnotation`@ to add custom metadata to the `Config`.
+-}
+, Option
+, registerOptions
+
+-- ** Phase 3: Transforming the spec tree
+{- |
+An extension can use `registerTransformation` during phase 1 to indirectly influence this phase.
+-}
+, registerTransformation
+
+-- ** Phase 4: Reporting results
+{- |
+An extension can register a [custom formatter](https://hspec.github.io/extending-hspec-formatter.html) in phase 1 to indirectly influence this phase.
+-}
+
+-- * Types
+{- |
+Operations on these types are provided by the following modules respectively:
+
+* "Test.Hspec.Core.Extension.Config"
+* "Test.Hspec.Core.Extension.Item"
+* "Test.Hspec.Core.Extension.Spec"
+* "Test.Hspec.Core.Extension.Tree"
+-}
+, Config
+, Item
+, SpecM
+, SpecWith
+, SpecTree
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import qualified Data.CallStack as CallStack
+
+import qualified GetOpt.Declarative as Declarative
+import           Test.Hspec.Core.Spec (SpecM, SpecWith, runIO, modifyConfig)
+import qualified Test.Hspec.Core.Spec as Core
+import qualified Test.Hspec.Core.Config.Definition as Core
+
+import           Test.Hspec.Core.Extension.Config (Config)
+import qualified Test.Hspec.Core.Extension.Config.Type as Config
+import           Test.Hspec.Core.Extension.Option
+import           Test.Hspec.Core.Extension.Item
+import           Test.Hspec.Core.Extension.Tree
+
+registerOptions :: HasCallStack => [Option] -> SpecWith a
+registerOptions = Core.modifyConfig . Core.addExtensionOptions section . map liftOption
+  where
+    section = "OPTIONS FOR " <> package
+    package = maybe "main" (CallStack.srcLocPackage . snd) CallStack.callSite
+
+    liftOption :: Option -> Declarative.Option Core.Config
+    liftOption = Config.unOption
+
+{- |
+Register a transformation that transforms the spec tree in phase 3.
+
+The registered transformation can use:
+
+- @Tree.`mapItems`@ to modify spec items
+- @Tree.`filterItems`@ to discard spec items
+- @Config.`Test.Hspec.Core.Extension.Config.getAnnotation`@ to access custom config metadata
+- @Item.`getAnnotation`@ to access custom item metadata
+-}
+registerTransformation :: (Config -> [SpecTree] -> [SpecTree]) -> SpecWith a
+registerTransformation = modifyConfig . Config.addSpecTransformation
diff --git a/src/Test/Hspec/Core/Extension/Config.hs b/src/Test/Hspec/Core/Extension/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Config.hs
@@ -0,0 +1,19 @@
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+module Test.Hspec.Core.Extension.Config {-# WARNING "This API is experimental." #-} (
+-- * Types
+  Config(..)
+, Path
+, ColorMode(..)
+, UnicodeMode(..)
+
+-- * Operations
+, setAnnotation
+, getAnnotation
+) where
+
+import           Prelude ()
+
+import           Test.Hspec.Core.Format
+import           Test.Hspec.Core.Config.Definition (ColorMode(..), UnicodeMode(..))
+
+import           Test.Hspec.Core.Extension.Config.Type
diff --git a/src/Test/Hspec/Core/Extension/Config/Type.hs b/src/Test/Hspec/Core/Extension/Config/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Config/Type.hs
@@ -0,0 +1,43 @@
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+module Test.Hspec.Core.Extension.Config.Type (
+  Option(..)
+, Config(..)
+
+, setAnnotation
+, getAnnotation
+
+, addSpecTransformation
+, applySpecTransformation
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import qualified GetOpt.Declarative as Declarative
+
+import           Test.Hspec.Core.Config.Definition (Config(..))
+import qualified Test.Hspec.Core.Config.Definition as Core
+
+import           Test.Hspec.Core.Extension.Tree (SpecTree)
+
+newtype Option = Option { unOption :: Declarative.Option Config }
+
+setAnnotation :: Typeable value => value -> Config -> Config
+setAnnotation = Core.setConfigAnnotation
+
+getAnnotation :: Typeable value => Config -> Maybe value
+getAnnotation = Core.getConfigAnnotation
+
+newtype SpecTransformation = SpecTransformation { unSpecTransformation :: Config -> [SpecTree] -> [SpecTree] }
+
+setSpecTransformation :: (Config -> [SpecTree] -> [SpecTree]) -> Config -> Config
+setSpecTransformation = setAnnotation . SpecTransformation
+
+getSpecTransformation :: Config -> Config -> [SpecTree] -> [SpecTree]
+getSpecTransformation = maybe (\ _ -> id) unSpecTransformation . getAnnotation
+
+addSpecTransformation :: (Config -> [SpecTree] -> [SpecTree]) -> Config -> Config
+addSpecTransformation f config = setSpecTransformation (\ c -> f c . getSpecTransformation config c) config
+
+applySpecTransformation :: Core.Config -> [SpecTree] -> [SpecTree]
+applySpecTransformation config = getSpecTransformation config config
diff --git a/src/Test/Hspec/Core/Extension/Item.hs b/src/Test/Hspec/Core/Extension/Item.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Item.hs
@@ -0,0 +1,46 @@
+module Test.Hspec.Core.Extension.Item {-# WARNING "This API is experimental." #-} (
+-- * Types
+  Item(..)
+, Location(..)
+, Params(..)
+, ActionWith
+, Progress
+, ProgressCallback
+, Result(..)
+, ResultStatus(..)
+, FailureReason(..)
+
+-- * Operations
+, isFocused
+, pending
+, pendingWith
+, setAnnotation
+, getAnnotation
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import           Test.Hspec.Core.Spec hiding (pending, pendingWith)
+import           Test.Hspec.Core.Tree
+
+isFocused :: Item a -> Bool
+isFocused = itemIsFocused
+
+pending :: Item a -> Item a
+pending item = item { itemExample = \ _params _hook _progress -> result }
+  where
+    result :: IO Result
+    result = return $ Result "" (Pending Nothing Nothing)
+
+pendingWith :: String -> Item a -> Item a
+pendingWith reason item = item { itemExample = \ _params _hook _progress -> result }
+  where
+    result :: IO Result
+    result = return $ Result "" (Pending Nothing (Just reason))
+
+setAnnotation :: Typeable value => value -> Item a -> Item a
+setAnnotation = setItemAnnotation
+
+getAnnotation :: Typeable value => Item a -> Maybe value
+getAnnotation = getItemAnnotation
diff --git a/src/Test/Hspec/Core/Extension/Option.hs b/src/Test/Hspec/Core/Extension/Option.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Option.hs
@@ -0,0 +1,27 @@
+module Test.Hspec.Core.Extension.Option {-# WARNING "This API is experimental." #-} (
+  Option
+, flag
+, argument
+, noArgument
+, optionalArgument
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import qualified GetOpt.Declarative as Declarative
+import qualified Test.Hspec.Core.Config.Definition as Core
+
+import           Test.Hspec.Core.Extension.Config.Type (Option(..), Config)
+
+flag :: String -> (Bool -> Config -> Config) -> String -> Option
+flag name setter = Option . Core.flag name setter
+
+argument :: String -> String -> (String -> Config -> Maybe Config) -> String -> Option
+argument name argumentName setter = Option . Core.option name (Declarative.Arg argumentName setter)
+
+optionalArgument :: String -> String -> (Maybe String -> Config -> Maybe Config) -> String -> Option
+optionalArgument name argumentName setter = Option . Core.option name (Declarative.OptArg argumentName setter)
+
+noArgument :: String -> (Config -> Config) -> String -> Option
+noArgument name setter help = Option $ Declarative.Option name Nothing (Declarative.NoArg setter) help False
diff --git a/src/Test/Hspec/Core/Extension/Spec.hs b/src/Test/Hspec/Core/Extension/Spec.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Spec.hs
@@ -0,0 +1,10 @@
+module Test.Hspec.Core.Extension.Spec {-# WARNING "This API is experimental." #-} (
+  mapItems
+) where
+
+import           Prelude ()
+
+import           Test.Hspec.Core.Spec
+
+mapItems :: (Item a -> Item b) -> SpecWith a -> SpecWith b
+mapItems = mapSpecItem_
diff --git a/src/Test/Hspec/Core/Extension/Tree.hs b/src/Test/Hspec/Core/Extension/Tree.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Core/Extension/Tree.hs
@@ -0,0 +1,19 @@
+module Test.Hspec.Core.Extension.Tree {-# WARNING "This API is experimental." #-} (
+  SpecTree
+, mapItems
+, filterItems
+) where
+
+import           Prelude ()
+import           Test.Hspec.Core.Compat
+
+import           Test.Hspec.Core.Spec hiding (SpecTree)
+import qualified Test.Hspec.Core.Spec as Core
+
+type SpecTree = Core.SpecTree ()
+
+mapItems :: (Item () -> Item ()) -> [SpecTree] -> [SpecTree]
+mapItems = map . fmap
+
+filterItems :: (Item () -> Bool) -> [SpecTree] -> [SpecTree]
+filterItems = filterForest
diff --git a/src/Test/Hspec/Core/Formatters/Internal.hs b/src/Test/Hspec/Core/Formatters/Internal.hs
--- a/src/Test/Hspec/Core/Formatters/Internal.hs
+++ b/src/Test/Hspec/Core/Formatters/Internal.hs
@@ -60,7 +60,7 @@
 
 import qualified System.IO as IO
 import           System.IO (stdout)
-import           System.Console.ANSI
+import           System.Console.ANSI hiding (clearLine)
 import           Control.Monad.Trans.Reader (ReaderT(..), ask)
 import           Control.Monad.IO.Class
 import           Data.Char (isSpace)
@@ -271,11 +271,20 @@
 writeTransient :: String -> FormatM ()
 writeTransient new = do
   reportProgress <- getConfigValue formatConfigReportProgress
-  when reportProgress $ do
-    write new
-    liftIO $ IO.hFlush stdout
-    write $ "\r" ++ replicate (length new) ' ' ++ "\r"
+  when reportProgress . liftIO $ do
+    bracket_ disableLineWrapping enableLineWrapping $ writePlain new
+    IO.hFlush stdout
+    clearLine
+  where
+    disableLineWrapping :: IO ()
+    disableLineWrapping = writePlain "\ESC[?7l"
 
+    enableLineWrapping :: IO ()
+    enableLineWrapping = writePlain "\ESC[?7h"
+
+    clearLine :: IO ()
+    clearLine = writePlain "\r\ESC[K"
+
 -- | Append some output to the report.
 write :: String -> FormatM ()
 write = mapM_ writeChunk . splitLines
@@ -288,13 +297,16 @@
 writeChunk :: String -> FormatM ()
 writeChunk str = do
   let
-    plainOutput = IO.hPutStr stdout str
+    plainOutput = writePlain str
     colorOutput color = bracket_ (hSetSGR stdout [color]) (hSetSGR stdout [Reset]) plainOutput
   mColor <- gets stateColor
   liftIO $ case mColor of
     Just (SetColor Foreground _ _) | all isSpace str -> plainOutput
     Just color -> colorOutput color
     Nothing -> plainOutput
+
+writePlain :: String -> IO ()
+writePlain = IO.hPutStr stdout
 
 -- | Set output color to red, run given action, and finally restore the default
 -- color.
diff --git a/src/Test/Hspec/Core/Formatters/V2.hs b/src/Test/Hspec/Core/Formatters/V2.hs
--- a/src/Test/Hspec/Core/Formatters/V2.hs
+++ b/src/Test/Hspec/Core/Formatters/V2.hs
@@ -188,7 +188,7 @@
       Success {} -> pass
       Failure {} -> pass
       Pending _ reason -> withPendingColor $ do
-        writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason
+        indentBy (indentationFor ("" : nesting)) $ "# PENDING: " ++ fromMaybe "No reason given" reason
 } where
     indentationFor nesting = replicate (length nesting * 2) ' '
 
@@ -198,8 +198,7 @@
       write $ indentationFor nesting ++ requirement ++ " ["
       withColor $ write symbol
       writeLine $ "]" ++ if shouldPrintTimes then times else ""
-      forM_ (lines info) $ \ s ->
-        writeLine $ indentationFor ("" : nesting) ++ s
+      indentBy (indentationFor ("" : nesting)) info
       where
         dt :: Int
         dt = toMilliseconds duration
@@ -233,7 +232,7 @@
         writeResult nesting requirement duration info
       Pending _ reason -> withPendingColor $ do
         writeResult nesting requirement duration info
-        writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason
+        indentBy (indentationFor ("" : nesting)) $ "# PENDING: " ++ fromMaybe "No reason given" reason
       Failure {} -> withFailColor $ do
         n <- getFailCount
         writeResult nesting (requirement ++ " FAILED [" ++ show n ++ "]") duration info
@@ -245,8 +244,7 @@
     writeResult nesting requirement (Seconds duration) info = do
       shouldPrintTimes <- printTimes
       writeLine $ indentationFor nesting ++ requirement ++ if shouldPrintTimes then times else ""
-      forM_ (lines info) $ \ s ->
-        writeLine $ indentationFor ("" : nesting) ++ s
+      indentBy (indentationFor ("" : nesting)) info
       where
         dt :: Int
         dt = floor (duration * 1000)
@@ -362,9 +360,12 @@
         writeLine ("  To rerun use: --match " ++ path_ <> " --seed " <> show seed)
       where
         indentation = "       "
-        indent message = do
-          forM_ (lines message) $ \line -> do
-            writeLine (indentation ++ line)
+        indent = indentBy indentation
+
+indentBy :: String -> String -> FormatM ()
+indentBy indentation message = do
+  forM_ (lines message) $ \ line -> do
+    writeLine (indentation ++ line)
 
 data Chunk = Original String | Modified String | OmittedLines Int
   deriving (Eq, Show)
diff --git a/src/Test/Hspec/Core/Runner.hs b/src/Test/Hspec/Core/Runner.hs
--- a/src/Test/Hspec/Core/Runner.hs
+++ b/src/Test/Hspec/Core/Runner.hs
@@ -117,8 +117,9 @@
 import           Test.Hspec.Core.Spec hiding (pruneTree, pruneForest)
 import           Test.Hspec.Core.Tree (formatDefaultDescription)
 import           Test.Hspec.Core.Config
+import           Test.Hspec.Core.Config.Definition as Config (getSeed, getFormatter)
+import           Test.Hspec.Core.Extension.Config.Type as Extension (applySpecTransformation)
 import           Test.Hspec.Core.Format (Format, FormatConfig(..))
-import qualified Test.Hspec.Core.Formatters.V1 as V1
 import qualified Test.Hspec.Core.Formatters.V2 as V2
 import           Test.Hspec.Core.FailureReport
 import           Test.Hspec.Core.QuickCheck.Util
@@ -197,7 +198,7 @@
 -- for all properties.  This helps with --seed and --rerun.
 ensureSeed :: Config -> IO (Config, Integer)
 ensureSeed config = do
-  seed <- case configSeed config <|> configQuickCheckSeed config of
+  seed <- case Config.getSeed config of
     Nothing -> toInteger <$> newSeed
     Just seed -> return seed
   return (config { configSeed = Just seed }, seed)
@@ -391,7 +392,7 @@
       , formatConfigExpertMode = configExpertMode config
       }
 
-      formatter = fromMaybe (V2.formatterToFormat V2.checks) (configFormat config <|> V1.formatterToFormat <$> configFormatter config)
+      formatter = fromMaybe (V2.formatterToFormat V2.checks) (Config.getFormatter config)
 
     format <- maybe id printSlowSpecItems (configPrintSlowItems config) <$> formatter formatConfig
 
@@ -418,6 +419,7 @@
   >>> addDefaultDescriptions
   >>> failFocusedItems config
   >>> failPendingItems config
+  >>> Extension.applySpecTransformation config
   >>> focusSpec config
   >>> toEvalItemForest params
   >>> applyDryRun config
@@ -450,7 +452,7 @@
 toEvalItemForest params = bimapForest id toEvalItem . filterForest itemIsFocused
   where
     toEvalItem :: Item () -> EvalItem
-    toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem {
+    toEvalItem (Item requirement loc isParallelizable _isFocused _annotations e) = EvalItem {
       evalItemDescription = requirement
     , evalItemLocation = loc
     , evalItemConcurrency = if isParallelizable == Just True then Concurrent else Sequential
diff --git a/src/Test/Hspec/Core/Tree.hs b/src/Test/Hspec/Core/Tree.hs
--- a/src/Test/Hspec/Core/Tree.hs
+++ b/src/Test/Hspec/Core/Tree.hs
@@ -24,6 +24,9 @@
 , callSite
 , formatDefaultDescription
 , toModuleName
+
+, setItemAnnotation
+, getItemAnnotation
 ) where
 
 import           Prelude ()
@@ -34,6 +37,8 @@
 import qualified Data.CallStack as CallStack
 
 import           Test.Hspec.Core.Example
+import           Test.Hspec.Core.Annotations (Annotations)
+import qualified Test.Hspec.Core.Annotations as Annotations
 
 -- | Internal tree data structure
 data Tree c a =
@@ -114,10 +119,21 @@
   -- | A flag that indicates whether this spec item is focused
 , itemIsFocused :: Bool
 
+  -- | Arbitrary additional data that can be used by third-party extensions.
+  --
+  -- @since 2.12.0
+, itemAnnotations :: Annotations
+
   -- | Example for behavior
 , itemExample :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result
 }
 
+setItemAnnotation :: Typeable value => value -> Item a -> Item a
+setItemAnnotation value config = config { itemAnnotations = Annotations.setValue value $ itemAnnotations config }
+
+getItemAnnotation :: Typeable value => Item a -> Maybe value
+getItemAnnotation = Annotations.getValue . itemAnnotations
+
 -- | The @specGroup@ function combines a list of specs into a larger spec.
 specGroup :: HasCallStack => String -> [SpecTree a] -> SpecTree a
 specGroup s = Node msg
@@ -134,6 +150,7 @@
   , itemLocation = location
   , itemIsParallelizable = Nothing
   , itemIsFocused = False
+  , itemAnnotations = mempty
   , itemExample = safeEvaluateExample e
   }
 
diff --git a/test/Test/Hspec/Core/AnnotationsSpec.hs b/test/Test/Hspec/Core/AnnotationsSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/Core/AnnotationsSpec.hs
@@ -0,0 +1,29 @@
+module Test.Hspec.Core.AnnotationsSpec (spec) where
+
+import           Prelude ()
+import           Helper
+
+import           Test.Hspec.Core.Annotations
+
+newtype A = A Int
+  deriving (Eq, Show)
+
+newtype B = B Int
+  deriving (Eq, Show)
+
+spec :: Spec
+spec = do
+  describe "Annotations" $ do
+    it "can store a value" $ do
+      let annotations = setValue (A 23) mempty
+      getValue annotations `shouldBe` Just (A 23)
+
+    it "can store multiple values of different types" $ do
+      let annotations = setValue (B 42) $ setValue (A 23) mempty
+      getValue annotations `shouldBe` Just (A 23)
+      getValue annotations `shouldBe` Just (B 42)
+
+    context "when a value of the same type is added multiple times" $ do
+      it "gives precedence to the value that was added last" $ do
+        let annotations = setValue (A 42) $  setValue (A 23) mempty
+        getValue annotations `shouldBe` Just (A 42)
diff --git a/test/Test/Hspec/Core/CompatSpec.hs b/test/Test/Hspec/Core/CompatSpec.hs
--- a/test/Test/Hspec/Core/CompatSpec.hs
+++ b/test/Test/Hspec/Core/CompatSpec.hs
@@ -5,7 +5,6 @@
 import           Helper
 
 import           System.Environment
-import           Data.Typeable
 
 data SomeType = SomeType
   deriving Typeable
diff --git a/test/Test/Hspec/Core/RunnerSpec.hs b/test/Test/Hspec/Core/RunnerSpec.hs
--- a/test/Test/Hspec/Core/RunnerSpec.hs
+++ b/test/Test/Hspec/Core/RunnerSpec.hs
@@ -1,3 +1,4 @@
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
@@ -27,6 +28,7 @@
 import qualified Test.Hspec.Core.Hooks as H
 
 import           Test.Hspec.Core.Formatters.Pretty.ParserSpec (Person(..))
+import           Test.Hspec.Core.Extension ()
 
 runPropFoo :: [String] -> IO String
 runPropFoo args = unlines . normalizeSummary . lines <$> do
diff --git a/version.yaml b/version.yaml
--- a/version.yaml
+++ b/version.yaml
@@ -1,4 +1,4 @@
-version: &version 2.11.9
+version: &version 2.11.10
 synopsis: A Testing Framework for Haskell
 author: Simon Hengel <sol@typeful.net>
 maintainer: Simon Hengel <sol@typeful.net>
