diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for finitary
 
+## 2.2.1.0 -- 2026-08-06
+
+* Adds the `typechecker-plugins` manual flag.
+  Disable this flag to compile the library without using typechecker plugins.
+  This does not change the API of the library, but it does entail passing
+  more `KnownNat` dictionaries around at runtime.
+
 ## 2.2.0.1 -- 2026-04-18
 
 * Relax upper bounds:
diff --git a/finitary.cabal b/finitary.cabal
--- a/finitary.cabal
+++ b/finitary.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               finitary
-version:            2.2.0.1
+version:            2.2.1.0
 synopsis:           A better, more type-safe Enum.
 description:
   Provides a type class witnessing that a type has
@@ -33,6 +33,14 @@
   default: True
   manual: True
 
+flag typechecker-plugins
+  description:
+    Use typechecker plugins in the library.
+    With typechecker plugins disabled, some workarounds are used that may
+    cause additional 'KnownNat' dictionaries to be passed at runtime.
+  default: True
+  manual: True
+
 source-repository head
   type:     git
   location: https://codeberg.org/sheaf/finitary.git
@@ -44,13 +52,18 @@
     , base
         >= 4.12     && < 5
     , finite-typelits
-        >= 0.1.4.2 && < 0.3
-    , ghc-typelits-knownnat
-        >= 0.7.2   && < 0.9
-    , ghc-typelits-natnormalise
-        >= 0.7.2   && < 0.10
+        >= 0.1.4.2  && < 0.3
     , template-haskell
-       >= 2.14.0.0 && < 3
+        >= 2.14.0.0 && < 3
+
+  if flag(typechecker-plugins)
+    cpp-options:
+      -DTCPLUGINS
+    build-depends:
+      , ghc-typelits-knownnat
+          >= 0.7.2   && < 0.9
+      , ghc-typelits-natnormalise
+          >= 0.7.2   && < 0.10
 
   if flag(bitvec)
     cpp-options:
