diff --git a/docs/releases/2.0.0.md b/docs/releases/2.0.0.md
index e5cd38b..7d3367e 100644
--- a/docs/releases/2.0.0.md
+++ b/docs/releases/2.0.0.md
@@ -47,6 +47,7 @@ See {ref}`internal transition` for more details.
 - [#342](https://github.com/fgmacedo/python-statemachine/pull/342): Assignment of `Transition`
   guards using decorators is now possible.
 - [#331](https://github.com/fgmacedo/python-statemachine/pull/331): Added a way to generate diagrams using [QuickChart.io](https://quickchart.io) instead of GraphViz. See {ref}`diagrams` for more details.
+- [#353](https://github.com/fgmacedo/python-statemachine/pull/353): Support for abstract state machine classes, so you can subclass `StateMachine` to add behavior on your own base class. Abstract `StateMachine` cannot be instantiated.
 
 ## Bugfixes in 2.0
 
diff --git a/statemachine/factory.py b/statemachine/factory.py
index 186f28c..720196b 100644
--- a/statemachine/factory.py
+++ b/statemachine/factory.py
@@ -8,7 +8,6 @@ from .graph import visit_connected_states
 from .state import State
 from .transition import Transition
 from .transition_list import TransitionList
-from .utils import qualname
 from .utils import ugettext as _
 
 
@@ -57,18 +56,19 @@ class StateMachineMetaclass(type):
             )
 
     def _check(cls):
+        has_states = bool(cls.states)
+        has_events = bool(cls._events)
 
-        # do not validate the base class
-        name = qualname(cls)
-        if name == "statemachine.statemachine.StateMachine":
-            return
+        cls._abstract = not has_states and not has_events
 
-        cls._abstract = False
+        # do not validate the base abstract classes
+        if cls._abstract:
+            return
 
-        if not cls.states:
+        if not has_states:
             raise InvalidDefinition(_("There are no states."))
 
-        if not cls._events:
+        if not has_events:
             raise InvalidDefinition(_("There are no events."))
 
         cls._check_disconnected_state()
diff --git a/statemachine/statemachine.py b/statemachine/statemachine.py
index f9e3831..fa9f983 100644
--- a/statemachine/statemachine.py
+++ b/statemachine/statemachine.py
@@ -5,10 +5,12 @@ from .dispatcher import resolver_factory
 from .event import Event
 from .event_data import EventData
 from .exceptions import InvalidStateValue
+from .exceptions import InvalidDefinition
 from .exceptions import TransitionNotAllowed
 from .factory import StateMachineMetaclass
 from .model import Model
 from .transition import Transition
+from .utils import ugettext as _
 
 
 if TYPE_CHECKING:
@@ -28,6 +30,9 @@ class StateMachine(metaclass=StateMachineMetaclass):
         self.state_field = state_field
         self.start_value = start_value
 
+        if self._abstract:
+            raise InvalidDefinition(_("There are no states or transitions."))
+
         initial_transition = Transition(
             None, self._get_initial_state(), event="__initial__"
         )

diff --git a/tests/test_statemachine.py b/tests/test_statemachine.py
index 095b44d..2d6d068 100644
--- a/tests/test_statemachine.py
+++ b/tests/test_statemachine.py
@@ -296,13 +296,21 @@ def test_state_machine_with_a_invalid_start_value(
         machine_cls(model, start_value=start_value)
 
 
-def test_should_not_create_instance_of_machine_without_states():
+def test_should_not_create_instance_of_abstract_machine():
+    class EmptyMachine(StateMachine):
+        "An empty machine"
+        pass
+
+    with pytest.raises(exceptions.InvalidDefinition):
+        EmptyMachine()
 
+
+def test_should_not_create_instance_of_machine_without_states():
+    s1 = State("X")
     with pytest.raises(exceptions.InvalidDefinition):
 
-        class EmptyMachine(StateMachine):
-            "An empty machine"
-            pass
+        class OnlyTransitionMachine(StateMachine):
+            t1 = s1.to.itself()
 
 
 def test_should_not_create_instance_of_machine_without_transitions():

Supporting abstract classes
### Discussed in https://github.com/fgmacedo/python-statemachine/discussions/344

<div type='discussions-op-text'>

<sup>Originally posted by **galhyt** January 30, 2023</sup>
I have a class that inherites statemachine and has no states and no transitions, all are defined in the children classes. The problem is that from version 1.0 it's mandatory to have transitions and states. Can you reverse it?</div>
tests/test_statemachine.py::test_should_not_create_instance_of_abstract_machine
tests/test_statemachine.py::test_machine_repr tests/test_statemachine.py::test_machine_should_be_at_start_state tests/test_statemachine.py::test_machine_should_only_allow_only_one_initial_state tests/test_statemachine.py::test_machine_should_not_allow_transitions_from_final_state tests/test_statemachine.py::test_should_change_state tests/test_statemachine.py::test_should_run_a_transition_that_keeps_the_state tests/test_statemachine.py::test_should_change_state_with_multiple_machine_instances tests/test_statemachine.py::test_call_to_transition_that_is_not_in_the_current_state_should_raise_exception[draft-deliver] tests/test_statemachine.py::test_call_to_transition_that_is_not_in_the_current_state_should_raise_exception[closed-add_job] tests/test_statemachine.py::test_machine_should_list_allowed_events_in_the_current_state tests/test_statemachine.py::test_machine_should_run_a_transition_by_his_key tests/test_statemachine.py::test_machine_should_raise_an_exception_if_a_transition_by_his_key_is_not_found tests/test_statemachine.py::test_machine_should_use_and_model_attr_other_than_state tests/test_statemachine.py::test_cant_assign_an_invalid_state_directly tests/test_statemachine.py::test_should_allow_validate_data_for_transition tests/test_statemachine.py::test_should_check_if_is_in_status tests/test_statemachine.py::test_defined_value_must_be_assigned_to_models tests/test_statemachine.py::test_state_machine_without_model tests/test_statemachine.py::test_state_machine_with_a_start_value[None-campaign_machine-producing] tests/test_statemachine.py::test_state_machine_with_a_start_value[None-campaign_machine_with_values-2] tests/test_statemachine.py::test_state_machine_with_a_start_value[model2-campaign_machine-producing] tests/test_statemachine.py::test_state_machine_with_a_start_value[model3-campaign_machine_with_values-2] tests/test_statemachine.py::test_state_machine_with_a_invalid_start_value[None-campaign_machine-tapioca] tests/test_statemachine.py::test_state_machine_with_a_invalid_start_value[None-campaign_machine_with_values-99] tests/test_statemachine.py::test_state_machine_with_a_invalid_start_value[model2-campaign_machine-tapioca] tests/test_statemachine.py::test_state_machine_with_a_invalid_start_value[model3-campaign_machine_with_values-99] tests/test_statemachine.py::test_should_not_create_instance_of_machine_without_states tests/test_statemachine.py::test_should_not_create_instance_of_machine_without_transitions tests/test_statemachine.py::test_should_not_create_disconnected_machine tests/test_statemachine.py::test_should_not_create_big_disconnected_machine tests/test_statemachine.py::test_state_value_is_correct tests/test_statemachine.py::test_final_states tests/test_statemachine.py::test_should_not_override_states_properties