sloria: I wanted to keep the API surface to a minimum here. If a user needs to customize `Meta`, they can declare a class.
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1784bcf0..86586ec4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,7 @@ Features:
 
 - Optimize ``List(Nested(...))`` (:issue:`779`).
 - Minor performance improvements and cleanup (:pr:`1328`).
+- Add ``Schema.from_dict`` (:issue:`1312`).
 
 Deprecations/Removals:
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 43ff596e..5bd26d68 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -41,6 +41,20 @@ Create a schema by defining a class with variables mapping attribute names to :c
 
     For a full reference on the available field classes, see the :ref:`API Docs <api_fields>`.
 
+Creating Schemas From Dictionaries
+----------------------------------
+
+You can create a schema from a dictionary of fields using the `from_dict <marshmallow.Schema.from_dict>` method.
+
+.. code-block:: python
+
+    from marshmallow import Schema, fields
+
+    UserSchema = Schema.from_dict(
+        {"name": fields.Str(), "email": fields.Email(), "created_at": fields.DateTime()}
+    )
+
+`from_dict <marshmallow.Schema.from_dict>` is especially useful for generating schemas at runtime.
 
 Serializing Objects ("Dumping")
 -------------------------------
@@ -96,8 +110,8 @@ with a dictionary of validation errors, which we'll :ref:`revisit later <validat
 
     user_data = {
         "created_at": "2014-08-11T05:26:03.869245",
-        "email": u"ken@yahoo.com",
-        "name": u"Ken",
+        "email": "ken@yahoo.com",
+        "name": "Ken",
     }
     schema = UserSchema()
     result = schema.load(user_data)
diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py
index fa1dd5a6..520ffb44 100644
--- a/src/marshmallow/schema.py
+++ b/src/marshmallow/schema.py
@@ -8,6 +8,7 @@ import decimal
 import copy
 import inspect
 import json
+import typing
 import warnings
 
 from marshmallow import base, fields as ma_fields, class_registry
@@ -409,6 +410,35 @@ class BaseSchema(base.SchemaABC):
     def set_class(self):
         return OrderedSet if self.ordered else set
 
+    @classmethod
+    def from_dict(
+        cls, fields: typing.Dict[str, ma_fields.Field], *, name: str = "GeneratedSchema"
+    ) -> typing.Type["Schema"]:
+        """Generate a `Schema` class given a dictionary of fields.
+
+        .. code-block:: python
+
+            from marshmallow import Schema, fields
+
+            PersonSchema = Schema.from_dict({"name": fields.Str()})
+            print(PersonSchema().load({"name": "David"}))  # => {'name': 'David'}
+
+        Generated schemas are not added to the class registry and therefore cannot
+        be referred to by name in `Nested` fields.
+
+        :param dict fields: Dictionary mapping field names to field instances.
+        :param str name: Optional name for the class, which will appear in
+            the ``repr`` for the class.
+
+        .. versionadded:: 3.0.0
+        """
+        attrs = fields.copy()
+        attrs["Meta"] = type(
+            "GeneratedMeta", (getattr(cls, "Meta", object),), {"register": False}
+        )
+        schema_cls = type(name, (cls,), attrs)
+        return schema_cls
+
     ##### Override-able methods #####
 
     def handle_error(self, error, data, *, many, **kwargs):

Add a way to generate a Schema from a dictionary
Generating schemas at runtime is a fairly common use case that I've run into a handful of times at this point ([webargs](https://github.com/marshmallow-code/webargs/blob/de061e037285fd08a42d73be95bc779f2a4e3c47/src/webargs/core.py#L50-L63), [environs](https://github.com/sloria/environs/blob/91b32a0e4bdf40281fab3978368164b27ce65e39/environs.py#L83-L95) , form builders...). 

It might make sense to incorporate this into marshmallow core.

## Proposed API

```python
>>> MyDynamicSchema = Schema.from_dict({"foo": fields.Str()})
>>> MyDynamicSchema
<class 'marshmallow.schema.GeneratedSchema'>
```

```python
class AlbumSchema(Schema):
     artist = fields.Nested(Schema.from_dict({"name": fields.Str()})
```
diff --git a/tests/test_schema.py b/tests/test_schema.py
index dde4d8ef..839c70e3 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -19,8 +19,13 @@ from marshmallow import (
     EXCLUDE,
     INCLUDE,
     RAISE,
+    class_registry,
+)
+from marshmallow.exceptions import (
+    ValidationError,
+    StringNotCollectionError,
+    RegistryError,
 )
