diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# effectful-plugin-2.1.0.0 (2026-04-02)
+* Drop support for GHC < 9.6.
+* Consider built-in instances when filtering candidates.
+
 # effectful-plugin-2.0.0.1 (2025-08-30)
 * Small optimization of checking suitable effects for unification.
 * Add `timing` flag for tracking execution time of the plugin.
diff --git a/effectful-plugin.cabal b/effectful-plugin.cabal
--- a/effectful-plugin.cabal
+++ b/effectful-plugin.cabal
@@ -1,7 +1,7 @@
 cabal-version:      3.0
 build-type:         Simple
 name:               effectful-plugin
-version:            2.0.0.1
+version:            2.1.0.0
 license:            BSD-3-Clause
 license-file:       LICENSE
 category:           Control
@@ -17,7 +17,7 @@
 extra-source-files: CHANGELOG.md
                     README.md
 
-tested-with: GHC == { 9.4.8, 9.6.7, 9.8.4, 9.10.2, 9.12.2, 9.14.1 }
+tested-with: GHC == { 9.6.7, 9.8.4, 9.10.3, 9.12.4, 9.14.1 }
 
 bug-reports:   https://github.com/haskell-effectful/effectful/issues
 source-repository head
@@ -35,7 +35,6 @@
 common language
     ghc-options:        -Wall
                         -Wcompat
-                        -Wno-unticked-promoted-constructors
                         -Wmissing-deriving-strategies
                         -Werror=prepositive-qualified-module
 
@@ -55,7 +54,9 @@
                         ImportQualifiedPost
                         LambdaCase
                         MultiParamTypeClasses
+                        NoFieldSelectors
                         NoStarIsType
+                        OverloadedRecordDot
                         PolyKinds
                         RankNTypes
                         RecordWildCards
@@ -68,10 +69,6 @@
                         TypeOperators
                         UndecidableInstances
 
-    if impl(ghc >= 9.4)
-      default-extensions: NoFieldSelectors
-                        , OverloadedRecordDot
-
 library
     import:         language
 
@@ -81,9 +78,9 @@
     if flag(verbose)
       cpp-options: -DVERBOSE
 
-    build-depends:    base                >= 4.16      && < 5
+    build-depends:    base                >= 4.18      && < 5
                     , containers          >= 0.5
-                    , ghc                 >= 9.4       && < 9.15
+                    , ghc                 >= 9.6       && < 9.15
 
     hs-source-dirs: src
 
diff --git a/src/Effectful/Plugin.hs b/src/Effectful/Plugin.hs
--- a/src/Effectful/Plugin.hs
+++ b/src/Effectful/Plugin.hs
@@ -9,7 +9,6 @@
 import Data.Maybe
 import Data.Set qualified as S
 import GHC.Core.Class
-import GHC.Core.InstEnv
 import GHC.Core.Predicate
 import GHC.Core.TyCo.Rep
 import GHC.Core.TyCo.Subst
@@ -18,6 +17,7 @@
 import GHC.Core.Unify
 import GHC.Driver.Env
 import GHC.Driver.Plugins
+import GHC.Tc.Instance.Class
 import GHC.Tc.Plugin
 import GHC.Tc.Types
 import GHC.Tc.Types.Constraint
@@ -31,25 +31,24 @@
 import GHC.Unit.Module
 import GHC.Utils.Outputable qualified as O
 
+#if __GLASGOW_HASKELL__ >= 908
+import GHC.Driver.DynFlags (DynFlags)
+#else
+import GHC.Driver.Session (DynFlags)
+#endif
+
 #if __GLASGOW_HASKELL__ <= 912
-import GHC.Driver.Config.Finder
+import GHC.Driver.Config.Finder (initFinderOpts)
 #endif
 
 #if __GLASGOW_HASKELL__ >= 912
-import GHC.Tc.Types.CtLoc
+import GHC.Tc.Types.CtLoc (CtLoc)
 #endif
 
 #ifdef TIMING
 import GHC.Clock
 #endif
 
