diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96d70a5b..f99ad419 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### API changes
 
 - Add `Result.forEach` https://github.com/rescript-association/rescript-core/pull/116
+- Add `Error.getUnsafe` https://github.com/rescript-association/rescript-core/pull/123
 
 ## 0.2.0
 
diff --git a/src/Core__Error.res b/src/Core__Error.res
index 408b08ff..c66143d2 100644
--- a/src/Core__Error.res
+++ b/src/Core__Error.res
@@ -37,3 +37,5 @@ module URIError = {
 external raise: t => 'a = "%raise"
 
 let panic = msg => make(`Panic! ${msg}`)->raise
+
+@get_index external getUnsafe: ('a, string) => option<'b> = ""
diff --git a/src/Core__Error.resi b/src/Core__Error.resi
index 76c12c0c..a8d69268 100644
--- a/src/Core__Error.resi
+++ b/src/Core__Error.resi
@@ -169,3 +169,23 @@ Error.panic("Uh oh. This was unexpected!")
 ```
 */
 let panic: string => 'a
+
+/**
+`getUnsafe` returns a custom property on an error object, given its name. This is useful for working with custom exceptions thrown from JavaScript.
+
+**Warning:** The return type is not guaranteed to be what you expect. It can be a string or null or anthing else. Run-time errors can occur if you use it as something that it is not. Consider the functions in the `Type` module to safely access its contents. ReScript exceptions can be handled in a type-safe way. See [Exceptions in ReScript](https://rescript-lang.org/docs/manual/latest/exception).
+
+## Examples
+```rescript
+switch exn->Error.fromException {
+| None => raise(exn) 
+| Some(err) =>
+  switch err->Error.getUnsafe("code") {
+  | Some("invalid-password") => Console.log("Try again!")
+  | _ => raise(exn)
+  }
+}
+```
+*/
+@get_index
+external getUnsafe: (t, string) => option<'a> = ""
diff --git a/test/ErrorTests.mjs b/test/ErrorTests.mjs
index a9af7afb..c202c511 100644
--- a/test/ErrorTests.mjs
+++ b/test/ErrorTests.mjs
@@ -2,6 +2,7 @@
 
 import * as Test from "./Test.mjs";
 import * as Js_exn from "rescript/lib/es6/js_exn.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
 import * as RescriptCore from "../src/RescriptCore.mjs";
 import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
 
@@ -33,7 +34,43 @@ function panicTest(param) {
 
 panicTest(undefined);
 
+function catchCustomError(param) {
+  var authenticationError = new Error("authentication error");
+  var codeCaught;
+  authenticationError["code"] = "invalid-password";
+  try {
+    throw authenticationError;
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    var err = Caml_js_exceptions.as_js_exn(exn);
+    if (err !== undefined) {
+      var code = Caml_option.valFromOption(err)["code"];
+      if (code !== undefined) {
+        codeCaught = Caml_option.valFromOption(code);
+      }
+      
+    } else {
+      throw exn;
+    }
+  }
+  Test.run([
+        [
+          "ErrorTests.res",
+          34,
+          15,
+          39
+        ],
+        "Can access custom code"
+      ], codeCaught, (function (prim0, prim1) {
+          return prim0 === prim1;
+        }), "invalid-password");
+}
+
+catchCustomError(undefined);
+
 export {
   panicTest ,
+  catchCustomError ,
 }
 /*  Not a pure module */
diff --git a/test/ErrorTests.res b/test/ErrorTests.res
index 07dcc078..f1b09414 100644
--- a/test/ErrorTests.res
+++ b/test/ErrorTests.res
@@ -9,3 +9,33 @@ let panicTest = () => {
 }
 
 panicTest()
+
+// This test case is based on catching authentication errors from the Firebase
+// SDK. Errors are subclassed from the Error object and contain custom
+// properties like "code".
+let catchCustomError = () => {
+  let authenticationError = Error.make("authentication error")
+  let codeCaught = ref(None)
+  Object.set(authenticationError->Obj.magic, "code", "invalid-password")
+  try {
+    authenticationError->Error.raise
+  } catch {
+  | _ as exn =>
+    switch exn->Error.fromException {
+    | None => raise(exn)
+    | Some(err) =>
+      switch err->Error.getUnsafe("code") {
+      | None => ()
+      | Some(code) => codeCaught := Some(code)
+      }
+    }
+  }
+  Test.run(
+    __POS_OF__("Can access custom code"),
+    codeCaught.contents,
+    \"==",
+    Some("invalid-password"),
+  )
+}
+
+catchCustomError()
diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs
index 2085495e..266545e6 100644
--- a/test/TestSuite.mjs
+++ b/test/TestSuite.mjs
@@ -27,6 +27,8 @@ var Concurrently = PromiseTest.Concurrently;
 
 var panicTest = ErrorTests.panicTest;
 
+var catchCustomError = ErrorTests.catchCustomError;
+
 var $$catch = IntTests.$$catch;
 
 var eq = ResultTests.eq;
@@ -46,6 +48,7 @@ export {
   Catching ,
   Concurrently ,
   panicTest ,
+  catchCustomError ,
   $$catch ,
   eq ,
   forEachIfOkCallFunction ,