diff --git a/stdnum/eu/eori.py b/stdnum/eu/eori.py
new file mode 100644
index 00000000..1c095320
--- /dev/null
+++ b/stdnum/eu/eori.py
@@ -0,0 +1,70 @@
+# eori.py - functions for handling Economic Operators Registration and Identification numbers
+# coding: utf-8
+#
+# Copyright (C) 2026 Sergi Almacellas Abellana
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see .
+
+"""EORI (Economic Operators Registration and Identification number).
+
+An EORI number identifies economic operators in customs procedures. It starts
+with a two-letter country code followed by up to 15 alphanumeric characters.
+This module validates only the common EORI format; national EORI formats are
+not checked.
+
+More information:
+
+* https://taxation-customs.ec.europa.eu/customs/customs-procedures-import-and-export/customs-operations/economic-operators-registration-and-identification-number-eori_en # noqa: E501
+
+>>> validate('ES B64717838')
+'ESB64717838'
+>>> validate('ESB12345678!')
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+"""
+
+from __future__ import annotations
+
+from string import ascii_uppercase, digits
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+_alphabet = ascii_uppercase + digits
+
+
+def compact(number: str) -> str:
+ """Convert the number to the minimal representation."""
+ return clean(number, ' -').strip().upper()
+
+
+def validate(number: str) -> str:
+ """Check if the number has a valid EORI format."""
+ number = compact(number)
+ if not 3 <= len(number) <= 17:
+ raise InvalidLength()
+ if (not all(x in ascii_uppercase for x in number[:2]) or
+ not all(x in _alphabet for x in number[2:])):
+ raise InvalidFormat()
+ return number
+
+
+def is_valid(number: str) -> bool:
+ """Check if the number has a valid EORI format."""
+ try:
+ return bool(validate(number))
+ except ValidationError:
+ return False
diff --git a/tests/test_eu_eori.doctest b/tests/test_eu_eori.doctest
new file mode 100644
index 00000000..1c2f58c1
--- /dev/null
+++ b/tests/test_eu_eori.doctest
@@ -0,0 +1,46 @@
+test_eu_eori.doctest - more detailed doctests for the stdnum.eu.eori module
+
+Copyright (C) 2026 Sergi Almacellas Abellana
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, see .
+
+This file contains more detailed doctests for the stdnum.eu.eori module.
+
+>>> from stdnum.eu import eori
+>>> from stdnum.exceptions import *
+
+EORI numbers are normalised and have a two-letter country code followed by at
+most 15 alphanumeric characters.
+
+>>> eori.compact('es- b64717838')
+'ESB64717838'
+>>> eori.validate('ESB64717838')
+'ESB64717838'
+
+Invalid lengths and characters are rejected.
+
+>>> eori.validate('ES')
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+>>> eori.validate('DE1234567890123456')
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+>>> eori.validate('E112345678')
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+>>> eori.is_valid('ES123/456')
+False