diff --git a/doc/api/errors.md b/doc/api/errors.md
index a818986d513063..e2e169eefb5d3d 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -714,6 +714,12 @@ Used when a child process is being forked without specifying an IPC channel.
Used when the main process is trying to read data from the child process's
STDERR/STDOUT, and the data's length is longer than the `maxBuffer` option.
+
+### ERR_CLOSED_MESSAGE_PORT
+
+There was an attempt to use a `MessagePort` instance in a closed
+state, usually after `.close()` has been called.
+
### `ERR_CONSOLE_WRITABLE_STREAM`
diff --git a/src/node_errors.h b/src/node_errors.h
index cec94023e19b56..ebea88bcf6ddb4 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -32,6 +32,7 @@ void OnFatalError(const char* location, const char* message);
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, Error) \
V(ERR_BUFFER_OUT_OF_BOUNDS, RangeError) \
V(ERR_BUFFER_TOO_LARGE, Error) \
+ V(ERR_CLOSED_MESSAGE_PORT, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
V(ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, RangeError) \
@@ -98,6 +99,7 @@ ERRORS_WITH_CODE(V)
#define PREDEFINED_ERROR_MESSAGES(V) \
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \
"Buffer is not available for the current Context") \
+ V(ERR_CLOSED_MESSAGE_PORT, "Cannot send data on closed MessagePort") \
V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
V(ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, \
diff --git a/src/node_messaging.cc b/src/node_messaging.cc
index 17ca38b76c784e..0a7d2551b2e094 100644
--- a/src/node_messaging.cc
+++ b/src/node_messaging.cc
@@ -1040,7 +1040,11 @@ void MessagePort::MoveToContext(const FunctionCallbackInfo& args) {
"The \"port\" argument must be a MessagePort instance");
}
MessagePort* port = Unwrap(args[0].As