-from marshmallow.exceptions import ValidationError, StringNotCollectionError
 
 from tests.base import (
     UserSchema,
@@ -2644,3 +2649,38 @@ class TestLoadOnly:
         data_with_no_top_level_domain = {"url": "marshmallow://app/discounts"}
         result = schema.load(data_with_no_top_level_domain)
         assert result == data_with_no_top_level_domain
+
+
+class TestFromDict:
+    def test_generates_schema(self):
+        MySchema = Schema.from_dict({"foo": fields.Str()})
+        assert issubclass(MySchema, Schema)
+
+    def test_name(self):
+        MySchema = Schema.from_dict({"foo": fields.Str()})
+        assert "GeneratedSchema" in repr(MySchema)
+        SchemaWithName = Schema.from_dict(
+            {"foo": fields.Int()}, name="MyGeneratedSchema"
+        )
+        assert "MyGeneratedSchema" in repr(SchemaWithName)
+
+    def test_generated_schemas_are_not_registered(self):
+        n_registry_entries = len(class_registry._registry)
+        Schema.from_dict({"foo": fields.Str()})
+        Schema.from_dict({"bar": fields.Str()}, name="MyGeneratedSchema")
+        assert len(class_registry._registry) == n_registry_entries
+        with pytest.raises(RegistryError):
+            class_registry.get_class("GeneratedSchema")
+        with pytest.raises(RegistryError):
+            class_registry.get_class("MyGeneratedSchema")
+
+    def test_meta_options_are_applied(self):
+        class OrderedSchema(Schema):
+            class Meta:
+                ordered = True
+                load_only = ("bar",)
+
+        OSchema = OrderedSchema.from_dict({"foo": fields.Int(), "bar": fields.Int()})
+        dumped = OSchema().dump({"foo": 42, "bar": 24})
+        assert isinstance(dumped, OrderedDict)
+        assert "bar" not in dumped

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.yaml==1.3.0
attrs==25.3.0
cachetools==5.5.2
cfgv==3.4.0
chardet==5.2.0
colorama==0.4.6
distlib==0.3.9
entrypoints==0.3
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.18.0
flake8==3.7.8
flake8-bugbear==19.3.0
identify==2.6.9
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
-e git+https://github.com/marshmallow-code/marshmallow.git@51238065d87d788532e50894ea52dafee1c97753#egg=marshmallow
mccabe==0.6.1
nodeenv==1.9.1
packaging @ file:///croot/packaging_1734472117206/work
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
pre-commit==1.21.0
pycodestyle==2.5.0
pyflakes==2.1.1
pyproject-api==1.9.0
pytest @ file:///croot/pytest_1738938843180/work
pytz==2025.2
PyYAML==6.0.2
simplejson==3.20.1
six==1.17.0
toml==0.10.2
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
virtualenv==20.29.3

name: marshmallow
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aspy-yaml==1.3.0
      - attrs==25.3.0
      - cachetools==5.5.2
      - cfgv==3.4.0
      - chardet==5.2.0
      - colorama==0.4.6
      - distlib==0.3.9
      - entrypoints==0.3
      - filelock==3.18.0
      - flake8==3.7.8
      - flake8-bugbear==19.3.0
      - identify==2.6.9
      - marshmallow==3.0.0rc9
      - mccabe==0.6.1
      - nodeenv==1.9.1
      - platformdirs==4.3.7
      - pre-commit==1.21.0
      - pycodestyle==2.5.0
      - pyflakes==2.1.1
      - pyproject-api==1.9.0
      - pytz==2025.2
      - pyyaml==6.0.2
      - simplejson==3.20.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/marshmallow

tests/test_schema.py::TestFromDict::test_generates_schema tests/test_schema.py::TestFromDict::test_name tests/test_schema.py::TestFromDict::test_generated_schemas_are_not_registered tests/test_schema.py::TestFromDict::test_meta_options_are_applied
tests/test_schema.py::test_serializing_basic_object[UserSchema] tests/test_schema.py::test_serializing_basic_object[UserMetaSchema] tests/test_schema.py::test_serializer_dump tests/test_schema.py::test_load_resets_errors tests/test_schema.py::test_load_validation_error_stores_input_data_and_valid_data tests/test_schema.py::test_load_resets_error_fields tests/test_schema.py::test_errored_fields_do_not_appear_in_output tests/test_schema.py::test_load_many_stores_error_indices tests/test_schema.py::test_dump_many tests/test_schema.py::test_multiple_errors_can_be_stored_for_a_given_index tests/test_schema.py::test_dump_returns_a_dict tests/test_schema.py::test_dumps_returns_a_string tests/test_schema.py::test_dumping_single_object_with_collection_schema tests/test_schema.py::test_loading_single_object_with_collection_schema tests/test_schema.py::test_dumps_many tests/test_schema.py::test_load_returns_an_object tests/test_schema.py::test_load_many tests/test_schema.py::test_load_invalid_input_type[None] tests/test_schema.py::test_load_invalid_input_type[False] tests/test_schema.py::test_load_invalid_input_type[1] tests/test_schema.py::test_load_invalid_input_type[1.2] tests/test_schema.py::test_load_invalid_input_type[val4] tests/test_schema.py::test_load_invalid_input_type[val5] tests/test_schema.py::test_load_invalid_input_type[val6] tests/test_schema.py::test_load_invalid_input_type[lol] tests/test_schema.py::test_load_many_invalid_input_type[None] tests/test_schema.py::test_load_many_invalid_input_type[False] tests/test_schema.py::test_load_many_invalid_input_type[1] tests/test_schema.py::test_load_many_invalid_input_type[1.2] tests/test_schema.py::test_load_many_invalid_input_type[val4] tests/test_schema.py::test_load_many_invalid_input_type[val5] tests/test_schema.py::test_load_many_invalid_input_type[val6] tests/test_schema.py::test_load_many_invalid_input_type[lol] tests/test_schema.py::test_load_many_empty_collection[val0] tests/test_schema.py::test_load_many_empty_collection[val1] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[False] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[1] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[1.2] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[val3] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[val4] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[val5] tests/test_schema.py::test_load_many_in_nested_invalid_input_type[lol] tests/test_schema.py::test_load_many_in_nested_empty_collection[val0] tests/test_schema.py::test_load_many_in_nested_empty_collection[val1] tests/test_schema.py::test_loads_returns_a_user tests/test_schema.py::test_loads_many tests/test_schema.py::test_loads_deserializes_from_json tests/test_schema.py::test_serializing_none tests/test_schema.py::test_default_many_symmetry tests/test_schema.py::test_on_bind_field_hook tests/test_schema.py::test_nested_on_bind_field_hook tests/test_schema.py::TestValidate::test_validate_raises_with_errors_dict tests/test_schema.py::TestValidate::test_validate_many tests/test_schema.py::TestValidate::test_validate_many_doesnt_store_index_if_index_errors_option_is_false tests/test_schema.py::TestValidate::test_validate tests/test_schema.py::TestValidate::test_validate_required tests/test_schema.py::test_fields_are_not_copies[UserSchema] tests/test_schema.py::test_fields_are_not_copies[UserMetaSchema] tests/test_schema.py::test_dumps_returns_json tests/test_schema.py::test_naive_datetime_field tests/test_schema.py::test_datetime_formatted_field tests/test_schema.py::test_datetime_iso_field tests/test_schema.py::test_tz_datetime_field tests/test_schema.py::test_class_variable tests/test_schema.py::test_serialize_many[UserSchema] tests/test_schema.py::test_serialize_many[UserMetaSchema] tests/test_schema.py::test_inheriting_schema tests/test_schema.py::test_custom_field tests/test_schema.py::test_url_field tests/test_schema.py::test_relative_url_field tests/test_schema.py::test_stores_invalid_url_error[UserSchema] tests/test_schema.py::test_stores_invalid_url_error[UserMetaSchema] tests/test_schema.py::test_email_field[UserSchema] tests/test_schema.py::test_email_field[UserMetaSchema] tests/test_schema.py::test_stored_invalid_email tests/test_schema.py::test_integer_field tests/test_schema.py::test_as_string tests/test_schema.py::test_method_field[UserSchema] tests/test_schema.py::test_method_field[UserMetaSchema] tests/test_schema.py::test_function_field tests/test_schema.py::test_fields_must_be_declared_as_instances tests/test_schema.py::test_serializing_generator[UserSchema] tests/test_schema.py::test_serializing_generator[UserMetaSchema] tests/test_schema.py::test_serializing_empty_list_returns_empty_list tests/test_schema.py::test_serializing_dict tests/test_schema.py::test_serializing_dict_with_meta_fields tests/test_schema.py::test_exclude_in_init[UserSchema] tests/test_schema.py::test_exclude_in_init[UserMetaSchema] tests/test_schema.py::test_only_in_init[UserSchema] tests/test_schema.py::test_only_in_init[UserMetaSchema] tests/test_schema.py::test_invalid_only_param tests/test_schema.py::test_can_serialize_uuid tests/test_schema.py::test_can_serialize_time tests/test_schema.py::test_invalid_dict_but_okay tests/test_schema.py::test_json_module_is_deprecated tests/test_schema.py::test_render_module tests/test_schema.py::test_custom_error_message tests/test_schema.py::test_custom_unknown_error_message tests/test_schema.py::test_custom_type_error_message tests/test_schema.py::test_custom_type_error_message_with_many tests/test_schema.py::test_custom_error_messages_with_inheritance tests/test_schema.py::test_load_errors_with_many tests/test_schema.py::test_error_raised_if_fields_option_is_not_list tests/test_schema.py::test_error_raised_if_additional_option_is_not_list tests/test_schema.py::test_nested_custom_set_in_exclude_reusing_schema tests/test_schema.py::test_nested_only tests/test_schema.py::test_nested_only_inheritance tests/test_schema.py::test_nested_only_empty_inheritance tests/test_schema.py::test_nested_exclude tests/test_schema.py::test_nested_exclude_inheritance tests/test_schema.py::test_nested_only_and_exclude tests/test_schema.py::test_nested_only_then_exclude_inheritance tests/test_schema.py::test_nested_exclude_then_only_inheritance tests/test_schema.py::test_nested_exclude_and_only_inheritance tests/test_schema.py::test_meta_nested_exclude tests/test_schema.py::test_nested_custom_set_not_implementing_getitem tests/test_schema.py::test_deeply_nested_only_and_exclude tests/test_schema.py::test_data_key_collision[f1] tests/test_schema.py::test_data_key_collision[f5] tests/test_schema.py::test_data_key_collision[None] tests/test_schema.py::test_attribute_collision[f1] tests/test_schema.py::test_attribute_collision[f5] tests/test_schema.py::test_attribute_collision[None] tests/test_schema.py::TestDeeplyNestedLoadOnly::test_load_only tests/test_schema.py::TestDeeplyNestedLoadOnly::test_dump_only tests/test_schema.py::TestDeeplyNestedListLoadOnly::test_load_only tests/test_schema.py::TestDeeplyNestedListLoadOnly::test_dump_only tests/test_schema.py::test_nested_constructor_only_and_exclude tests/test_schema.py::test_only_and_exclude tests/test_schema.py::test_only_and_exclude_with_fields tests/test_schema.py::test_invalid_only_and_exclude_with_fields tests/test_schema.py::test_only_and_exclude_with_additional tests/test_schema.py::test_invalid_only_and_exclude_with_additional tests/test_schema.py::test_exclude_invalid_attribute tests/test_schema.py::test_only_bounded_by_fields tests/test_schema.py::test_only_bounded_by_additional tests/test_schema.py::test_only_empty tests/test_schema.py::test_only_and_exclude_as_string[only] tests/test_schema.py::test_only_and_exclude_as_string[exclude] tests/test_schema.py::test_nested_with_sets tests/test_schema.py::test_meta_serializer_fields tests/test_schema.py::test_meta_fields_mapping tests/test_schema.py::test_meta_field_not_on_obj_raises_attribute_error tests/test_schema.py::test_exclude_fields tests/test_schema.py::test_fields_option_must_be_list_or_tuple tests/test_schema.py::test_exclude_option_must_be_list_or_tuple tests/test_schema.py::test_datetimeformat_option tests/test_schema.py::test_dateformat_option tests/test_schema.py::test_default_dateformat tests/test_schema.py::test_inherit_meta tests/test_schema.py::test_inherit_meta_override tests/test_schema.py::test_additional tests/test_schema.py::test_cant_set_both_additional_and_fields tests/test_schema.py::test_serializing_none_meta tests/test_schema.py::TestFieldValidation::test_errors_are_cleared_after_loading_collection tests/test_schema.py::TestFieldValidation::test_raises_error_with_list tests/test_schema.py::TestFieldValidation::test_raises_error_with_dict tests/test_schema.py::TestFieldValidation::test_ignored_if_not_in_only tests/test_schema.py::test_schema_repr tests/test_schema.py::TestNestedSchema::test_nested_many_with_missing_attribute tests/test_schema.py::TestNestedSchema::test_nested_with_attribute_none tests/test_schema.py::TestNestedSchema::test_nested_field_does_not_validate_required tests/test_schema.py::TestNestedSchema::test_nested_none tests/test_schema.py::TestNestedSchema::test_nested tests/test_schema.py::TestNestedSchema::test_nested_many_fields tests/test_schema.py::TestNestedSchema::test_nested_meta_many tests/test_schema.py::TestNestedSchema::test_nested_only tests/test_schema.py::TestNestedSchema::test_exclude tests/test_schema.py::TestNestedSchema::test_list_field tests/test_schema.py::TestNestedSchema::test_nested_load_many tests/test_schema.py::TestNestedSchema::test_nested_errors tests/test_schema.py::TestNestedSchema::test_nested_method_field tests/test_schema.py::TestNestedSchema::test_nested_function_field tests/test_schema.py::TestNestedSchema::test_serializer_meta_with_nested_fields tests/test_schema.py::TestNestedSchema::test_serializer_with_nested_meta_fields tests/test_schema.py::TestNestedSchema::test_nested_fields_must_be_passed_a_serializer tests/test_schema.py::TestNestedSchema::test_invalid_type_passed_to_nested_field tests/test_schema.py::TestNestedSchema::test_all_errors_on_many_nested_field_with_validates_decorator tests/test_schema.py::TestNestedSchema::test_nested_unknown_validation[None] tests/test_schema.py::TestNestedSchema::test_nested_unknown_validation[raise] tests/test_schema.py::TestNestedSchema::test_nested_unknown_validation[include] tests/test_schema.py::TestNestedSchema::test_nested_unknown_validation[exclude] tests/test_schema.py::TestPluckSchema::test_pluck tests/test_schema.py::TestPluckSchema::test_pluck_none tests/test_schema.py::TestPluckSchema::test_pluck_with_data_key tests/test_schema.py::TestSelfReference::test_nesting_schema_within_itself tests/test_schema.py::TestSelfReference::test_nesting_schema_by_passing_class_name tests/test_schema.py::TestSelfReference::test_nesting_within_itself_meta tests/test_schema.py::TestSelfReference::test_nested_self_with_only_param tests/test_schema.py::TestSelfReference::test_multiple_pluck_self_field tests/test_schema.py::TestSelfReference::test_nested_self_many tests/test_schema.py::TestSelfReference::test_nested_self_list tests/test_schema.py::test_serialization_with_required_field tests/test_schema.py::test_deserialization_with_required_field tests/test_schema.py::test_deserialization_with_required_field_and_custom_validator tests/test_schema.py::TestContext::test_context_method tests/test_schema.py::TestContext::test_context_method_function tests/test_schema.py::TestContext::test_function_field_raises_error_when_context_not_available tests/test_schema.py::TestContext::test_function_field_handles_bound_serializer tests/test_schema.py::TestContext::test_fields_context tests/test_schema.py::TestContext::test_nested_fields_inherit_context tests/test_schema.py::TestContext::test_nested_list_fields_inherit_context tests/test_schema.py::TestContext::test_nested_dict_fields_inherit_context tests/test_schema.py::test_serializer_can_specify_nested_object_as_attribute tests/test_schema.py::TestFieldInheritance::test_inherit_fields_from_schema_subclass tests/test_schema.py::TestFieldInheritance::test_inherit_fields_from_non_schema_subclass tests/test_schema.py::TestFieldInheritance::test_inheritance_follows_mro tests/test_schema.py::TestGetAttribute::test_get_attribute_is_used tests/test_schema.py::TestGetAttribute::test_get_attribute_with_many tests/test_schema.py::TestRequiredFields::test_required_string_field_missing tests/test_schema.py::TestRequiredFields::test_required_string_field_failure tests/test_schema.py::TestRequiredFields::test_allow_none_param tests/test_schema.py::TestRequiredFields::test_allow_none_custom_message tests/test_schema.py::TestDefaults::test_missing_inputs_are_excluded_from_dump_output tests/test_schema.py::TestDefaults::test_none_is_serialized_to_none tests/test_schema.py::TestDefaults::test_default_and_value_missing tests/test_schema.py::TestDefaults::test_loading_none tests/test_schema.py::TestDefaults::test_missing_inputs_are_excluded_from_load_output tests/test_schema.py::TestLoadOnly::test_load_only tests/test_schema.py::TestLoadOnly::test_dump_only tests/test_schema.py::TestLoadOnly::test_url_field_requre_tld_false
CHANGELOG.rst docs/quickstart.rst src/marshmallow/schema.py
CHANGELOG.rst docs/quickstart.rst src/marshmallow/schema.py