diff --git a/src/Data/Finitary.hs b/src/Data/Finitary.hs
--- a/src/Data/Finitary.hs
+++ b/src/Data/Finitary.hs
@@ -1,11 +1,13 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE ConstrainedClassMethods #-}
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DefaultSignatures #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MagicHash #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE Trustworthy #-}
@@ -15,9 +17,16 @@
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE NoStarIsType #-}
 {-# LANGUAGE PatternSynonyms #-}
+
+#ifdef TCPLUGINS
 {-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
 {-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
+#endif
 
+#if __GLASGOW_HASKELL__ >= 914
+{-# OPTIONS_GHC -Wno-pattern-namespace-specifier #-}
+#endif
+
 {-
  - Copyright (C) 2019-2020  Koz Ross <koz.ross@retro-freedom.nz>
  -
@@ -105,6 +114,9 @@
 import Data.Ord (Down (..))
 import Data.Proxy (Proxy (..))
 import Data.Semigroup (All, Any, Dual, First, Last, Max, Min, Product, Sum)
+#if !defined(TCPLUGINS) || defined(VECTOR)
+import Data.Type.Equality ((:~:) (..))
+#endif
 import Data.Void (Void)
 import Data.Word (Word16, Word32, Word64, Word8)
 import GHC.Exts (proxy#)
@@ -121,9 +133,13 @@
     to,
   )
 import GHC.TypeNats
+#ifndef TCPLUGINS
+import Unsafe.Coerce (unsafeCoerce)
+#endif
 
 -- finitary
 import Data.Finitary.TH
+  ( charCardinality, cardinalityOf, adjustmentOf )
 
 -- finite-typelits
 import Data.Finite
@@ -150,7 +166,6 @@
 import Control.Monad (forM_)
 import Control.Monad.Primitive (PrimMonad (..))
 import Control.Monad.ST (ST, runST)
-import Data.Type.Equality ((:~:) (..))
 import Foreign.Storable (Storable)
 
 -- finite-typelits
@@ -290,15 +305,35 @@
   {-# INLINE gToFinite #-}
   gToFinite = toFinite . unK1
 
-instance (GFinitary a, GFinitary b) => GFinitary (a :+: b) where
+instance
+  ( GFinitary a
+  , GFinitary b
+#ifndef TCPLUGINS
+  , KnownNat (GCardinality a + GCardinality b)
+#endif
+  ) => GFinitary (a :+: b) where
   type GCardinality (a :+: b) = GCardinality a + GCardinality b
   {-# INLINE gFromFinite #-}
   gFromFinite = either (L1 . gFromFinite) (R1 . gFromFinite) . separateSum
   {-# INLINABLE gToFinite #-}
-  gToFinite (L1 x) = weakenN . gToFinite $ x
-  gToFinite (R1 x) = shiftN . gToFinite $ x
+  gToFinite (L1 x) =
+#ifndef TCPLUGINS
+    claimLE @( GCardinality a ) @( GCardinality a + GCardinality b ) $
+#endif
+    weakenN . gToFinite $ x
+  gToFinite (R1 x) =
+#ifndef TCPLUGINS
+    claimLE @( GCardinality b ) @( GCardinality a + GCardinality b ) $
+#endif
+    shiftN . gToFinite $ x
 
-instance (GFinitary a, GFinitary b) => GFinitary (a :*: b) where
+instance
+  ( GFinitary a
+  , GFinitary b
+#ifndef TCPLUGINS
+  , KnownNat (GCardinality a * GCardinality b)
+#endif
+  ) => GFinitary (a :*: b) where
   type GCardinality (a :*: b) = GCardinality a * GCardinality b
   {-# INLINABLE gFromFinite #-}
   gFromFinite i =
@@ -553,23 +588,61 @@
   previous = fmap dec . guarded (/= minBound)
 
 -- | @Maybe a@ introduces one additional inhabitant (namely, 'Nothing') to @a@.
-instance (Finitary a) => Finitary (Maybe a)
+instance ( Finitary a
+#ifndef TCPLUGINS
+         , KnownNat (1 + Cardinality a)
+#endif
+         ) => Finitary (Maybe a)
 
 -- | The sum of two finite types will also be finite, with a cardinality equal
 -- to the sum of their cardinalities.
-instance (Finitary a, Finitary b) => Finitary (Either a b)
+instance ( Finitary a, Finitary b
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a + Cardinality b)
+#endif
+         ) => Finitary (Either a b)
 
 -- | The product of two finite types will also be finite, with a cardinality
 -- equal to the product of their cardinalities.
-instance (Finitary a, Finitary b) => Finitary (a, b)
+instance ( Finitary a, Finitary b
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a * Cardinality b)
+#endif
+         ) => Finitary (a, b)
 
-instance (Finitary a, Finitary b, Finitary c) => Finitary (a, b, c)
+instance ( Finitary a, Finitary b, Finitary c
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a * (Cardinality b * Cardinality c))
+         , KnownNat (Cardinality b * Cardinality c)
+#endif
+         ) => Finitary (a, b, c)
 
-instance (Finitary a, Finitary b, Finitary c, Finitary d) => Finitary (a, b, c, d)
+instance ( Finitary a, Finitary b, Finitary c, Finitary d
+#ifndef TCPLUGINS
+         , KnownNat ((Cardinality a * Cardinality b) * (Cardinality c * Cardinality d))
+         , KnownNat (Cardinality a * Cardinality b)
+         , KnownNat (Cardinality c * Cardinality d)
+#endif
+         ) => Finitary (a, b, c, d)
 
-instance (Finitary a, Finitary b, Finitary c, Finitary d, Finitary e) => Finitary (a, b, c, d, e)
+instance ( Finitary a, Finitary b, Finitary c, Finitary d, Finitary e
+#ifndef TCPLUGINS
+         , KnownNat ((Cardinality a * Cardinality b) * (Cardinality c * (Cardinality d * Cardinality e)))
+         , KnownNat (Cardinality a * Cardinality b)
+         , KnownNat (Cardinality c * (Cardinality d * Cardinality e))
+         , KnownNat (Cardinality d * Cardinality e)
+#endif
+         ) => Finitary (a, b, c, d, e)
 
-instance (Finitary a, Finitary b, Finitary c, Finitary d, Finitary e, Finitary f) => Finitary (a, b, c, d, e, f)
+instance ( Finitary a, Finitary b, Finitary c, Finitary d, Finitary e, Finitary f
+#ifndef TCPLUGINS
+         , KnownNat ((Cardinality a * (Cardinality b * Cardinality c)) * (Cardinality d * (Cardinality e * Cardinality f)))
+         , KnownNat (Cardinality a * (Cardinality b * Cardinality c))
+         , KnownNat (Cardinality b * Cardinality c)
+         , KnownNat (Cardinality d * (Cardinality e * Cardinality f))
+         , KnownNat (Cardinality e * Cardinality f)
+#endif
+         ) => Finitary (a, b, c, d, e, f)
 
 instance (Finitary a) => Finitary (Const a b)
 
@@ -608,7 +681,11 @@
 -- with the ordering determined by the @Finitary a@ instance (thus, the
 -- \'first\' such @Vector@ is the one where each element is @start :: a@, and
 -- the \'last\' is the one where each element is @end :: a@).
-instance (Finitary a, KnownNat n) => Finitary (VS.Vector n a) where
+instance ( Finitary a, KnownNat n
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a ^ n)
+#endif
+         ) => Finitary (VS.Vector n a) where
   type Cardinality (VS.Vector n a) = Cardinality a ^ n
   {-# INLINABLE fromFinite #-}
   fromFinite i = runST (go i)
@@ -621,7 +698,11 @@
   {-# INLINE toFinite #-}
   toFinite = roll
 
-instance (Finitary a, VUMS.Unbox a, KnownNat n) => Finitary (VUS.Vector n a) where
+instance ( Finitary a, VUMS.Unbox a, KnownNat n
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a ^ n)
+#endif
+         ) => Finitary (VUS.Vector n a) where
   type Cardinality (VUS.Vector n a) = Cardinality a ^ n
   {-# INLINABLE fromFinite #-}
   fromFinite i = runST (go i)
@@ -634,7 +715,11 @@
   {-# INLINE toFinite #-}
   toFinite = roll
 
-instance (Finitary a, Storable a, KnownNat n) => Finitary (VSS.Vector n a) where
+instance ( Finitary a, Storable a, KnownNat n
+#ifndef TCPLUGINS
+         , KnownNat (Cardinality a ^ n)
+#endif
+         ) => Finitary (VSS.Vector n a) where
   type Cardinality (VSS.Vector n a) = Cardinality a ^ n
   {-# INLINABLE fromFinite #-}
   fromFinite i = runST (go i)
@@ -647,21 +732,41 @@
   {-# INLINE toFinite #-}
   toFinite = roll
 
-unroll :: forall a m v n. (Finitary a, PrimMonad m, KnownNat n, VGM.MVector v a) => VGMS.MVector v n (PrimState m) a -> Finite (Cardinality a ^ n) -> m ()
+unroll
+  :: forall a m v n
+  .  ( Finitary a, PrimMonad m, KnownNat n, VGM.MVector v a )
+  => VGMS.MVector v n (PrimState m) a
+  -> Finite (Cardinality a ^ n)
+  -> m ()
 unroll v acc =
   forM_ @_ @_ @_ @()
     (isLE (Proxy @1) (Proxy @n))
-    ( \Refl -> do
-        let (d, r) = separateProduct @(Cardinality a ^ (n -1)) @(Cardinality a) acc
+    ( \ _pf@Refl ->
+#ifndef TCPLUGINS
+      withPeel @a @n _pf $
+#endif
+      do
+        let (d, r) = separateProduct @(Cardinality a ^ (n - 1)) @(Cardinality a) acc
         let x = fromFinite r
         VGMS.write v 0 x
         unroll (VGMS.tail v) d
     )
 
-roll :: forall a v n. (Finitary a, VG.Vector v a, KnownNat n) => VGS.Vector v n a -> Finite (Cardinality a ^ n)
+roll
+  :: forall a v n
+  .  ( Finitary a, VG.Vector v a, KnownNat n
+#ifndef TCPLUGINS
+     , KnownNat (Cardinality a ^ n)
+#endif
+     )
+  => VGS.Vector v n a
+  -> Finite (Cardinality a ^ n)
 roll v = case isLE (Proxy @1) (Proxy @n) of
   Nothing -> 0
-  Just Refl ->
+  Just _pf@Refl ->
+#ifndef TCPLUGINS
+    withPeel @a @n _pf $
+#endif
     let (h, t) = (VGS.head v, VGS.tail v)
      in combineProduct (roll t, toFinite h)
 #endif
@@ -728,3 +833,61 @@
 {-# INLINE opp #-}
 opp :: forall a. (KnownNat (Cardinality a)) => Finite (Cardinality a) -> Finite (Cardinality a)
 opp = ( maxBound - )
+
+--------------------------------------------------------------------------------
+-- Workarounds when typechecker plugins aren't available.
+
+#ifndef TCPLUGINS
+
+-- | Claim @a <= b@, when a typechecker plugin would otherwise prove it.
+claimLE
+  :: forall (a :: Nat) (b :: Nat) r. ( a <= b => r ) -> r
+claimLE f
+  | Refl <- unsafeCoerce Refl :: ( a <=? b ) :~: True
+  = f
+{-# INLINE claimLE #-}
+
+#ifdef VECTOR
+-- | Claim @a~b@, when a typechecker plugin would otherwise prove it.
+claimEq
+  :: forall (a :: Nat) (b :: Nat) r. ( a ~ b => r ) -> r
+claimEq f
+  | Refl <- unsafeCoerce Refl :: a :~: b
+  = f
+{-# INLINE claimEq #-}
+
+-- | Claim @KnownNat n@, when a typechecker plugin would otherwise prove it.
+claimKN
+  :: forall n r. Natural -> ( KnownNat n => r ) -> r
+claimKN i f = case someNatVal i of
+  SomeNat ( _ :: Proxy m )
+    | Refl <- ( unsafeCoerce Refl :: n :~: m )
+    -> f
+{-# INLINE claimKN #-}
+
+-- | Bring into scope the facts needed to peel the first element off a vector
+-- of length @n@ with elements in @a@, given a proof that @n@ is non-zero.
+withPeel
+  :: forall a n r
+  .  ( Finitary a, KnownNat n )
+  => ( 1 <=? n ) :~: True
+  -> ( ( n ~ 1 + (n - 1)
+       , Cardinality a ^ n ~ (Cardinality a ^ (n - 1)) * Cardinality a
+       , KnownNat (n - 1)
+       , KnownNat (Cardinality a ^ (n - 1))
+       ) => r
+     )
+  -> r
+withPeel Refl f =
+  claimEq @n @( 1 + (n - 1) ) $
+  claimEq @( Cardinality a ^ n ) @( (Cardinality a ^ (n - 1)) * Cardinality a ) $
+  claimKN @(n - 1) m $
+  claimKN @(Cardinality a ^ (n - 1)) (natVal' @(Cardinality a) proxy# ^ m) $
+  f
+  where
+    m :: Natural
+    m = natVal' @n proxy# - 1
+{-# INLINE withPeel #-}
+
+#endif
+#endif
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -6,6 +6,9 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeOperators #-}
 
+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
+
 {-
  - Copyright (C) 2019  Koz Ross <koz.ross@retro-freedom.nz>
  -
@@ -276,4 +279,3 @@
     previous (start @a) `shouldBe` Nothing
   it ("(end :: " <> name <> ") should have no successor") $
     next (end @a) `shouldBe` Nothing
-    