-#if __GLASGOW_HASKELL__ <= 904
-type Subst = TCvSubst
-
-isEmptySubst :: Subst -> Bool
-isEmptySubst = isEmptyTCvSubst
-#endif
-
 data EffGiven = EffGiven
   { effCon :: Type
   , eff :: Type
@@ -135,7 +134,7 @@
   printList "Wanteds" allWanteds
   printList "EffWanteds" effWanteds
   printList "OtherWanteds" otherWanteds
-  instEnvs <- getInstEnvs
+  dflags <- hsc_dflags <$> getTopEnv
   solutions <- tcPluginIO $ newIORef []
   forM_ effWanteds $ \wanted -> do
     printSingle "Wanted" wanted
@@ -147,7 +146,7 @@
         emitEqConstraint solutions wanted given
       Right candidates -> do
         printList "Multiple candidates found" $ map fst candidates
-        filterCandidates instEnvs None candidates >>= \case
+        filterCandidates dflags None candidates >>= \case
           None -> printLn "No candidates left"
           Single given -> do
             printSingle "Single candidate left" given
@@ -170,11 +169,11 @@
       $ allWanteds
 
     filterCandidates
-      :: InstEnvs
+      :: DynFlags
       -> Candidates
       -> [(EffGiven, Subst)]
       -> TcPluginM Candidates
-    filterCandidates instEnvs acc = \case
+    filterCandidates dflags acc = \case
       [] -> pure acc
       (given, subst) : rest -> do
         printSingle "Candidate" given
@@ -187,12 +186,12 @@
           True -> do
             printLn "Candidate fits"
             case acc of
-              None -> filterCandidates instEnvs (Single given) rest
+              None -> filterCandidates dflags (Single given) rest
               Single _ -> pure Multiple
               Multiple -> error "unreachable"
           False -> do
             printLn "Candidate doesn't fit, skipping"
-            filterCandidates instEnvs acc rest
+            filterCandidates dflags acc rest
       where
         allWantedsSolvable :: [Type] -> TcPluginM Bool
         allWantedsSolvable = \case
@@ -208,16 +207,27 @@
                   Nothing -> do
                     printLn "Not a class constraint"
                     pure False
-                  Just cls -> case lookupInstEnv False instEnvs cls args of
-                    ([], _, _) -> do
-                      printLn "No matching instances found"
-                      pure False
-                    _ -> do
-                      printLn "Found matching instances"
+                  Just cls -> findMatchingInstances dflags cls args >>= \case
+                    OneInst { cir_what = inst } -> do
+                      printSingle "Single matching instance" inst
                       allWantedsSolvable rest
+                    NoInstance -> do
+                      printLn "No matching instances"
+                      pure False
+                    NotSure -> do
+                      printLn "Multiple matching instances"
+                      pure False
 
 ----------------------------------------
 -- Standalone helpers
+
+findMatchingInstances :: DynFlags -> Class -> [Type] -> TcPluginM ClsInstResult
+findMatchingInstances dflags cls args =
+#if __GLASGOW_HASKELL__ <= 912
+  unsafeTcPluginTcM $ matchGlobalInst dflags False cls args
+#else
+  unsafeTcPluginTcM $ matchGlobalInst dflags False cls args Nothing
+#endif
 
 findPluginModuleCompat :: HscEnv -> ModuleName -> TcPluginM FindResult
 findPluginModuleCompat hsc_env mod_name = do
diff --git a/tests/PluginTests.hs b/tests/PluginTests.hs
--- a/tests/PluginTests.hs
+++ b/tests/PluginTests.hs
@@ -9,6 +9,7 @@
 module Main where
 
 import Data.String
+import Data.Typeable
 import Unsafe.Coerce
 
 import Effectful
@@ -23,6 +24,20 @@
 
 ----------------------------------------
 -- Tests
+
+data X1 = X1 { x1 :: Int }
+data X2 = X2 { x2 :: X1 }
+
+x1x2 :: (State X1 :> es, State X2 :> es) => Eff es ()
+x1x2 = do
+  _ <- gets (.x1)
+  _ <- gets (.x2.x1)
+  pure ()
+
+typeable :: (State X1 :> es, State x :> es) => Eff es ()
+typeable = do
+  _ <- gets typeOf
+  pure ()
 
 data Function i o :: Effect where
   Call :: i -> Function i o m o
