diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b4deda7..ece163d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## Next version
 
+- Deprecated `Exn` module. Use the `Error` module instead. https://github.com/rescript-association/rescript-core/pull/230
+
 ## 1.5.2
 
 - Remove aliases for runtime modules (`MapperRt`, `Internal`) and `Re`. https://github.com/rescript-association/rescript-core/pull/237
diff --git a/src/Core__BigInt.res b/src/Core__BigInt.res
index 028349fb..e1fe4ea3 100644
--- a/src/Core__BigInt.res
+++ b/src/Core__BigInt.res
@@ -30,7 +30,7 @@ BigInt.fromStringExn("0o11")
 try {
   BigInt.fromStringExn("a")
 } catch {
-| Exn.Error(_error) => 0n
+| Error.Error(_error) => 0n
 }
 ```
 */
diff --git a/src/Core__Error.mjs b/src/Core__Error.mjs
index e5334cf8..886807e6 100644
--- a/src/Core__Error.mjs
+++ b/src/Core__Error.mjs
@@ -17,7 +17,10 @@ function panic(msg) {
   throw new Error("Panic! " + msg);
 }
 
+var $$Error$1 = "JsError";
+
 export {
+  $$Error$1 as $$Error,
   $$EvalError ,
   $$RangeError ,
   $$ReferenceError ,
diff --git a/src/Core__Error.res b/src/Core__Error.res
index 408b08ff..98bb964a 100644
--- a/src/Core__Error.res
+++ b/src/Core__Error.res
@@ -1,4 +1,7 @@
-type t = Js.Exn.t
+type t = unknown
+
+@@warning("-38") /* unused extension constructor */
+exception Error = JsError
 
 external fromException: exn => option<t> = "?as_js_exn"
 external toException: t => exn = "%identity"
@@ -10,6 +13,9 @@ external toException: t => exn = "%identity"
 
 @new external make: string => t = "Error"
 
+external isCamlExceptionOrOpenVariant: 'a => bool = "?is_extension"
+external anyToExnInternal: 'a => exn = "#wrap_exn"
+
 module EvalError = {
   @new external make: string => t = "EvalError"
 }
diff --git a/src/Core__Error.resi b/src/Core__Error.resi
index 38b6a973..5aca8d49 100644
--- a/src/Core__Error.resi
+++ b/src/Core__Error.resi
@@ -5,7 +5,9 @@ See [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
 */
 
 /** Represents a JavaScript exception. */
-type t = Js.Exn.t
+type t
+
+type exn += private Error(t)
 
 external fromException: exn => option<t> = "?as_js_exn"
 
@@ -86,6 +88,35 @@ Console.log(error->Error.name) // Logs "Error" to the console, because this is a
 @new
 external make: string => t = "Error"
 
+/** internal use only */
+external isCamlExceptionOrOpenVariant: 'a => bool = "?is_extension"
+
+/**
+`anyToExnInternal(obj)` will take any value `obj` and wrap it
+in a `Error.Error` if given value is not an exn already. If
+`obj` is an exn, it will return `obj` without any changes.
+
+This function is mostly useful for cases where you want to unify a type of a value
+that potentially is either exn, a JS error, or any other JS value really (e.g. for
+a value passed to a Promise.catch callback)
+
+**IMPORTANT**: This is an internal API and may be changed / removed any time in the future.
+
+## Examples
+
+```rescript
+switch Error.anyToExnInternal("test") {
+| Error.Error(v) =>
+  switch(Error.message(v)) {
+  | Some(_) => assert(false)
+  | None => Console.log2("We will land here", v)
+  }
+| _ => assert(false)
+}
+```
+*/
+external anyToExnInternal: 'a => exn = "#wrap_exn"
+
 module EvalError: {
   /**
   Creates a new `EvalError` with the provided `message`.
diff --git a/src/Core__JSON.resi b/src/Core__JSON.resi
index 9b5f714b..dcb7f1cd 100644
--- a/src/Core__JSON.resi
+++ b/src/Core__JSON.resi
@@ -33,7 +33,7 @@ try {
   let _ = JSON.parseExn("")
   // error
 } catch {
-| Exn.Error(_) => Console.log("error")
+| Error.Error(_) => Console.log("error")
 }
 
 let reviver = (_, value: JSON.t) =>
@@ -52,15 +52,15 @@ try {
   JSON.parseExn("", ~reviver)->Console.log
   // error
 } catch {
-| Exn.Error(_) => Console.log("error")
+| Error.Error(_) => Console.log("error")
 }
 ```
 
 ## Exceptions 
 
