diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Revision history for base64-bytes
 
+## 0.1.1.0 -- 2022-12-07
+
+* Support GHC 9.2
+* Add `decode64`
+* Decode base64-url bytes to Word64
+
 ## 0.1.0.0 -- 2020-06-01
 
 * Initial release.
diff --git a/base64-bytes.cabal b/base64-bytes.cabal
--- a/base64-bytes.cabal
+++ b/base64-bytes.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: base64-bytes
-version: 0.1.0.0
+version: 0.1.1.0
 synopsis: Base64 encoding of byte sequences
 homepage: https://github.com/andrewthad/base64-bytes
 bug-reports: https://github.com/andrewthad/base64-bytes/issues
@@ -16,6 +16,7 @@
 library
   exposed-modules:
     Data.Bytes.Base64
+    Data.Bytes.Base64.Url
   build-depends:
     , base >=4.12 && <5
     , primitive >=0.7 && <0.8
@@ -24,6 +25,7 @@
     , run-st >=0.1 && <0.2
     , bytebuild >=0.3.4 && <0.4
     , natural-arithmetic >=0.1.1 && <0.2
+    , word-compat >=0.0.4
   hs-source-dirs: src
   default-language: Haskell2010
   ghc-options: -Wall -O2
@@ -37,7 +39,7 @@
     , base
     , base64-bytes
     , natural-arithmetic >=0.1.1
-    , small-bytearray-builder
+    , bytebuild
     , primitive
     , byteslice >=0.1.4.0
   ghc-options: -O2 -Wall
