diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+1.0.0.0
+
+  * Initial release
diff --git a/library/Skylighting/Modding.hs b/library/Skylighting/Modding.hs
new file mode 100644
--- /dev/null
+++ b/library/Skylighting/Modding.hs
@@ -0,0 +1,153 @@
+{-# OPTIONS_GHC -Wall #-}
+
+{-# LANGUAGE LambdaCase #-}
+
+{- |
+
+Functions for making alterations to @skylighting-core@ values ('SyntaxMap',
+'Syntax', 'Context', 'Rule', etc.).
+
+-}
+
+module Skylighting.Modding
+  (
+  -- * Syntax maps
+    SyntaxMap, syntaxMap, addSyntax, modifySyntax
+
+  -- * Syntax fields
+  , modifySyntaxContexts
+
+  -- * Context maps
+  , ContextMap, contextMap, addContext, modifyContext
+
+  -- * Rules
+  , replaceKeywordRule, isKeywordRule
+
+  ) where
+
+-- containers
+import qualified Data.Map as Map
+import Data.Map (Map)
+
+-- skylighting-core
+import Skylighting.Core hiding (syntaxMap)
+
+-- text
+import Data.Text (Text)
+
+{- |
+
+A map of contexts, keyed by 'cName'.
+
+This is the type of 'sContexts'.
+
+-}
+
+type ContextMap = Map Text Context
+
+{- |
+
+Convert a 'Context' into a 'ContextMap' that contains only that one context.
+
+-}
+
+contextMap :: Context -> ContextMap
+contextMap c = Map.singleton (cName c) c
+
+{- |
+
+Adds one 'Context' to a 'ContextMap', or replaces a context of the same name if
+one exists.
+
+-}
+
+addContext :: Context -> ContextMap -> ContextMap
+addContext c m = Map.insert (cName c) c m
+
+{- |
+
+Modify a 'SyntaxMap' by looking up a particular syntax by name, applying some
+function to it, and placing the resulting syntax into the map in place of the
+original syntax.
+
+-}
+
+modifyContext
+    :: Text -- ^ The name of the context to modify
+    -> (Context -> Context) -> ContextMap -> ContextMap
+
+modifyContext name f m = Map.adjust f name m
+
+{- |
+
+Convert a 'Syntax' into a 'SyntaxMap' that contains only that one syntax.
+
+-}
+
+syntaxMap :: Syntax -> SyntaxMap
+syntaxMap s = Map.singleton (sName s) s
+
+{- |
+
+Adds one 'Syntax' to a 'SyntaxMap', or replaces a context of the same name if
+one exists.
+
+-}
+
+addSyntax :: Syntax -> SyntaxMap -> SyntaxMap
+addSyntax s m = Map.insert (sName s) s m
+
+{- |
+
+Modify a 'SyntaxMap' by looking up a particular syntax by name, applying some
+function to it, and placing the resulting syntax into the map in place of the
+original syntax.
+
+-}
+
+modifySyntax
+    :: Text -- ^ The name of the syntax to modify
+    -> (Syntax -> Syntax) -> SyntaxMap -> SyntaxMap
+
+modifySyntax name f m = Map.adjust f name m
+
+{- |
+
+Apply a function to the 'sContexts' field of a 'Syntax'.
+
+-}
+
+modifySyntaxContexts :: (ContextMap -> ContextMap) -> Syntax -> Syntax
+modifySyntaxContexts f s = s { sContexts = f (sContexts s) }
+
+{- |
+
+Alters a 'Context' by replacing any 'Rule' that looks like a typical keyword
+rule (as determined by 'isKeywordRule') with the given rule.
+
+-}
+
+replaceKeywordRule :: Rule -> (Context -> Context)
+replaceKeywordRule newRule context =
+    let
+        rules =
+            fmap
+                (\r -> if isKeywordRule r then newRule else r)
+                (cRules context)
+    in
+        context { cRules = rules }
+
+{- |
+
+Determines whether a 'Rule' looks like a typical rule for keywords:
+
+  1. 'rAttribute' = 'KeywordTok'
+  2. 'rMatcher' is of the 'Keyword' variety
+
+-}
+
+isKeywordRule :: Rule -> Bool
+isKeywordRule =
+    \case
+        Rule{ rAttribute = KeywordTok, rMatcher = Keyword _ _ } -> True
+        _                                                       -> False
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,18 @@
+Copyright 2019 Typeclass Consulting, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/skylighting-modding.cabal b/skylighting-modding.cabal
new file mode 100644
--- /dev/null
+++ b/skylighting-modding.cabal
@@ -0,0 +1,40 @@
+cabal-version: 2.0
+
+name: skylighting-modding
+version: 1.0.0.0
+
+synopsis: Utilities for modifying Skylighting syntaxes
+category: Text
+
+description:
+    Functions for making alterations to @skylighting-core@ values ('SyntaxMap',
+    'Syntax', 'Context', 'Rule', etc.).
+
+homepage:    https://github.com/typeclasses/pandoc-highlighting-ext
+bug-reports: https://github.com/typeclasses/pandoc-highlighting-ext/issues
+
+author:     Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+copyright: 2018 Typeclass Consulting, LLC
+license: MIT
+license-file: license.txt
+
+build-type: Simple
+tested-with: GHC==8.6.1
+
+extra-source-files:
+    changelog.md
+
+library
+    hs-source-dirs: library
+    default-language: Haskell2010
+
+    exposed-modules:
+        Skylighting.Modding
+
+    build-depends:
+        base >=4.10 && <5
+      , containers
+      , skylighting-core
+      , text