-- Raises a SyntaxError (Exn.t) if the string isn't valid JSON.
+- Raises a SyntaxError (Error.t) if the string isn't valid JSON.
 */
-@raises(Exn.t)
+@raises(Error.t)
 @val
 external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse"
 
@@ -89,7 +89,7 @@ try {
   JSON.parseExnWithReviver("", reviver)->Console.log
   // error
 } catch {
-| Exn.Error(_) => Console.log("error")
+| Error.Error(_) => Console.log("error")
 }
 ```
 
@@ -98,7 +98,7 @@ try {
 - Raises a SyntaxError if the string isn't valid JSON.
 */
 @deprecated("Use `parseExn` with optional parameter instead")
-@raises(Exn.t)
+@raises(Error.t)
 @val
 external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse"
 
@@ -350,7 +350,7 @@ BigInt.fromInt(0)->JSON.stringifyAny
 - Raises a TypeError if the value contains circular references.
 - Raises a TypeError if the value contains `BigInt`s.
 */
-@raises(Exn.t)
+@raises(Error.t)
 @val
 external stringifyAny: ('a, ~replacer: replacer=?, ~space: int=?) => option<string> =
   "JSON.stringify"
@@ -391,7 +391,7 @@ BigInt.fromInt(0)->JSON.stringifyAny
 - Raises a TypeError if the value contains `BigInt`s.
 */
 @deprecated("Use `stringifyAny` with optional parameter instead")
-@raises(Exn.t)
+@raises(Error.t)
 @val
 external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option<string> = "JSON.stringify"
 
diff --git a/src/Core__Promise.res b/src/Core__Promise.res
index 8a4418d2..75e71c12 100644
--- a/src/Core__Promise.res
+++ b/src/Core__Promise.res
@@ -99,7 +99,7 @@ external _catch: (t<'a>, exn => t<'a>) => t<'a> = "catch"
 
 let catch = (promise: promise<'a>, callback: exn => promise<'a>): promise<'a> => {
   _catch(promise, err => {
-    callback(Js.Exn.anyToExnInternal(err))
+    callback(Core__Error.anyToExnInternal(err))
   })
 }
 
diff --git a/src/Core__Promise.resi b/src/Core__Promise.resi
index 5468bf93..212368c3 100644
--- a/src/Core__Promise.resi
+++ b/src/Core__Promise.resi
@@ -126,8 +126,8 @@ reject(SomeError("this is an error"))
 ->catch(e => {
   let msg = switch(e) {
     | SomeError(msg) => "ReScript error occurred: " ++ msg
-    | Exn.Error(obj) =>
-      switch Exn.message(obj) {
+    | Error.Error(obj) =>
+      switch Error.message(obj) {
         | Some(msg) => "JS exception occurred: " ++ msg
         | None => "Some other JS value has been thrown"
       }
diff --git a/src/RescriptCore.res b/src/RescriptCore.res
index 2b52eb5f..b85988fe 100644
--- a/src/RescriptCore.res
+++ b/src/RescriptCore.res
@@ -94,7 +94,9 @@ async function main() {
 */
 external import: 'a => promise<'a> = "#import"
 
+@deprecated("Use `Error` module instead")
 module Exn = Js.Exn
+
 module Option = Core__Option
 module List = Core__List
 module Result = Core__Result
diff --git a/test/ErrorTests.mjs b/test/ErrorTests.mjs
index dcced1d5..2865c24b 100644
--- a/test/ErrorTests.mjs
+++ b/test/ErrorTests.mjs
@@ -1,7 +1,7 @@
 // Generated by ReScript, PLEASE EDIT WITH CARE
 
 import * as Test from "./Test.mjs";
-import * as Js_exn from "rescript/lib/es6/js_exn.js";
+import * as Core__Error from "../src/Core__Error.mjs";
 import * as RescriptCore from "../src/RescriptCore.mjs";
 import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
 
@@ -12,7 +12,7 @@ function panicTest() {
   }
   catch (raw_err){
     var err = Caml_js_exceptions.internalToOCamlException(raw_err);
-    if (err.RE_EXN_ID === Js_exn.$$Error) {
+    if (err.RE_EXN_ID === Core__Error.$$Error) {
       caught = err._1.message;
     } else {
       throw err;
diff --git a/test/ErrorTests.res b/test/ErrorTests.res
index 07dcc078..21bd55e5 100644
--- a/test/ErrorTests.res
+++ b/test/ErrorTests.res
@@ -2,7 +2,7 @@ open RescriptCore
 
 let panicTest = () => {
   let caught = try panic("uh oh") catch {
-  | Exn.Error(err) => Error.message(err)
+  | Error.Error(err) => Error.message(err)
   }
 
   Test.run(__POS_OF__("Should resolve test"), caught, \"==", Some("Panic! uh oh"))
diff --git a/test/IntTests.mjs b/test/IntTests.mjs
index 593fb9eb..5fd72810 100644
--- a/test/IntTests.mjs
+++ b/test/IntTests.mjs
@@ -1,9 +1,9 @@
 // Generated by ReScript, PLEASE EDIT WITH CARE
 
 import * as Test from "./Test.mjs";
-import * as Js_exn from "rescript/lib/es6/js_exn.js";
 import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
 import * as Core__Int from "../src/Core__Int.mjs";
+import * as Core__Error from "../src/Core__Error.mjs";
 import * as PervasivesU from "rescript/lib/es6/pervasivesU.js";
 import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
 
@@ -16,7 +16,7 @@ function $$catch(f) {
   }
   catch (raw_err){
     var err = Caml_js_exceptions.internalToOCamlException(raw_err);
-    if (err.RE_EXN_ID === Js_exn.$$Error) {
+    if (err.RE_EXN_ID === Core__Error.$$Error) {
       return err._1;
     }
     throw err;
diff --git a/test/IntTests.res b/test/IntTests.res
index 4535b18e..95f5e9ce 100644
--- a/test/IntTests.res
+++ b/test/IntTests.res
@@ -7,7 +7,7 @@ let catch = f =>
     let _ = f()
     failwith("no exception raised")
   } catch {
-  | Exn.Error(err) => err
+  | Error.Error(err) => err
   }
 
 Test.run(__POS_OF__("range - positive, increasing"), Int.range(3, 6), eq, [3, 4, 5])
diff --git a/test/PromiseTest.mjs b/test/PromiseTest.mjs
index ffdb1df1..8a5468a8 100644
--- a/test/PromiseTest.mjs
+++ b/test/PromiseTest.mjs
@@ -1,14 +1,16 @@
 // Generated by ReScript, PLEASE EDIT WITH CARE
 
 import * as Test from "./Test.mjs";
-import * as Js_exn from "rescript/lib/es6/js_exn.js";
 import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as Core__Error from "../src/Core__Error.mjs";
 import * as Core__Promise from "../src/Core__Promise.mjs";
 import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
 
 var TestError = /* @__PURE__ */Caml_exceptions.create("PromiseTest.TestError");
 
-var fail = Js_exn.raiseError;
+function fail(msg) {
+  throw msg;
+}
 
 var equal = Caml_obj.equal;
 
@@ -161,7 +163,7 @@ function testExternalPromiseThrow() {
   return Core__Promise.$$catch(asyncParseFail().then(function (param) {
                   return Promise.resolve();
                 }), (function (e) {
-                var success = e.RE_EXN_ID === Js_exn.$$Error ? Caml_obj.equal(e._1.name, "SyntaxError") : false;
+                var success = e.RE_EXN_ID === Core__Error.$$Error ? Caml_obj.equal(e._1.name, "SyntaxError") : false;
                 Test.run([
                       [
                         "PromiseTest.res",
@@ -199,9 +201,9 @@ function testExnThrow() {
 
 function testRaiseErrorThrow() {
   return Core__Promise.$$catch(Promise.resolve().then(function () {
-                  return Js_exn.raiseError("Some JS error");
+                  throw new Error("Some JS error");
                 }), (function (e) {
-                var isTestErr = e.RE_EXN_ID === Js_exn.$$Error ? Caml_obj.equal(e._1.message, "Some JS error") : false;
+                var isTestErr = e.RE_EXN_ID === Core__Error.$$Error ? Caml_obj.equal(e._1.message, "Some JS error") : false;
                 Test.run([
                       [
                         "PromiseTest.res",
diff --git a/test/PromiseTest.res b/test/PromiseTest.res
index 35197fea..8165fc60 100644
--- a/test/PromiseTest.res
+++ b/test/PromiseTest.res
@@ -3,7 +3,7 @@ open RescriptCore
 exception TestError(string)
 
 let fail = msg => {
-  Exn.raiseError(msg)
+  Error.raise(msg)
 }
 
 let equal = (a, b) => {
@@ -132,7 +132,7 @@ module Catching = {
     ->then(_ => resolve()) // Since our asyncParse will fail anyways, we convert to promise<unit> for our catch later
     ->catch(e => {
       let success = switch e {
-      | Exn.Error(err) => Exn.name(err) == Some("SyntaxError")
+      | Error.Error(err) => Error.name(err) == Some("SyntaxError")
       | _ => false
       }
 
@@ -166,7 +166,7 @@ module Catching = {
     open Promise
 
     let causeErr = () => {
-      Exn.raiseError("Some JS error")
+      Error.make("Some JS error")->Error.raise
     }
 
     resolve()
@@ -175,7 +175,7 @@ module Catching = {
     })
     ->catch(e => {
       let isTestErr = switch e {
-      | Exn.Error(err) => Exn.message(err) == Some("Some JS error")
+      | Error.Error(err) => Error.message(err) == Some("Some JS error")
       | _ => false
       }
       Test.run(__POS_OF__("Should be some JS error"), isTestErr, equal, true)
diff --git a/test/intl/IntlTests.mjs b/test/intl/IntlTests.mjs
index 6fcf5875..6929a88d 100644
--- a/test/intl/IntlTests.mjs
+++ b/test/intl/IntlTests.mjs
@@ -1,7 +1,7 @@
 // Generated by ReScript, PLEASE EDIT WITH CARE
 
-import * as Js_exn from "rescript/lib/es6/js_exn.js";
 import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Core__Error from "../../src/Core__Error.mjs";
 import * as Core__Option from "../../src/Core__Option.mjs";
 import * as Intl__LocaleTest from "./Intl__LocaleTest.mjs";
 import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
@@ -29,7 +29,7 @@ try {
 }
 catch (raw_e){
   var e = Caml_js_exceptions.internalToOCamlException(raw_e);
-  if (e.RE_EXN_ID === Js_exn.$$Error) {
+  if (e.RE_EXN_ID === Core__Error.$$Error) {
     console.error(e._1);
   } else {
     throw e;
@@ -46,7 +46,7 @@ try {
 }
 catch (raw_e$1){
   var e$1 = Caml_js_exceptions.internalToOCamlException(raw_e$1);
-  if (e$1.RE_EXN_ID === Js_exn.$$Error) {
+  if (e$1.RE_EXN_ID === Core__Error.$$Error) {
     console.error(e$1._1);
   } else {
     throw e$1;
@@ -59,7 +59,7 @@ try {
 }
 catch (raw_e$2){
   var e$2 = Caml_js_exceptions.internalToOCamlException(raw_e$2);
-  if (e$2.RE_EXN_ID === Js_exn.$$Error) {
+  if (e$2.RE_EXN_ID === Core__Error.$$Error) {
     var e$3 = e$2._1;
     var message = Core__Option.map(e$3.message, (function (prim) {
             return prim.toLowerCase();
diff --git a/test/intl/IntlTests.res b/test/intl/IntlTests.res
index d5fa1c6d..ad32f690 100644
--- a/test/intl/IntlTests.res
+++ b/test/intl/IntlTests.res
@@ -18,7 +18,7 @@ Intl.getCanonicalLocalesManyExn(["EN-US", "Fr"])->Console.log
 try {
   Intl.getCanonicalLocalesExn("bloop")->Console.log
 } catch {
-| Exn.Error(e) => Console.error(e)
+| Error.Error(e) => Console.error(e)
 }
 
 try {
@@ -29,7 +29,7 @@ try {
   Intl.supportedValuesOfExn("timeZone")->Console.log
   Intl.supportedValuesOfExn("unit")->Console.log
 } catch {
-| Exn.Error(e) => Console.error(e)
+| Error.Error(e) => Console.error(e)
 }
 
 try {
@@ -37,7 +37,7 @@ try {
 
   Console.error("Shouldn't have been hit")
 } catch {
-| Exn.Error(e) =>
+| Error.Error(e) =>
   switch Error.message(e)->Option.map(String.toLowerCase) {
   | Some("invalid key : someinvalidkey") => Console.log("Caught expected error")
   | message => {