diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for eventuo11y-prometheus
+
+## 0.1.0.0 -- 2023-02-27
+
+Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2023 Shea Levy.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this project except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/eventuo11y-prometheus.cabal b/eventuo11y-prometheus.cabal
new file mode 100644
--- /dev/null
+++ b/eventuo11y-prometheus.cabal
@@ -0,0 +1,34 @@
+cabal-version:      3.0
+name:               eventuo11y-prometheus
+version:            0.1.0.0
+synopsis:           Prometheus backend for eventuo11y
+description:
+  Render [eventuo11y](https://hackage.haskell.org/package/eventuo11y) 'Observe.Event.Event's
+  to Prometheus metrics.
+
+bug-reports:        https://github.com/shlevy/eventuo11y/issues
+license:            Apache-2.0
+license-file:       LICENSE
+author:             Shea Levy
+maintainer:         shea@shealevy.com
+copyright:          Copyright 2023 Shea Levy.
+category:           Observability
+extra-source-files: CHANGELOG.md
+tested-with:        GHC == { 8.10.7, 9.2.4 }
+
+source-repository head
+  type:     git
+  location: https://github.com/shlevy/eventuo11y
+
+library
+  exposed-modules:
+    Observe.Event.Render.Prometheus
+
+  build-depends:
+    , base       ^>= { 4.14, 4.16, 4.17 }
+    , containers ^>= { 0.6 }
+    , eventuo11y ^>= { 0.7, 0.8, 0.9 }
+    , prometheus ^>= { 2.2.3 }
+
+  hs-source-dirs:   src
+  default-language: Haskell2010
diff --git a/src/Observe/Event/Render/Prometheus.hs b/src/Observe/Event/Render/Prometheus.hs
new file mode 100644
--- /dev/null
+++ b/src/Observe/Event/Render/Prometheus.hs
@@ -0,0 +1,191 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+
+-- |
+-- Description : EventBackend for rendering events as Prometheus metrics
+-- Copyright   : Copyright 2023 Shea Levy.
+-- License     : Apache-2.0
+-- Maintainer  : shea@shealevy.com
+module Observe.Event.Render.Prometheus where
+
+import Control.Exception
+import Data.Foldable
+import Data.IORef
+import Data.Map
+import Data.Traversable
+import Observe.Event.Backend
+import System.Metrics.Prometheus.Concurrent.Registry
+import qualified System.Metrics.Prometheus.Metric.Counter as PC
+import qualified System.Metrics.Prometheus.Metric.Gauge as PG
+import qualified System.Metrics.Prometheus.Metric.Histogram as PH
+import System.Metrics.Prometheus.MetricId
+import Prelude hiding (lookup)
+
+-- | An 'EventBackend' that populates a 'Registry'.
+--
+-- All metrics are registered before the backend is returned.
+prometheusEventBackend :: forall es s. (EventMetrics es) => Registry -> RenderSelectorPrometheus s es -> IO (EventBackend IO PrometheusReference s)
+prometheusEventBackend registry render = do
+  counters <- fmap fromAscList . for [minBound @(Counter es) ..] $ \cId -> do
+    c <- registerCounter (metricName cId) (metricLabels cId) registry
+    pure (cId, c)
+  gauges <- fmap fromAscList . for [minBound @(Gauge es) ..] $ \gId -> do
+    g <- registerGauge (metricName gId) (metricLabels gId) registry
+    pure (gId, g)
+  histograms <- fmap fromAscList . for [minBound @(Histogram es) ..] $ \hId -> do
+    h <- registerHistogram (metricName hId) (metricLabels hId) (metricBounds hId) registry
+    pure (hId, h)
+  let m !@ k = case lookup k m of
+        Just a -> pure a
+        Nothing -> throwIO NonExhaustiveMetricEnumeration
+
+      modifyCounter (AddCounter v) = PC.add v
+      modifyCounter IncCounter = PC.inc
+
+      modifyGauge (AddGauge v) = PG.add v
+      modifyGauge (Sub v) = PG.sub v
+      modifyGauge IncGauge = PG.inc
+      modifyGauge Dec = PG.dec
+      modifyGauge (Set v) = PG.set v
+
+      modifyHistogram (Observe v) = PH.observe v
+
+      performModification (ModifyCounter modC cId) =
+        counters !@ cId >>= modifyCounter modC
+      performModification (ModifyGauge modG gId) =
+        gauges !@ gId >>= modifyGauge modG
+      performModification (ModifyHistogram modH hId) =
+        histograms !@ hId >>= modifyHistogram modH
+
+      performModifications = traverse_ performModification
+  pure $
+    EventBackend
+      { newEvent = \(NewEventArgs {..}) -> do
+          let PrometheusRendered {..} = render newEventSelector
+          performModifications $ onStart newEventInitialFields Extended
+          fieldsRef <- newIORef []
+          pure $
+            Event
+              { reference = PrometheusReference,
+                addField = \f -> do
+                  performModifications $ onField f
+                  atomicModifyIORef' fieldsRef $ \fields ->
+                    (f : fields, ()),
+                finalize = \e -> do
+                  fields <- readIORef fieldsRef
+                  performModifications $ onFinalize e (newEventInitialFields ++ (reverse fields))
+              },
+        emitImmediateEvent = \(NewEventArgs {..}) -> do
+          let PrometheusRendered {..} = render newEventSelector
+          performModifications $ onStart newEventInitialFields Immediate
+          pure PrometheusReference
+      }
+
+-- | A specification of a collection of prometheus metrics.
+--
+-- Note that due to limitations in the underlying prometheus client library, summaries are not yet supported.
+class (EventMetric (Counter es), EventMetric (Gauge es), EventHistogram (Histogram es)) => EventMetrics es where
+  -- | The [counters](https://prometheus.io/docs/concepts/metric_types/#counter)
+  type Counter es
+
+  -- | The [gauges](https://prometheus.io/docs/concepts/metric_types/#gauge)
+  type Gauge es
+
+  -- | The [histograms](https://prometheus.io/docs/concepts/metric_types/#histogram)
+  type Histogram es
+
+-- | A specification of a single prometheus metric of any type
+--
+-- Must satisfy @∀ x : a, x \`elem\` [minBound .. maxBound]@
+class (Ord a, Enum a, Bounded a) => EventMetric a where
+  -- | The [name](https://prometheus.io/docs/practices/naming/#metric-names) of the metric
+  metricName :: a -> Name
+
+  -- | The [labels](https://prometheus.io/docs/practices/naming/#labels) of the metric
+  metricLabels :: a -> Labels
+
+-- | A specification of a prometheus [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram)
+class (EventMetric h) => EventHistogram h where
+  -- | The upper bounds of the histogram buckets.
+  metricBounds :: h -> [PH.UpperBound]
+
+-- | Render all events selectable by @s@ to prometheus metrics according to 'EventMetrics' @es@
+--
+-- We may want to add functionality for easily combining 'RenderSelectorPrometheus's and 'EventMetrics'
+-- from nested selector types, possibly with additional labels layered on top.
+type RenderSelectorPrometheus s es = forall f. s f -> PrometheusRendered f es
+
+-- | How to render a specific 'Event' according to 'EventMetrics' @es@
+data PrometheusRendered f es = PrometheusRendered
+  { -- | Modify metrics at event start
+    --
+    -- Passed the 'newEventInitialFields'.
+    onStart :: !([f] -> EventDuration -> [MetricModification es]),
+    -- | Modify metrics when a field is added
+    --
+    -- Only called for events added with 'addField'
+    onField :: !(f -> [MetricModification es]),
+    -- | Modify metrics when an event finishes.
+    --
+    -- Passed all event fields (both initial fields and those added
+    -- during the event lifetime).
+    --
+    -- This is not called if the event is 'Immediate'.
+    onFinalize :: !(Maybe SomeException -> [f] -> [MetricModification es])
+  }
+
+-- | DSL for modifying metrics specified in 'EventMetrics' @es@
+data MetricModification es
+  = -- | Modify the specified counter
+    ModifyCounter !CounterModification !(Counter es)
+  | -- | Modify the specified gauge
+    ModifyGauge !GaugeModification !(Gauge es)
+  | -- | Modify the specified histogram
+    ModifyHistogram !HistogramModification !(Histogram es)
+
+-- | DSL for modifying a counter metric
+data CounterModification
+  = -- | Add a value to a counter
+    AddCounter !Int
+  | -- | Increment a counter
+    IncCounter
+
+-- | DSL for modifying a gauge metric
+data GaugeModification
+  = -- | Add a value to a gauge
+    AddGauge !Double
+  | -- | Subtract a value from a gauge
+    Sub !Double
+  | -- | Increment a gauge
+    IncGauge
+  | -- | Decrement a gauge
+    Dec
+  | -- | Set the value of a gauge
+    Set !Double
+
+-- | DSL for modifying a histogram metric
+data HistogramModification
+  = -- | Record an observation
+    Observe !Double
+
+-- | What duration event is this?
+data EventDuration
+  = -- | A immediately finalized event
+    Immediate
+  | -- | An event with an extended lifetime
+    Extended
+
+-- | Reference type for 'prometheusEventBackend'
+--
+-- Prometheus can't make use of references, so this carries no information.
+data PrometheusReference = PrometheusReference
+
+-- | Exception thrown if we encounter an element of an 'EventMetric' that
+-- is not in @[minBound .. maxBound]@
+data NonExhaustiveMetricEnumeration = NonExhaustiveMetricEnumeration deriving (Show)
+
+instance Exception NonExhaustiveMetricEnumeration
