diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.3.1.0
+
+* Add `Bluefin.Internal.DslBuilder`
+
 # 0.3.0.0
 
 * Add `Bluefin.Internal.OneWayCoercible` module and related instances
diff --git a/bluefin-internal.cabal b/bluefin-internal.cabal
--- a/bluefin-internal.cabal
+++ b/bluefin-internal.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               bluefin-internal
-version:            0.3.0.0
+version:            0.3.1.0
 license:            MIT
 license-file:       LICENSE
 author:             Tom Ellis
@@ -83,6 +83,8 @@
       monad-control < 1.1
     exposed-modules:
       Bluefin.Internal,
+      Bluefin.Internal.DslBuilder,
+      Bluefin.Internal.DslBuilderEffects,
       Bluefin.Internal.Examples,
       Bluefin.Internal.Exception.Scoped,
       Bluefin.Internal.Key,
diff --git a/src/Bluefin/Internal/DslBuilder.hs b/src/Bluefin/Internal/DslBuilder.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/Internal/DslBuilder.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE QuantifiedConstraints #-}
+
+module Bluefin.Internal.DslBuilder where
+
+import Bluefin.Internal
+import Bluefin.Internal.DslBuilderEffects
+  ( DslBuilderEffects,
+    dslBuilderEffects,
+    runDslBuilderEffects,
+  )
+
+newtype Forall f r = MkForall {unForall :: forall es. f es r}
+
+mkForall :: (forall es. f es r) -> Forall f r
+mkForall = MkForall
+
+instance (forall es. Functor (f es)) => Functor (Forall f) where
+  fmap f k = MkForall (fmap f (unForall k))
+
+instance (forall es. Applicative (f es)) => Applicative (Forall f) where
+  pure x = mkForall (pure x)
+  f <*> x = mkForall (unForall f <*> unForall x)
+
+instance (forall es. Monad (f es)) => Monad (Forall f) where
+  m >>= f = mkForall $ do
+    r <- unForall m
+    unForall (f r)
+
+newtype DslBuilder h r
+  = MkDslBuilder {unMkDslBuilder :: Forall (DslBuilderEffects h) r}
+
+runDslBuilder :: (Handle h) => h es -> DslBuilder h r -> Eff es r
+runDslBuilder h f = runDslBuilderEffects h (unForall (unMkDslBuilder f))
+
+dslBuilder :: (forall e. h e -> Eff e r) -> DslBuilder h r
+dslBuilder k = MkDslBuilder (mkForall (dslBuilderEffects (useImpl . k)))
+
+instance (Handle h) => Functor (DslBuilder h) where
+  fmap f g = dslBuilder (\h -> fmap f (runDslBuilder h g))
+
+instance (Handle h) => Applicative (DslBuilder h) where
+  pure x = dslBuilder (pure (pure x))
+  f <*> x = dslBuilder (\h -> runDslBuilder h f <*> runDslBuilder h x)
+
+instance (Handle h) => Monad (DslBuilder h) where
+  m >>= f = dslBuilder $ \h -> do
+    r <- runDslBuilder h m
+    runDslBuilder h (f r)
diff --git a/src/Bluefin/Internal/DslBuilderEffects.hs b/src/Bluefin/Internal/DslBuilderEffects.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/Internal/DslBuilderEffects.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+
+module Bluefin.Internal.DslBuilderEffects where
+
+import Bluefin.Internal
+import Bluefin.Internal.OneWayCoercible
+  ( OneWayCoercible,
+    oneWayCoercible,
+    oneWayCoercibleImpl,
+  )
+
+newtype DslBuilderEffects h es r
+  = MkDslBuilderEffects {unMkDslBuilderEffects :: forall e. h e -> Eff (e :& es) r}
+
+useImplDslBuilderEffects :: (e :> es) => DslBuilderEffects h e r -> DslBuilderEffects h es r
+useImplDslBuilderEffects (MkDslBuilderEffects f) = MkDslBuilderEffects (useImplUnder . f)
+
+runDslBuilderEffects :: h es -> DslBuilderEffects h es r -> Eff es r
+runDslBuilderEffects h f = makeOp (unMkDslBuilderEffects f h)
+
+dslBuilderEffects :: (forall e. h e -> Eff (e :& es) r) -> DslBuilderEffects h es r
+dslBuilderEffects = MkDslBuilderEffects
+
+instance
+  (e :> es) =>
+  OneWayCoercible (DslBuilderEffects h e r) (DslBuilderEffects h es r)
+  where
+  oneWayCoercibleImpl = oneWayCoercible
+
+instance (Handle h) => Functor (DslBuilderEffects h es) where
+  fmap f g =
+    dslBuilderEffects $ \h ->
+      fmap f (runDslBuilderEffects (mapHandle h) (useImplDslBuilderEffects g))
+
+instance (Handle h) => Applicative (DslBuilderEffects h es) where
+  pure x = dslBuilderEffects (pure (pure x))
+  f <*> x = dslBuilderEffects $ \h ->
+    runDslBuilderEffects (mapHandle h) (useImplDslBuilderEffects f)
+      <*> runDslBuilderEffects (mapHandle h) (useImplDslBuilderEffects x)
+
+instance (Handle h) => Monad (DslBuilderEffects h es) where
+  m >>= f = dslBuilderEffects $ \h -> do
+    r <- runDslBuilderEffects (mapHandle h) (useImplDslBuilderEffects m)
+    runDslBuilderEffects (mapHandle h) (useImplDslBuilderEffects (f r))
