diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Sridhar Ratnakumar
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# commonmark-simple
+
diff --git a/commonmark-simple.cabal b/commonmark-simple.cabal
new file mode 100644
--- /dev/null
+++ b/commonmark-simple.cabal
@@ -0,0 +1,92 @@
+cabal-version:      2.4
+name:               commonmark-simple
+version:            0.1.0.0
+license:            MIT
+copyright:          2022 Sridhar Ratnakumar
+maintainer:         srid@srid.ca
+author:             Sridhar Ratnakumar
+category:           Text
+
+-- TODO: Before hackage release.
+-- A short (one-line) description of the package.
+synopsis: Simple interface to commonmark-hs
+
+-- A longer description of the package.
+description: Simple interface to parsing Markdown using commonmark-hs
+
+-- A URL where users can report bugs.
+bug-reports: https://github.com/srid/commonmark-simple
+
+extra-source-files:
+  LICENSE
+  README.md
+
+library
+  build-depends:
+    , aeson
+    , base                   >=4.13.0.0 && <=4.18.0.0
+    , commonmark
+    , commonmark-extensions
+    , commonmark-pandoc
+    , containers
+    , megaparsec
+    , pandoc-types
+    , parsec
+    , parser-combinators
+    , relude
+    , yaml
+
+  exposed-modules:    Commonmark.Simple
+  mixins:
+    base hiding (Prelude),
+    relude (Relude as Prelude, Relude.Container.One),
+    relude
+
+  ghc-options:
+    -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns
+    -Wmissing-deriving-strategies -Wunused-foralls -Wunused-foralls
+    -fprint-explicit-foralls -fprint-explicit-kinds
+
+  default-extensions:
+    NoStarIsType
+    BangPatterns
+    ConstraintKinds
+    DataKinds
+    DeriveDataTypeable
+    DeriveFoldable
+    DeriveFunctor
+    DeriveGeneric
+    DeriveLift
+    DeriveTraversable
+    DerivingStrategies
+    DerivingVia
+    EmptyCase
+    EmptyDataDecls
+    EmptyDataDeriving
+    ExistentialQuantification
+    ExplicitForAll
+    FlexibleContexts
+    FlexibleInstances
+    GADTSyntax
+    GeneralisedNewtypeDeriving
+    ImportQualifiedPost
+    KindSignatures
+    LambdaCase
+    MultiParamTypeClasses
+    MultiWayIf
+    NumericUnderscores
+    OverloadedStrings
+    PolyKinds
+    PostfixOperators
+    RankNTypes
+    ScopedTypeVariables
+    StandaloneDeriving
+    StandaloneKindSignatures
+    TupleSections
+    TypeApplications
+    TypeFamilies
+    TypeOperators
+    ViewPatterns
+
+  hs-source-dirs:     src
+  default-language:   Haskell2010
diff --git a/src/Commonmark/Simple.hs b/src/Commonmark/Simple.hs
new file mode 100644
--- /dev/null
+++ b/src/Commonmark/Simple.hs
@@ -0,0 +1,109 @@
+module Commonmark.Simple
+  ( parseMarkdownWithFrontMatter,
+    parseMarkdown,
+    fullMarkdownSpec,
+  )
+where
+
+import Commonmark qualified as CM
+import Commonmark.Extensions qualified as CE
+import Commonmark.Pandoc qualified as CP
+import Control.Monad.Combinators (manyTill)
+import Data.Aeson (FromJSON)
+import Data.Yaml qualified as Y
+import Text.Megaparsec qualified as M
+import Text.Megaparsec.Char qualified as M
+import Text.Pandoc.Builder qualified as B
+import Text.Pandoc.Definition (Pandoc (..))
+
+-- | Parse a Markdown file using commonmark-hs with all extensions enabled
+parseMarkdownWithFrontMatter ::
+  forall meta m il bl.
+  ( FromJSON meta,
+    m ~ Either CM.ParseError,
+    bl ~ CP.Cm () B.Blocks,
+    il ~ CP.Cm () B.Inlines
+  ) =>
+  CM.SyntaxSpec m il bl ->
+  -- | Path to file associated with this Markdown
+  FilePath ->
+  -- | Markdown text to parse
+  Text ->
+  Either Text (Maybe meta, Pandoc)
+parseMarkdownWithFrontMatter spec fn s = do
+  (mMeta, markdown) <- partitionMarkdown fn s
+  mMetaVal <- first show $ (Y.decodeEither' . encodeUtf8) `traverse` mMeta
+  blocks <- first show $ join $ CM.commonmarkWith @(Either CM.ParseError) spec fn markdown
+  let doc = Pandoc mempty $ B.toList . CP.unCm @() @B.Blocks $ blocks
+  pure (mMetaVal, doc)
+
+parseMarkdown :: FilePath -> Text -> Either Text Pandoc
+parseMarkdown fn s = do
+  cmBlocks <- first show $ join $ CM.commonmarkWith @(Either CM.ParseError) fullMarkdownSpec fn s
+  let blocks = B.toList . CP.unCm @() @B.Blocks $ cmBlocks
+  pure $ Pandoc mempty blocks
+
+type SyntaxSpec' m il bl =
+  ( Monad m,
+    CM.IsBlock il bl,
+    CM.IsInline il,
+    Typeable m,
+    Typeable il,
+    Typeable bl,
+    CE.HasEmoji il,
+    CE.HasStrikethrough il,
+    CE.HasPipeTable il bl,
+    CE.HasTaskList il bl,
+    CM.ToPlainText il,
+    CE.HasFootnote il bl,
+    CE.HasMath il,
+    CE.HasDefinitionList il bl,
+    CE.HasDiv bl,
+    CE.HasQuoted il,
+    CE.HasSpan il
+  )
+
+-- | GFM + official commonmark extensions
+fullMarkdownSpec ::
+  SyntaxSpec' m il bl =>
+  CM.SyntaxSpec m il bl
+fullMarkdownSpec =
+  mconcat
+    [ CE.gfmExtensions,
+      CE.fancyListSpec,
+      CE.footnoteSpec,
+      CE.mathSpec,
+      CE.smartPunctuationSpec,
+      CE.definitionListSpec,
+      CE.attributesSpec,
+      CE.rawAttributeSpec,
+      CE.fencedDivSpec,
+      CE.bracketedSpanSpec,
+      CE.autolinkSpec,
+      CM.defaultSyntaxSpec,
+      -- as the commonmark documentation states, pipeTableSpec should be placed after
+      -- fancyListSpec and defaultSyntaxSpec to avoid bad results when parsing
+      -- non-table lines
+      CE.pipeTableSpec
+    ]
+
+-- | Identify metadata block at the top, and split it from markdown body.
+--
+-- FIXME: https://github.com/srid/neuron/issues/175
+partitionMarkdown :: FilePath -> Text -> Either Text (Maybe Text, Text)
+partitionMarkdown =
+  parse (M.try splitP <|> fmap (Nothing,) M.takeRest)
+  where
+    separatorP :: M.Parsec Void Text ()
+    separatorP =
+      void $ M.string "---" <* M.eol
+    splitP :: M.Parsec Void Text (Maybe Text, Text)
+    splitP = do
+      separatorP
+      a <- toText <$> manyTill M.anySingle (M.try $ M.eol *> separatorP)
+      b <- M.takeRest
+      pure (Just a, b)
+    parse :: M.Parsec Void Text a -> String -> Text -> Either Text a
+    parse p fn s =
+      first (toText . M.errorBundlePretty) $
+        M.parse (p <* M.eof) fn s