diff --git a/src/Data/Bytes/Base64.hs b/src/Data/Bytes/Base64.hs
--- a/src/Data/Bytes/Base64.hs
+++ b/src/Data/Bytes/Base64.hs
@@ -1,11 +1,12 @@
+{-# language BangPatterns #-}
 {-# language BinaryLiterals #-}
 {-# language BlockArguments #-}
 {-# language DataKinds #-}
+{-# language ExplicitNamespaces #-}
 {-# language MagicHash #-}
 {-# language NoStarIsType #-}
-{-# language BangPatterns #-}
+{-# language PatternSynonyms #-}
 {-# language ScopedTypeVariables #-}
-{-# language ExplicitNamespaces #-}
 {-# language TypeApplications #-}
 {-# language TypeOperators #-}
 {-# language UnboxedTuples #-}
@@ -14,6 +15,7 @@
   ( encode
   , builder
   , recodeBoundedBuilder
+  , decode64
   ) where
 
 import GHC.TypeNats (type (+),type (*),Div)
@@ -24,17 +26,20 @@
 import Data.Primitive (ByteArray(..),MutableByteArray(..))
 import Data.Primitive (newByteArray,unsafeFreezeByteArray,readByteArray)
 import Data.Primitive.Ptr (indexOffPtr)
-import Data.Word (Word8)
+import Data.Word (Word8,Word64)
 import GHC.Exts (Ptr(Ptr),Int(I#),State#,(+#),(-#))
 import GHC.ST (ST(ST))
-import GHC.Word (Word(W#),Word32(W32#))
+import GHC.Word (Word(W#),Word32)
+import GHC.Word.Compat (pattern W32#)
 
 import qualified Arithmetic.Nat as Nat
 import qualified Arithmetic.Types as Arithmetic
+import qualified Data.Bytes as Bytes
 import qualified Data.Bytes.Builder.Unsafe as BU
 import qualified Data.Bytes.Builder.Bounded.Unsafe as BBU
 import qualified Data.Primitive.ByteArray.BigEndian as BE
 import qualified Data.Primitive.ByteArray.LittleEndian as LE
+import qualified Data.Primitive.Ptr as PM
 import qualified GHC.Exts as Exts
 
 -- | Encode a byte sequence with base64.
@@ -211,3 +216,34 @@
 
 unST :: ST s a -> State# s -> (# State# s, a #)
 unST (ST f) s = f s
+
+-- Decode a base64-url-encoded 64-bit word. Rejects encoded numbers
+-- greater than or equal to @2^64@. This maps the rightmost byte to
+-- the 6 least significant bits of the word.
+decode64 :: Bytes -> Maybe Word64
+decode64 bs
+  | Bytes.length bs > 10 = Nothing
+  | otherwise = Bytes.foldlM
+      (\ !(acc :: Word64) b -> case PM.indexOffPtr decodeTable (fromIntegral @Word8 @Int b) of
+        0xFF -> Nothing
+        w -> pure $! (unsafeShiftL acc 6 .|. fromIntegral @Word8 @Word64 w)
+      ) 0 bs
+
+decodeTable :: Ptr Word8
+decodeTable = Ptr
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\xff\x3f\
+  \\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\x63\xff\xff\
+  \\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\
+  \\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\xff\
+  \\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\
+  \\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
diff --git a/src/Data/Bytes/Base64/Url.hs b/src/Data/Bytes/Base64/Url.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bytes/Base64/Url.hs
@@ -0,0 +1,47 @@
+{-# language BangPatterns #-}
+{-# language MagicHash #-}
+{-# language ScopedTypeVariables #-}
+{-# language TypeApplications #-}
+
+module Data.Bytes.Base64.Url
+  ( decode64
+  ) where
+
+import Data.Bytes (Bytes)
+import GHC.Exts (Ptr(Ptr))
+import Data.Word (Word8,Word64)
+import Data.Bits (unsafeShiftL,(.|.))
+
+import qualified Data.Bytes as Bytes
+import qualified Data.Primitive.Ptr as PM
+
+-- Decode a base64-url-encoded 64-bit word. Rejects encoded numbers
+-- greater than or equal to @2^64@. This maps the rightmost byte to
+-- the 6 least significant bits of the word. 
+decode64 :: Bytes -> Maybe Word64
+decode64 bs
+  | Bytes.length bs > 10 = Nothing
+  | otherwise = Bytes.foldlM
+      (\ !(acc :: Word64) b -> case PM.indexOffPtr table (fromIntegral @Word8 @Int b) of
+        0xFF -> Nothing
+        w -> pure $! (unsafeShiftL acc 6 .|. fromIntegral @Word8 @Word64 w)
+      ) 0 bs
+
+table :: Ptr Word8
+table = Ptr
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\
+  \\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\x63\xff\xff\
+  \\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\
+  \\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\x3f\
+  \\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\
+  \\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
+  \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,19 +1,25 @@
 {-# language BangPatterns #-}
+{-# language LambdaCase #-}
 
 import Control.Monad (when)
+import Data.Char (ord)
 import Data.Primitive (ByteArray)
 import Data.Word (Word8)
 import Numeric (showHex)
 
 import qualified Arithmetic.Nat as Nat
-import qualified Data.ByteArray.Builder.Bounded as BB
+import qualified Data.Bytes.Builder.Bounded as BB
 import qualified Data.Bytes as Bytes
+import qualified Data.Bytes.Text.Latin1 as Latin1
 import qualified Data.Primitive as PM
 import qualified GHC.Exts as Exts
 import qualified Data.Bytes.Base64 as Base64
+import qualified Data.Bytes.Base64.Url as Base64Url
 
 main :: IO ()
 main = do
+  putStr "Base 64 Encoding\n"
+  putStr "================\n"
   putStr "Encoding: foobar\n"
   putStr "Expected: 5a6d3976596d4679 (Zm9vYmFy)\n"
   putStr "Got:      "
@@ -34,6 +40,24 @@
   putStr "Got:      "
   printHex actualNumbers
   when (actualNumbers /= expectedNumbers) (fail "Did not match")
+  putStr "Base 64 URL Decoding\n"
+  putStr "====================\n"
+  putStr "Decoding: _\n"
+  putStr "Expected: 63\n"
+  underscoreDecodeResult <- case Base64Url.decode64 (Bytes.singleton (c2w '_')) of
+    Nothing -> fail "Could not decode"
+    Just w -> pure w
+  putStr ("Got:      " ++ show underscoreDecodeResult ++ "\n")
+  when (underscoreDecodeResult /= 63) $
+    (fail "Base64 URL decode of single underscore failed")
+  putStr "Decoding: _-a\n"
+  putStr "Expected: 262042\n"
+  urlResB <- case Base64Url.decode64 (Latin1.fromString "_-a") of
+    Nothing -> fail "Could not decode"
+    Just w -> pure w
+  putStr ("Got:      " ++ show urlResB ++ "\n")
+  when (urlResB /= 262042) $
+    (fail "Base64 URL decode of _-a failed")
   putStr "\nAll tests succeeded!\n"
 
 printHex :: ByteArray -> IO ()
@@ -46,13 +70,13 @@
     else "\n"
 
 actualFoobar :: ByteArray
-actualFoobar = Base64.encode (Bytes.fromAsciiString "foobar")
+actualFoobar = Base64.encode (Latin1.fromString "foobar")
 
 actualHelloworld :: ByteArray
-actualHelloworld = Base64.encode (Bytes.fromAsciiString "helloworld")
+actualHelloworld = Base64.encode (Latin1.fromString "helloworld")
 
 actualCamel :: ByteArray
-actualCamel = Base64.encode (Bytes.fromAsciiString "camel")
+actualCamel = Base64.encode (Latin1.fromString "camel")
 
 actualNumbers :: ByteArray
 actualNumbers = BB.run Nat.constant
@@ -78,3 +102,6 @@
   [0x4d,0x54,0x49,0x7a,0x4c,0x6a
   ,0x59,0x33,0x4f,0x44,0x6b,0x3d
   ]
+
+c2w :: Char -> Word8
+c2w = fromIntegral . ord
