diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md
index c8382833f..66bb45cc7 100644
--- a/docs/book/content/types/scalars.md
+++ b/docs/book/content/types/scalars.md
@@ -40,34 +40,61 @@ crates. They are enabled via features that are on by default.
 * url::Url
 * bson::oid::ObjectId
 
-## newtype pattern
+
+
+
+## Custom scalars
+
+### `#[graphql(transparent)]` attribute
 
 Often, you might need a custom scalar that just wraps an existing type.
 
 This can be done with the newtype pattern and a custom derive, similar to how
 serde supports this pattern with `#[serde(transparent)]`.
 
-```rust
+```rust,ignore
 # extern crate juniper;
-#[derive(juniper::GraphQLScalarValue)]
+#
+#[derive(juniper::GraphQLScalar)]
+#[graphql(transparent)]
 pub struct UserId(i32);
 
 #[derive(juniper::GraphQLObject)]
 struct User {
     id: UserId,
 }
+#
+# fn main() {}
+```
+
+`#[derive(GraphQLScalar)]` is mostly interchangeable with `#[graphql_scalar]` 
+attribute:
+
+```rust,ignore
+# extern crate juniper;
+# use juniper::graphql_scalar;
+#
+#[graphql_scalar(transparent)]
+pub struct UserId {
+  value: i32,
+}
 
+#[derive(juniper::GraphQLObject)]
+struct User {
+    id: UserId,
+}
+#
 # fn main() {}
 ```
 
-That's it, you can now user `UserId` in your schema.
+That's it, you can now use `UserId` in your schema.
 
 The macro also allows for more customization:
 
-```rust
+```rust,ignore
 # extern crate juniper;
 /// You can use a doc comment to specify a description.
-#[derive(juniper::GraphQLScalarValue)]
+#[derive(juniper::GraphQLScalar)]
 #[graphql(
     transparent,
     // Overwrite the GraphQL type name.
@@ -77,37 +104,276 @@ The macro also allows for more customization:
     description = "My user id description",
 )]
 pub struct UserId(i32);
+#
+# fn main() {}
+```
+
+All the methods used from newtype's field can be replaced with attributes:
+
+### `#[graphql(to_output_with = <fn>)]` attribute
+
+```rust,ignore
+# use juniper::{GraphQLScalar, ScalarValue, Value};
+#
+#[derive(GraphQLScalar)]
+#[graphql(to_output_with = to_output, transparent)]
+struct Incremented(i32);
+
+/// Increments [`Incremented`] before converting into a [`Value`].
+fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
+    Value::from(v.0 + 1)
+}
+# 
+# fn main() {}
+```
+
+### `#[graphql(from_input_with = <fn>)]` attribute
 
+```rust,ignore
+# use juniper::{GraphQLScalar, InputValue, ScalarValue};
+#
+#[derive(GraphQLScalar)]
+#[graphql(from_input_with = Self::from_input, transparent)]
+struct UserId(String);
+
+impl UserId {
+    /// Checks whether [`InputValue`] is `String` beginning with `id: ` and
+    /// strips it.
+    fn from_input<S>(input: &InputValue<S>) -> Result<Self, String> 
+    where
+        S: ScalarValue
+    {
+        input.as_string_value()
+            .ok_or_else(|| format!("Expected `String`, found: {}", input))
+            .and_then(|str| {
+                str.strip_prefix("id: ")
+                    .ok_or_else(|| {
+                        format!(
+                            "Expected `UserId` to begin with `id: `, \
+                             found: {}",
+                            input,
+                        )
+                    })
+            })
+            .map(|id| Self(id.to_owned()))
+    }
+}
+#
 # fn main() {}
 ```
 
-## Custom scalars
+### `#[graphql(parse_token_with = <fn>]` or `#[graphql(parse_token(<types>)]` attributes
 
-For more complex situations where you also need custom parsing or validation,
-you can use the `graphql_scalar` proc macro.
+```rust,ignore
+# use juniper::{
+#     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, 
+#     ScalarValue, ScalarToken, Value
+# };
+#
+#[derive(GraphQLScalar)]
+#[graphql(
+    to_output_with = to_output,
+    from_input_with = from_input,
+    parse_token_with = parse_token,
+//  ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)`
+//                   which tries to parse as `String` and then as `i32`
+//                   if prior fails.
+)]
+enum StringOrInt {
+    String(String),
+    Int(i32),
+}
 
-Typically, you represent your custom scalars as strings.
+fn to_output<S>(v: &StringOrInt) -> Value<S> 
+where
+    S: ScalarValue
+{
+    match v {
+        StringOrInt::String(str) => Value::scalar(str.to_owned()),
+        StringOrInt::Int(i) => Value::scalar(*i),
+    }
+}
 
-The example below implements a custom scalar for a custom `Date` type.
+fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String> 
+where
+    S: ScalarValue
+{
+    v.as_string_value()
+        .map(|s| StringOrInt::String(s.to_owned()))
+        .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
+        .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+}
 
-Note: juniper already has built-in support for the `chrono::DateTime` type
-via `chrono` feature, which is enabled by default and should be used for this
-purpose.
+fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> 
+where
+    S: ScalarValue
+{
+    <String as ParseScalarValue<S>>::from_str(value)
+        .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
+}
+#
+# fn main() {}
+```
 
-The example below is used just for illustration.
+> __NOTE:__ As you can see, once you provide all 3 custom resolvers, there
+>           is no need to follow `newtype` pattern.
 
-**Note**: the example assumes that the `Date` type implements
-`std::fmt::Display` and `std::str::FromStr`.
+### `#[graphql(with = <path>)]` attribute
 
+Instead of providing all custom resolvers, you can provide path to the `to_output`, 
+`from_input`, `parse_token` functions.
+
+Path can be simply `with = Self` (default path where macro expects resolvers to be), 
+in case there is an impl block with custom resolvers:
+
+```rust,ignore
+# use juniper::{
+#     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
+#     ScalarValue, ScalarToken, Value
+# };
+#
+#[derive(GraphQLScalar)]
+// #[graphql(with = Self)] <- default behaviour
+enum StringOrInt {
+    String(String),
+    Int(i32),
+}
+
+impl StringOrInt {
+    fn to_output<S: ScalarValue>(&self) -> Value<S> {
+        match self {
+            Self::String(str) => Value::scalar(str.to_owned()),
+            Self::Int(i) => Value::scalar(*i),
+        }
+    }
+  
+    fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
+    where
+        S: ScalarValue,
+    {
+        v.as_string_value()
+            .map(|s| Self::String(s.to_owned()))
+            .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+    }
+  
+    fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
+    where
+        S: ScalarValue,
+    {
+        <String as ParseScalarValue<S>>::from_str(value)
+            .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
+    }
+}
+#
+# fn main() {}
+```
+
+Or it can be path to a module, where custom resolvers are located.
+
+```rust,ignore
+# use juniper::{
+#     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, 
+#     ScalarValue, ScalarToken, Value
+# };
+#
+#[derive(GraphQLScalar)]
+#[graphql(with = string_or_int)]
+enum StringOrInt {
+    String(String),
+    Int(i32),
+}
+
+mod string_or_int {
+    use super::*;
+
+    pub(super) fn to_output<S>(v: &StringOrInt) -> Value<S>
+    where
+        S: ScalarValue,
+    {
+        match v {
+            StringOrInt::String(str) => Value::scalar(str.to_owned()),
+            StringOrInt::Int(i) => Value::scalar(*i),
+        }
+    }
+  
+    pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
+    where
+        S: ScalarValue,
+    {
+        v.as_string_value()
+            .map(|s| StringOrInt::String(s.to_owned()))
+            .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
+            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+    }
+  
+    pub(super) fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
+    where
+        S: ScalarValue,
+    {
+        <String as ParseScalarValue<S>>::from_str(value)
+            .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
+    }
+}
+#
+# fn main() {}
+```
+
+Also, you can partially override `#[graphql(with)]` attribute with other custom scalars.
+
+```rust,ignore
+# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value};
+#
+#[derive(GraphQLScalar)]
+#[graphql(parse_token(String, i32))]
+enum StringOrInt {
+    String(String),
+    Int(i32),
+}
+
+impl StringOrInt {
+    fn to_output<S>(&self) -> Value<S>
+    where
+        S: ScalarValue,
+    {
+        match self {
+            Self::String(str) => Value::scalar(str.to_owned()),
+            Self::Int(i) => Value::scalar(*i),
+        }
+    }
+  
+    fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
+    where
+        S: ScalarValue,
+    {
+        v.as_string_value()
+            .map(|s| Self::String(s.to_owned()))
+            .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+    }
+}
+#
+# fn main() {}
+```
+
+### Using foreign types as scalars
+
+For implementing custom scalars on foreign types there is `#[graphql_scalar]` attribute macro.
+
+> __NOTE:__ To satisfy [orphan rules] you should provide local [`ScalarValue`] implementation.
 
 ```rust
 # extern crate juniper;
 # mod date {
 #    pub struct Date;
 #    impl std::str::FromStr for Date {
-#        type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
+#        type Err = String;
+#
+#        fn from_str(_value: &str) -> Result<Self, Self::Err> { 
+#            unimplemented!()
+#        }
 #    }
-#    // And we define how to represent date as a string.
+#
 #    impl std::fmt::Display for Date {
 #        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
 #            unimplemented!()
@@ -115,32 +381,34 @@ The example below is used just for illustration.
 #    }
 # }
 #
-use juniper::{Value, ParseScalarResult, ParseScalarValue};
-use date::Date;
-
-#[juniper::graphql_scalar(description = "Date")]
-impl<S> GraphQLScalar for Date
-where
-    S: ScalarValue
-{
-    // Define how to convert your custom scalar into a primitive type.
-    fn resolve(&self) -> Value {
-        Value::scalar(self.to_string())
-    }
-
-    // Define how to parse a primitive type into your custom scalar.
-    // NOTE: The error type should implement `IntoFieldError<S>`.
-    fn from_input_value(v: &InputValue) -> Result<Date, String> {
-        v.as_string_value()
-            .ok_or_else(|| format!("Expected `String`, found: {}", v))
-            .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
+# use juniper::DefaultScalarValue as CustomScalarValue;
+use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(
+    with = date_scalar, 
+    parse_token(String),
+    scalar = CustomScalarValue,
+//           ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
+)]
+type Date = date::Date;
+//          ^^^^^^^^^^ Type from another crate.
+
+mod date_scalar {
+    use super::*;
+  
+    pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
+        Value::scalar(v.to_string())
     }
 
-    // Define how to parse a string value.
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <String as ParseScalarValue<S>>::from_str(value)
+    pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
+      v.as_string_value()
+          .ok_or_else(|| format!("Expected `String`, found: {}", v))
+          .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
     }
 }
 #
 # fn main() {}
 ```
+
+[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
+[`ScalarValue`]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr
index 61340f732..726e30c8d 100644
--- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr
+++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr
@@ -1,21 +1,21 @@
-error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
+error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
   --> fail/interface/implementers_duplicate_ugly.rs:11:1
    |
 11 | #[graphql_interface(for = [ObjA, ObjAlias])]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    | |
    | first implementation here
-   | conflicting implementation for `ObjA`
+   | conflicting implementation for `CharacterValueEnum<ObjA, ObjA>`
    |
-   = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info)
+   = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
+error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
   --> fail/interface/implementers_duplicate_ugly.rs:11:1
    |
 11 | #[graphql_interface(for = [ObjA, ObjAlias])]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    | |
    | first implementation here
-   | conflicting implementation for `CharacterValueEnum<ObjA, ObjA>`
+   | conflicting implementation for `ObjA`
    |
-   = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
+   = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs
new file mode 100644
index 000000000..4d93bc1f6
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs
@@ -0,0 +1,16 @@
+use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
+struct ScalarSpecifiedByUrl(i32);
+
+impl ScalarSpecifiedByUrl {
+    fn to_output<S: ScalarValue>(&self) -> Value<S> {
+        Value::scalar(0)
+    }
+
+    fn from_input<S: ScalarValue>(_: &InputValue<S>) -> Result<Self, String> {
+        Ok(Self)
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr
new file mode 100644
index 000000000..1b6edaebc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr
@@ -0,0 +1,17 @@
+error: Invalid URL: relative URL without a base
+ --> fail/scalar/derive_input/impl_invalid_url.rs:3:37
+  |
+3 | #[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
+  |                                     ^^^^^^^^^^^^
+
+error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope
+ --> fail/scalar/derive_input/impl_invalid_url.rs:6:6
+  |
+6 | impl ScalarSpecifiedByUrl {
+  |      ^^^^^^^^^^^^^^^^^^^^ not found in this scope
+
+error: the `Self` constructor can only be used with tuple or unit structs
+  --> fail/scalar/derive_input/impl_invalid_url.rs:12:12
+   |
+12 |         Ok(Self)
+   |            ^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs
deleted file mode 100644
index 50549f11b..000000000
--- a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use juniper::GraphQLScalarValue;
-
-#[derive(GraphQLScalarValue)]
-#[graphql(specified_by_url = "not an url")]
-struct ScalarSpecifiedByUrl(i64);
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr
deleted file mode 100644
index 9a0d5afde..000000000
--- a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: Invalid URL: relative URL without a base
- --> fail/scalar/derive_invalid_url.rs:4:30
-  |
-4 | #[graphql(specified_by_url = "not an url")]
-  |                              ^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs
deleted file mode 100644
index a71da71b1..000000000
--- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use juniper::graphql_scalar;
-
-struct ScalarSpecifiedByUrl(i32);
-
-#[graphql_scalar(specified_by_url = "not an url")]
-impl GraphQLScalar for ScalarSpecifiedByUrl {
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
-        v.as_int_value()
-            .map(ScalarSpecifiedByUrl)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-        <i32 as ParseScalarValue>::from_str(value)
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr
deleted file mode 100644
index 999a04b6b..000000000
--- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: Invalid URL: relative URL without a base
- --> fail/scalar/impl_invalid_url.rs:5:18
-  |
-5 | #[graphql_scalar(specified_by_url = "not an url")]
-  |                  ^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs
new file mode 100644
index 000000000..61b9c340e
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs
@@ -0,0 +1,26 @@
+use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
+
+struct ScalarSpecifiedByUrl;
+
+#[graphql_scalar(
+    specified_by_url = "not an url",
+    with = scalar,
+    parse_token(i32),
+)]
+type Scalar = ScalarSpecifiedByUrl;
+
+mod scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(_: &ScalarSpecifiedByUrl) -> Value<S> {
+        Value::scalar(0)
+    }
+
+    pub(super) fn from_input<S: ScalarValue>(
+        _: &InputValue<S>,
+    ) -> Result<ScalarSpecifiedByUrl, String> {
+        Ok(ScalarSpecifiedByUrl)
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr
new file mode 100644
index 000000000..b000693ca
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr
@@ -0,0 +1,5 @@
+error: Invalid URL: relative URL without a base
+ --> fail/scalar/type_alias/impl_invalid_url.rs:6:24
+  |
+6 |     specified_by_url = "not an url",
+  |                        ^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.rs
new file mode 100644
index 000000000..3b37e214b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.rs
@@ -0,0 +1,14 @@
+use juniper::{graphql_scalar, Value};
+
+struct Scalar;
+
+#[graphql_scalar(to_output_with = Scalar::to_output)]
+type CustomScalar = Scalar;
+
+impl Scalar {
+    fn to_output(&self) -> Value {
+        Value::scalar(0)
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr
new file mode 100644
index 000000000..9502bb062
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
+ --> fail/scalar/type_alias/impl_with_not_all_resolvers.rs:6:1
+  |
+6 | type CustomScalar = Scalar;
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs
new file mode 100644
index 000000000..e428deb2a
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs
@@ -0,0 +1,8 @@
+use juniper::graphql_scalar;
+
+struct Scalar;
+
+#[graphql_scalar]
+type CustomScalar = Scalar;
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr
new file mode 100644
index 000000000..93f6eb7ab
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
+ --> fail/scalar/type_alias/impl_without_resolvers.rs:6:1
+  |
+6 | type CustomScalar = Scalar;
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml
index 0a6ce2c1c..d037598b5 100644
--- a/integration_tests/juniper_tests/Cargo.toml
+++ b/integration_tests/juniper_tests/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2018"
 publish = false
 
 [dependencies]
+chrono = "0.4"
 derive_more = "0.99"
 futures = "0.3"
 juniper = { path = "../../juniper" }
diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs
deleted file mode 100644
index 24e329bcf..000000000
--- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs
+++ /dev/null
@@ -1,125 +0,0 @@
-use juniper::{
-    execute, graphql_value, EmptyMutation, EmptySubscription, FromInputValue, InputValue, RootNode,
-    ToInputValue, Value, Variables,
-};
-
-use crate::custom_scalar::MyScalarValue;
-
-#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalarValue)]
-#[graphql(
-    transparent,
-    scalar = MyScalarValue,
-    specified_by_url = "https://tools.ietf.org/html/rfc4122",
-)]
-pub struct LargeId(i64);
-
-#[derive(juniper::GraphQLObject)]
-#[graphql(scalar = MyScalarValue)]
-struct User {
-    id: LargeId,
-}
-
-struct Query;
-
-#[juniper::graphql_object(scalar = MyScalarValue)]
-impl Query {
-    fn user() -> User {
-        User { id: LargeId(0) }
-    }
-}
-
-struct Mutation;
-
-#[juniper::graphql_object(scalar = MyScalarValue)]
-impl Mutation {
-    fn change_user(id: LargeId) -> User {
-        User { id }
-    }
-}
-
-#[test]
-fn test_scalar_value_large_id() {
-    let num: i64 = 4294967297;
-
-    let input_integer: InputValue<MyScalarValue> =
-        serde_json::from_value(serde_json::json!(num)).unwrap();
-
-    let output: LargeId =
-        FromInputValue::<MyScalarValue>::from_input_value(&input_integer).unwrap();
-    assert_eq!(output, LargeId(num));
-
-    let id = LargeId(num);
-    let output = ToInputValue::<MyScalarValue>::to_input_value(&id);
-    assert_eq!(output, InputValue::scalar(num));
-}
-
-#[tokio::test]
-async fn test_scalar_value_large_specified_url() {
-    let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let doc = r#"{
-        __type(name: "LargeId") {
-            specifiedByUrl
-        }
-    }"#;
-
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
-        Ok((
-            graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc4122"}}),
-            vec![],
-        )),
-    );
-}
-
-#[tokio::test]
-async fn test_scalar_value_large_query() {
-    let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let doc = r#"{
-        user { id }
-    }"#;
-
-    let val = Value::<MyScalarValue>::scalar(0_i64);
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
-        Ok((graphql_value!({"user": {"id": val}}), vec![])),
-    );
-}
-
-#[tokio::test]
-async fn test_scalar_value_large_mutation() {
-    let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
-        Query,
-        Mutation,
-        EmptySubscription::<()>::new(),
-    );
-
-    let doc = r#"mutation {
-        changeUser(id: 1) { id }
-    }"#;
-
-    let val = Value::<MyScalarValue>::scalar(1_i64);
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
-        Ok((graphql_value!({"changeUser": {"id": val}}), vec![])),
-    );
-
-    let doc = r#"mutation {
-        changeUser(id: 4294967297) { id }
-    }"#;
-
-    let val = Value::<MyScalarValue>::scalar(4294967297_i64);
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
-        Ok((graphql_value!({"changeUser": {"id": val}}), vec![])),
-    );
-}
diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs
deleted file mode 100644
index b852416ec..000000000
--- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs
+++ /dev/null
@@ -1,405 +0,0 @@
-use juniper::{
-    execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue,
-    EmptyMutation, EmptySubscription, Object, ParseScalarResult, ParseScalarValue, RootNode, Value,
-};
-
-use crate::custom_scalar::MyScalarValue;
-
-struct DefaultName(i32);
-struct OtherOrder(i32);
-struct Named(i32);
-struct ScalarDescription(i32);
-struct ScalarSpecifiedByUrl(i32);
-struct Generated(String);
-
-struct Root;
-
-/*
-
-Syntax to validate:
-
-* Default name vs. custom name
-* Description vs. no description on the scalar
-
-*/
-
-#[graphql_scalar]
-impl<S> GraphQLScalar for DefaultName
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<DefaultName, String> {
-        v.as_int_value()
-            .map(DefaultName)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <i32 as ParseScalarValue<S>>::from_str(value)
-    }
-}
-
-#[graphql_scalar]
-impl GraphQLScalar for OtherOrder {
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<OtherOrder, String> {
-        v.as_int_value()
-            .map(OtherOrder)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-        <i32 as ParseScalarValue>::from_str(value)
-    }
-}
-
-#[graphql_scalar(name = "ANamedScalar")]
-impl GraphQLScalar for Named {
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<Named, String> {
-        v.as_int_value()
-            .map(Named)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-        <i32 as ParseScalarValue>::from_str(value)
-    }
-}
-
-#[graphql_scalar(description = "A sample scalar, represented as an integer")]
-impl GraphQLScalar for ScalarDescription {
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<ScalarDescription, String> {
-        v.as_int_value()
-            .map(ScalarDescription)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-        <i32 as ParseScalarValue>::from_str(value)
-    }
-}
-
-#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")]
-impl GraphQLScalar for ScalarSpecifiedByUrl {
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
-        v.as_int_value()
-            .map(ScalarSpecifiedByUrl)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-        <i32 as ParseScalarValue>::from_str(value)
-    }
-}
-
-macro_rules! impl_scalar {
-    ($name: ident) => {
-        #[graphql_scalar]
-        impl<S> GraphQLScalar for $name
-        where
-            S: ScalarValue,
-        {
-            fn resolve(&self) -> Value {
-                Value::scalar(self.0.clone())
-            }
-
-            fn from_input_value(v: &InputValue) -> Result<Self, &'static str> {
-                v.as_scalar_value()
-                    .and_then(|v| v.as_str())
-                    .and_then(|s| Some(Self(s.to_owned())))
-                    .ok_or_else(|| "Expected `String`")
-            }
-
-            fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-                <String as ParseScalarValue<S>>::from_str(value)
-            }
-        }
-    };
-}
-
-impl_scalar!(Generated);
-
-#[graphql_object(scalar = DefaultScalarValue)]
-impl Root {
-    fn default_name() -> DefaultName {
-        DefaultName(0)
-    }
-    fn other_order() -> OtherOrder {
-        OtherOrder(0)
-    }
-    fn named() -> Named {
-        Named(0)
-    }
-    fn scalar_description() -> ScalarDescription {
-        ScalarDescription(0)
-    }
-    fn scalar_specified_by_url() -> ScalarSpecifiedByUrl {
-        ScalarSpecifiedByUrl(0)
-    }
-    fn generated() -> Generated {
-        Generated("foo".to_owned())
-    }
-}
-
-struct WithCustomScalarValue(i32);
-
-#[graphql_scalar]
-impl GraphQLScalar for WithCustomScalarValue {
-    fn resolve(&self) -> Value<MyScalarValue> {
-        Value::scalar(self.0)
-    }
-
-    fn from_input_value(v: &InputValue<MyScalarValue>) -> Result<WithCustomScalarValue, String> {
-        v.as_int_value()
-            .map(WithCustomScalarValue)
-            .ok_or_else(|| format!("Expected Int, found: {}", v))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
-        <i32 as ParseScalarValue<MyScalarValue>>::from_str(value)
-    }
-}
-
-struct RootWithCustomScalarValue;
-
-#[graphql_object(scalar = MyScalarValue)]
-impl RootWithCustomScalarValue {
-    fn with_custom_scalar_value() -> WithCustomScalarValue {
-        WithCustomScalarValue(0)
-    }
-}
-
-async fn run_type_info_query<F>(doc: &str, f: F)
-where
-    F: Fn(&Object<DefaultScalarValue>) -> (),
-{
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let (result, errs) = execute(doc, None, &schema, &graphql_vars! {}, &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    f(type_info);
-}
-
-#[test]
-fn path_in_resolve_return_type() {
-    struct ResolvePath(i32);
-
-    #[graphql_scalar]
-    impl GraphQLScalar for ResolvePath {
-        fn resolve(&self) -> self::Value {
-            Value::scalar(self.0)
-        }
-
-        fn from_input_value(v: &InputValue) -> Result<ResolvePath, String> {
-            v.as_int_value()
-                .map(ResolvePath)
-                .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-        }
-
-        fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
-            <i32 as ParseScalarValue>::from_str(value)
-        }
-    }
-}
-
-#[tokio::test]
-async fn default_name_introspection() {
-    let doc = r#"
-    {
-        __type(name: "DefaultName") {
-            name
-            description
-        }
-    }
-    "#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("DefaultName")),
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(&graphql_value!(null)),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn other_order_introspection() {
-    let doc = r#"
-    {
-        __type(name: "OtherOrder") {
-            name
-            description
-        }
-    }
-    "#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("OtherOrder")),
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(&graphql_value!(null)),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn named_introspection() {
-    let doc = r#"
-    {
-        __type(name: "ANamedScalar") {
-            name
-            description
-        }
-    }
-    "#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("ANamedScalar")),
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(&graphql_value!(null)),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn scalar_description_introspection() {
-    let doc = r#"
-    {
-        __type(name: "ScalarDescription") {
-            name
-            description
-            specifiedByUrl
-        }
-    }
-    "#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("ScalarDescription")),
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(&graphql_value!(
-                "A sample scalar, represented as an integer",
-            )),
-        );
-        assert_eq!(
-            type_info.get_field_value("specifiedByUrl"),
-            Some(&graphql_value!(null)),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn scalar_specified_by_url_introspection() {
-    let doc = r#"{
-        __type(name: "ScalarSpecifiedByUrl") {
-            name
-            specifiedByUrl
-        }
-    }"#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("ScalarSpecifiedByUrl")),
-        );
-        assert_eq!(
-            type_info.get_field_value("specifiedByUrl"),
-            Some(&graphql_value!("https://tools.ietf.org/html/rfc4122")),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn generated_scalar_introspection() {
-    let doc = r#"
-    {
-        __type(name: "Generated") {
-            name
-            description
-        }
-    }
-    "#;
-
-    run_type_info_query(doc, |type_info| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&graphql_value!("Generated")),
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(&graphql_value!(null)),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn resolves_with_custom_scalar_value() {
-    const DOC: &str = r#"{ withCustomScalarValue }"#;
-
-    let schema = RootNode::<_, _, _, MyScalarValue>::new_with_scalar_value(
-        RootWithCustomScalarValue,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    assert_eq!(
-        execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
-        Ok((graphql_value!({"withCustomScalarValue": 0}), vec![])),
-    );
-}
diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
index 7750a1501..e5bdc4fce 100644
--- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs
+++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
@@ -2,34 +2,11 @@
 
 use juniper::{
     execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
-    EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject,
-    GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue,
+    Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion,
+    IntoFieldError, ScalarValue,
 };
 
-fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
-where
-    Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
-{
-    RootNode::new(
-        query_root,
-        EmptyMutation::<C>::new(),
-        EmptySubscription::<C>::new(),
-    )
-}
-
-fn schema_with_scalar<'q, S, C, Q>(
-    query_root: Q,
-) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>, S>
-where
-    Q: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
-    S: ScalarValue + 'q,
-{
-    RootNode::new_with_scalar_value(
-        query_root,
-        EmptyMutation::<C>::new(),
-        EmptySubscription::<C>::new(),
-    )
-}
+use crate::util::{schema, schema_with_scalar};
 
 mod no_implers {
     use super::*;
diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs
index 6348a66c8..f87b4a3c5 100644
--- a/integration_tests/juniper_tests/src/codegen/mod.rs
+++ b/integration_tests/juniper_tests/src/codegen/mod.rs
@@ -1,12 +1,11 @@
 mod derive_enum;
 mod derive_input_object;
 mod derive_object_with_raw_idents;
-mod derive_scalar;
-mod impl_scalar;
 mod interface_attr;
 mod object_attr;
 mod object_derive;
-mod scalar_value_transparent;
+mod scalar_attr_derive_input;
+mod scalar_attr_type_alias;
 mod subscription_attr;
 mod union_attr;
 mod union_derive;
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs
new file mode 100644
index 000000000..d50cc58c9
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs
@@ -0,0 +1,972 @@
+use std::fmt;
+
+use chrono::{DateTime, TimeZone, Utc};
+use juniper::{
+    execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue,
+    ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value,
+};
+
+use crate::{
+    custom_scalar::MyScalarValue,
+    util::{schema, schema_with_scalar},
+};
+
+mod trivial {
+    use super::*;
+
+    #[graphql_scalar]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(t)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod all_custom_resolvers {
+    use super::*;
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+    )]
+    #[graphql_scalar(parse_token_with = parse_token)]
+    struct Counter(i32);
+
+    fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+        Value::scalar(v.0)
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+        v.as_int_value()
+            .map(Counter)
+            .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+    }
+
+    fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+        <i32 as ParseScalarValue<S>>::from_str(value)
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod explicit_name {
+    use super::*;
+
+    #[graphql_scalar(name = "Counter")]
+    struct CustomCounter(i32);
+
+    impl CustomCounter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(value)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: CustomCounter) -> CustomCounter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod delegated_parse_token {
+    use super::*;
+
+    #[graphql_scalar(parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod multiple_delegated_parse_token {
+    use super::*;
+
+    #[graphql_scalar(parse_token(String, i32))]
+    enum StringOrInt {
+        String(String),
+        Int(i32),
+    }
+
+    impl StringOrInt {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            match self {
+                Self::String(str) => Value::scalar(str.to_owned()),
+                Self::Int(i) => Value::scalar(*i),
+            }
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_string_value()
+                .map(|s| Self::String(s.to_owned()))
+                .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+                .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn string_or_int(value: StringOrInt) -> StringOrInt {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_string() {
+        const DOC: &str = r#"{ stringOrInt(value: "test") }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": "test"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_int() {
+        const DOC: &str = r#"{ stringOrInt(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": 0}), vec![])),
+        );
+    }
+}
+
+mod where_attribute {
+    use super::*;
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token(String),
+        where(Tz: From<Utc>, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
+
+    fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        Value::scalar(v.0.to_rfc3339())
+    }
+
+    fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        v.as_string_value()
+            .ok_or_else(|| format!("Expected `String`, found: {}", v))
+            .and_then(|s| {
+                DateTime::parse_from_rfc3339(s)
+                    .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
+                    .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+            })
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod with_self {
+    use super::*;
+
+    #[graphql_scalar(with = Self)]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(value)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod with_module {
+    use super::*;
+
+    #[graphql_scalar(
+        with = custom_date_time,
+        parse_token(String),
+        where(Tz: From<Utc>, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
+
+    mod custom_date_time {
+        use super::*;
+
+        pub(super) fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            Value::scalar(v.0.to_rfc3339())
+        }
+
+        pub(super) fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            v.as_string_value()
+                .ok_or_else(|| format!("Expected `String`, found: {}", v))
+                .and_then(|s| {
+                    DateTime::parse_from_rfc3339(s)
+                        .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
+                        .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+                })
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    /// Description
+    #[graphql_scalar(parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_attribute {
+    use super::*;
+
+    /// Doc comment
+    #[graphql_scalar(description = "Description from attribute", parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description from attribute"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod custom_scalar {
+    use super::*;
+
+    /// Description
+    #[graphql_scalar(scalar = MyScalarValue, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic_scalar {
+    use super::*;
+
+    /// Description
+    #[graphql_scalar(scalar = S: ScalarValue, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![]
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    #[graphql_scalar(scalar = S: ScalarValue + Clone, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs
new file mode 100644
index 000000000..3f8854c3a
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs
@@ -0,0 +1,996 @@
+use std::fmt;
+
+use chrono::{DateTime, TimeZone, Utc};
+use juniper::{
+    execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue,
+    ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value,
+};
+
+use crate::{
+    custom_scalar::MyScalarValue,
+    util::{schema, schema_with_scalar},
+};
+
+mod all_custom_resolvers {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+    )]
+    #[graphql_scalar(
+        parse_token_with = parse_token,
+    )]
+    type Counter = CustomCounter;
+
+    fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+        Value::scalar(v.0)
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+        v.as_int_value()
+            .map(CustomCounter)
+            .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+    }
+
+    fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+        <i32 as ParseScalarValue<S>>::from_str(value)
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod explicit_name {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    #[graphql_scalar(
+        name = "Counter",
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token_with = parse_token,
+    )]
+    type CounterScalar = CustomCounter;
+
+    fn to_output<S: ScalarValue>(v: &CounterScalar) -> Value<S> {
+        Value::scalar(v.0)
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<CounterScalar, String> {
+        v.as_int_value()
+            .map(CustomCounter)
+            .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+    }
+
+    fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+        <i32 as ParseScalarValue<S>>::from_str(value)
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: CounterScalar) -> CounterScalar {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn no_custom_counter() {
+        for name in ["CustomCounter", "CustomScalar"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        kind
+                    }}
+                }}"#,
+                name,
+            );
+
+            let schema = schema(QueryRoot);
+
+            assert_eq!(
+                execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
+                Ok((graphql_value!(null), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod delegated_parse_token {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token(i32),
+    )]
+    type Counter = CustomCounter;
+
+    fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+        Value::scalar(v.0)
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+        v.as_int_value()
+            .map(CustomCounter)
+            .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod multiple_delegated_parse_token {
+    use super::*;
+
+    enum StringOrIntScalar {
+        String(String),
+        Int(i32),
+    }
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token(String, i32),
+    )]
+    type StringOrInt = StringOrIntScalar;
+
+    fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
+        match v {
+            StringOrInt::String(str) => Value::scalar(str.to_owned()),
+            StringOrInt::Int(i) => Value::scalar(*i),
+        }
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
+        v.as_string_value()
+            .map(|s| StringOrInt::String(s.to_owned()))
+            .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
+            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn string_or_int(value: StringOrInt) -> StringOrInt {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_string() {
+        const DOC: &str = r#"{ stringOrInt(value: "test") }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": "test"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_int() {
+        const DOC: &str = r#"{ stringOrInt(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": 0}), vec![])),
+        );
+    }
+}
+
+mod where_attribute {
+    use super::*;
+
+    struct CustomDateTimeScalar<Tz: TimeZone>(DateTime<Tz>);
+
+    #[graphql_scalar(
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token(String),
+        where(Tz: From<Utc> + TimeZone, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    type CustomDateTime<Tz> = CustomDateTimeScalar<Tz>;
+
+    fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        Value::scalar(v.0.to_rfc3339())
+    }
+
+    fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        v.as_string_value()
+            .ok_or_else(|| format!("Expected `String`, found: {}", v))
+            .and_then(|s| {
+                DateTime::parse_from_rfc3339(s)
+                    .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc))))
+                    .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+            })
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod with_self {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    #[graphql_scalar(with = Self)]
+    type Counter = CustomCounter;
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(value)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod with_module {
+    use super::*;
+
+    struct CustomDateTimeScalar<Tz: TimeZone>(DateTime<Tz>);
+
+    #[graphql_scalar(
+        with = custom_date_time,
+        parse_token(String),
+        where(Tz: From<Utc> + TimeZone, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    type CustomDateTime<Tz> = CustomDateTimeScalar<Tz>;
+
+    mod custom_date_time {
+        use super::*;
+
+        pub(super) fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            Value::scalar(v.0.to_rfc3339())
+        }
+
+        pub(super) fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            v.as_string_value()
+                .ok_or_else(|| format!("Expected `String`, found: {}", v))
+                .and_then(|s| {
+                    DateTime::parse_from_rfc3339(s)
+                        .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc))))
+                        .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+                })
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    /// Description
+    #[graphql_scalar(with = counter, parse_token(i32))]
+    type Counter = CustomCounter;
+
+    mod counter {
+        use super::*;
+
+        pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+            Value::scalar(v.0)
+        }
+
+        pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+            v.as_int_value()
+                .map(CustomCounter)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_attribute {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    /// Doc comment
+    #[graphql_scalar(
+        description = "Description from attribute",
+        with = counter,
+        parse_token(i32),
+    )]
+    type Counter = CustomCounter;
+
+    mod counter {
+        use super::*;
+
+        pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+            Value::scalar(v.0)
+        }
+
+        pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+            v.as_int_value()
+                .map(CustomCounter)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description from attribute"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod custom_scalar {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    /// Description
+    #[graphql_scalar(
+        scalar = MyScalarValue,
+        with = counter,
+        parse_token(i32),
+    )]
+    type Counter = CustomCounter;
+
+    mod counter {
+        use super::*;
+
+        pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+            Value::scalar(v.0)
+        }
+
+        pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+            v.as_int_value()
+                .map(CustomCounter)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic_scalar {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    /// Description
+    #[graphql_scalar(
+        scalar = S: ScalarValue,
+        with = counter,
+        parse_token(i32),
+    )]
+    type Counter = CustomCounter;
+
+    mod counter {
+        use super::*;
+
+        pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+            Value::scalar(v.0)
+        }
+
+        pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+            v.as_int_value()
+                .map(CustomCounter)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    struct CustomCounter(i32);
+
+    /// Description
+    #[graphql_scalar(
+        scalar = S: ScalarValue + Clone,
+        with = counter,
+        parse_token(i32),
+    )]
+    type Counter = CustomCounter;
+
+    mod counter {
+        use super::*;
+
+        pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+            Value::scalar(v.0)
+        }
+
+        pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+            v.as_int_value()
+                .map(CustomCounter)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
deleted file mode 100644
index 828f12d32..000000000
--- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-use fnv::FnvHashMap;
-use juniper::{
-    graphql_input_value, graphql_object, DefaultScalarValue, FromInputValue, GraphQLObject,
-    GraphQLScalarValue, GraphQLType, InputValue, Registry, ToInputValue,
-};
-
-#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
-#[graphql(transparent)]
-struct UserId(String);
-
-#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
-#[graphql(transparent, name = "MyUserId", description = "custom description...")]
-struct CustomUserId(String);
-
-/// The doc comment...
-#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
-#[graphql(transparent, specified_by_url = "https://tools.ietf.org/html/rfc4122")]
-struct IdWithDocComment(i32);
-
-#[derive(GraphQLObject)]
-struct User {
-    id: UserId,
-    id_custom: CustomUserId,
-}
-
-struct User2;
-
-#[graphql_object]
-impl User2 {
-    fn id(&self) -> UserId {
-        UserId("id".to_string())
-    }
-}
-
-#[test]
-fn test_scalar_value_simple() {
-    assert_eq!(
-        <UserId as GraphQLType<DefaultScalarValue>>::name(&()),
-        Some("UserId")
-    );
-
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = UserId::meta(&(), &mut registry);
-    assert_eq!(meta.name(), Some("UserId"));
-    assert_eq!(meta.description(), None);
-
-    let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
-    let output: UserId = FromInputValue::from_input_value(&input).unwrap();
-    assert_eq!(output, UserId("userId1".into()),);
-
-    let id = UserId("111".into());
-    let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
-    assert_eq!(output, graphql_input_value!("111"));
-}
-
-#[test]
-fn test_scalar_value_custom() {
-    assert_eq!(
-        <CustomUserId as GraphQLType<DefaultScalarValue>>::name(&()),
-        Some("MyUserId")
-    );
-
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = CustomUserId::meta(&(), &mut registry);
-    assert_eq!(meta.name(), Some("MyUserId"));
-    assert_eq!(meta.description(), Some("custom description..."));
-    assert_eq!(meta.specified_by_url(), None);
-
-    let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
-    let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
-    assert_eq!(output, CustomUserId("userId1".into()),);
-
-    let id = CustomUserId("111".into());
-    let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
-    assert_eq!(output, graphql_input_value!("111"));
-}
-
-#[test]
-fn test_scalar_value_doc_comment() {
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = IdWithDocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some("The doc comment..."));
-    assert_eq!(
-        meta.specified_by_url(),
-        Some("https://tools.ietf.org/html/rfc4122"),
-    );
-}
diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs
index a59b73002..3e0606232 100644
--- a/integration_tests/juniper_tests/src/custom_scalar.rs
+++ b/integration_tests/juniper_tests/src/custom_scalar.rs
@@ -6,11 +6,11 @@ use juniper::{
     graphql_vars,
     parser::{ParseError, ScalarToken, Token},
     serde::{de, Deserialize, Deserializer, Serialize},
-    EmptyMutation, FieldResult, GraphQLScalarValue, InputValue, Object, ParseScalarResult,
-    RootNode, ScalarValue, Value, Variables,
+    EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, ScalarValue,
+    Value, Variables,
 };
 
-#[derive(GraphQLScalarValue, Clone, Debug, PartialEq, Serialize)]
+#[derive(Clone, Debug, PartialEq, Serialize)]
 #[serde(untagged)]
 pub(crate) enum MyScalarValue {
     Int(i32),
@@ -20,6 +20,149 @@ pub(crate) enum MyScalarValue {
     Boolean(bool),
 }
 
+// TODO: replace all underlying `From` impls with `GraphQLScalarValue` macro.
+impl From<i32> for MyScalarValue {
+    fn from(v: i32) -> Self {
+        Self::Int(v)
+    }
+}
+
+impl From<MyScalarValue> for Option<i32> {
+    fn from(v: MyScalarValue) -> Self {
+        if let MyScalarValue::Int(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a MyScalarValue> for Option<&'a i32> {
+    fn from(v: &'a MyScalarValue) -> Self {
+        if let MyScalarValue::Int(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<i64> for MyScalarValue {
+    fn from(v: i64) -> Self {
+        Self::Long(v)
+    }
+}
+
+impl From<MyScalarValue> for Option<i64> {
+    fn from(v: MyScalarValue) -> Self {
+        if let MyScalarValue::Long(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a MyScalarValue> for Option<&'a i64> {
+    fn from(v: &'a MyScalarValue) -> Self {
+        if let MyScalarValue::Long(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<f64> for MyScalarValue {
+    fn from(v: f64) -> Self {
+        Self::Float(v)
+    }
+}
+
+impl From<MyScalarValue> for Option<f64> {
+    fn from(v: MyScalarValue) -> Self {
+        if let MyScalarValue::Float(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a MyScalarValue> for Option<&'a f64> {
+    fn from(v: &'a MyScalarValue) -> Self {
+        if let MyScalarValue::Float(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<String> for MyScalarValue {
+    fn from(v: String) -> Self {
+        Self::String(v)
+    }
+}
+
+impl From<MyScalarValue> for Option<String> {
+    fn from(v: MyScalarValue) -> Self {
+        if let MyScalarValue::String(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a MyScalarValue> for Option<&'a String> {
+    fn from(v: &'a MyScalarValue) -> Self {
+        if let MyScalarValue::String(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<bool> for MyScalarValue {
+    fn from(v: bool) -> Self {
+        Self::Boolean(v)
+    }
+}
+
+impl From<MyScalarValue> for Option<bool> {
+    fn from(v: MyScalarValue) -> Self {
+        if let MyScalarValue::Boolean(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a MyScalarValue> for Option<&'a bool> {
+    fn from(v: &'a MyScalarValue) -> Self {
+        if let MyScalarValue::Boolean(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl fmt::Display for MyScalarValue {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Int(v) => v.fmt(f),
+            Self::Long(v) => v.fmt(f),
+            Self::Float(v) => v.fmt(f),
+            Self::String(v) => v.fmt(f),
+            Self::Boolean(v) => v.fmt(f),
+        }
+    }
+}
+
 impl ScalarValue for MyScalarValue {
     fn as_int(&self) -> Option<i32> {
         match self {
@@ -132,19 +275,23 @@ impl<'de> Deserialize<'de> for MyScalarValue {
     }
 }
 
-#[graphql_scalar(name = "Long")]
-impl GraphQLScalar for i64 {
-    fn resolve(&self) -> Value {
-        Value::scalar(*self)
+#[graphql_scalar(with = long, scalar = MyScalarValue)]
+type Long = i64;
+
+mod long {
+    use super::*;
+
+    pub(super) fn to_output(v: &Long) -> Value<MyScalarValue> {
+        Value::scalar(*v)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<i64, String> {
+    pub(super) fn from_input(v: &InputValue<MyScalarValue>) -> Result<Long, String> {
         v.as_scalar_value::<i64>()
             .copied()
             .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v))
     }
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
+    pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> {
         if let ScalarToken::Int(v) = value {
             v.parse()
                 .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs
index 0d166d2b7..2e59d8c30 100644
--- a/integration_tests/juniper_tests/src/lib.rs
+++ b/integration_tests/juniper_tests/src/lib.rs
@@ -37,7 +37,37 @@ mod pre_parse;
 /// Common utilities used across tests.
 pub(crate) mod util {
     use futures::StreamExt as _;
-    use juniper::{graphql_value, ExecutionError, GraphQLError, ScalarValue, Value, ValuesStream};
+    use juniper::{
+        graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError,
+        GraphQLError, GraphQLType, RootNode, ScalarValue, Value, ValuesStream,
+    };
+
+    pub(crate) fn schema<'q, C, Q>(
+        query_root: Q,
+    ) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
+    where
+        Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
+    {
+        RootNode::new(
+            query_root,
+            EmptyMutation::<C>::new(),
+            EmptySubscription::<C>::new(),
+        )
+    }
+
+    pub(crate) fn schema_with_scalar<'q, S, C, Q>(
+        query_root: Q,
+    ) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>, S>
+    where
+        Q: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
+        S: ScalarValue + 'q,
+    {
+        RootNode::new_with_scalar_value(
+            query_root,
+            EmptyMutation::<C>::new(),
+            EmptySubscription::<C>::new(),
+        )
+    }
 
     /// Extracts a single next value from the result returned by
     /// [`juniper::resolve_into_stream()`] and transforms it into a regular
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index c73bf9104..56da38c61 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -21,6 +21,13 @@
   - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields).
   - Forbid default impls on non-ignored trait methods.
   - Support coercion of additional nullable arguments and return sub-typing on implementer.
+- Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017))
+  - Support generic scalars.
+  - Support structs with single named field.
+  - Support overriding resolvers.
+- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014))
+  - Mirror `#[derive(GraphQLScalar)]` macro.
+  - Support usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules).
 
 ## Features
 
diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs
index 9b9de9497..47d5fead2 100644
--- a/juniper/src/executor/mod.rs
+++ b/juniper/src/executor/mod.rs
@@ -1252,7 +1252,7 @@ impl<'r, S: 'r> Registry<'r, S> {
     /// Creates a [`ScalarMeta`] type.
     pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
     where
-        T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S> + 'r,
+        T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S>,
         T::Error: IntoFieldError<S>,
         S: ScalarValue,
     {
diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs
index a5ec22cbd..8f140cf78 100644
--- a/juniper/src/executor_tests/introspection/mod.rs
+++ b/juniper/src/executor_tests/introspection/mod.rs
@@ -9,8 +9,7 @@ use crate::{
     graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
-    value::{ParseScalarResult, ParseScalarValue, Value},
-    GraphQLEnum,
+    GraphQLEnum, InputValue, ScalarValue, Value,
 };
 
 #[derive(GraphQLEnum)]
@@ -20,23 +19,20 @@ enum Sample {
     Two,
 }
 
+// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
+#[graphql_scalar(name = "SampleScalar", parse_token(i32))]
 struct Scalar(i32);
 
-#[graphql_scalar(name = "SampleScalar")]
-impl<S: ScalarValue> GraphQLScalar for Scalar {
-    fn resolve(&self) -> Value {
+impl Scalar {
+    fn to_output<S: ScalarValue>(&self) -> Value<S> {
         Value::scalar(self.0)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Scalar, String> {
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
         v.as_int_value()
-            .map(Scalar)
+            .map(Self)
             .ok_or_else(|| format!("Expected `Int`, found: {}", v))
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <i32 as ParseScalarValue<S>>::from_str(value)
-    }
 }
 
 /// A sample interface
diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs
index d05727307..22bb8b496 100644
--- a/juniper/src/executor_tests/variables.rs
+++ b/juniper/src/executor_tests/variables.rs
@@ -5,30 +5,27 @@ use crate::{
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
     validation::RuleError,
-    value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue},
+    value::{DefaultScalarValue, Object},
     GraphQLError::ValidationError,
-    GraphQLInputObject,
+    GraphQLInputObject, InputValue, ScalarValue, Value,
 };
 
+// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
 #[derive(Debug)]
+#[graphql_scalar(parse_token(String))]
 struct TestComplexScalar;
 
-#[graphql_scalar]
-impl<S: ScalarValue> GraphQLScalar for TestComplexScalar {
-    fn resolve(&self) -> Value {
+impl TestComplexScalar {
+    fn to_output<S: ScalarValue>(&self) -> Value<S> {
         graphql_value!("SerializedValue")
     }
 
-    fn from_input_value(v: &InputValue) -> Result<TestComplexScalar, String> {
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
         v.as_string_value()
             .filter(|s| *s == "SerializedValue")
-            .map(|_| TestComplexScalar)
+            .map(|_| Self)
             .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v))
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <String as ParseScalarValue<S>>::from_str(value)
-    }
 }
 
 #[derive(GraphQLInputObject, Debug)]
diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs
index e1c9c1cf5..c9af35fb6 100644
--- a/juniper/src/integrations/bson.rs
+++ b/juniper/src/integrations/bson.rs
@@ -1,66 +1,46 @@
 //! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
 
-use bson::{oid::ObjectId, DateTime as UtcDateTime};
 use chrono::prelude::*;
 
-use crate::{
-    graphql_scalar,
-    parser::{ParseError, ScalarToken, Token},
-    value::ParseScalarResult,
-    Value,
-};
-
-#[graphql_scalar(description = "ObjectId")]
-impl<S> GraphQLScalar for ObjectId
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.to_hex())
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(with = object_id, parse_token(String))]
+type ObjectId = bson::oid::ObjectId;
+
+mod object_id {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &ObjectId) -> Value<S> {
+        Value::scalar(v.to_hex())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<ObjectId, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<ObjectId, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                Self::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e))
+                ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(val) = value {
-            Ok(S::from(val.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
-#[graphql_scalar(description = "UtcDateTime")]
-impl<S> GraphQLScalar for UtcDateTime
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar((*self).to_chrono().to_rfc3339())
+#[graphql_scalar(with = utc_date_time, parse_token(String))]
+type UtcDateTime = bson::DateTime;
+
+mod utc_date_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> {
+        Value::scalar((*v).to_chrono().to_rfc3339())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<UtcDateTime, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
                 s.parse::<DateTime<Utc>>()
                     .map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e))
             })
-            .map(Self::from_chrono)
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(val) = value {
-            Ok(S::from(val.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
+            .map(UtcDateTime::from_chrono)
     }
 }
 
@@ -72,7 +52,7 @@ mod test {
     use crate::{graphql_input_value, FromInputValue, InputValue};
 
     #[test]
-    fn objectid_from_input_value() {
+    fn objectid_from_input() {
         let raw = "53e37d08776f724e42000000";
         let input: InputValue = graphql_input_value!((raw));
 
@@ -83,7 +63,7 @@ mod test {
     }
 
     #[test]
-    fn utcdatetime_from_input_value() {
+    fn utcdatetime_from_input() {
         let raw = "2020-03-23T17:38:32.446+00:00";
         let input: InputValue = graphql_input_value!((raw));
 
diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs
index 55cfd50ed..6c0f8fc2a 100644
--- a/juniper/src/integrations/chrono.rs
+++ b/juniper/src/integrations/chrono.rs
@@ -16,66 +16,48 @@
 
 */
 #![allow(clippy::needless_lifetimes)]
-use chrono::prelude::*;
-
-use crate::{
-    parser::{ParseError, ScalarToken, Token},
-    value::{ParseScalarResult, ParseScalarValue},
-    Value,
-};
-
-#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")]
-impl<S> GraphQLScalar for DateTime<FixedOffset>
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.to_rfc3339())
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(with = date_time_fixed_offset, parse_token(String))]
+type DateTimeFixedOffset = chrono::DateTime<chrono::FixedOffset>;
+
+mod date_time_fixed_offset {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &DateTimeFixedOffset) -> Value<S> {
+        Value::scalar(v.to_rfc3339())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<DateTime<FixedOffset>, String> {
+    pub(super) fn from_input<S: ScalarValue>(
+        v: &InputValue<S>,
+    ) -> Result<DateTimeFixedOffset, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                DateTime::parse_from_rfc3339(s)
+                DateTimeFixedOffset::parse_from_rfc3339(s)
                     .map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(value) = value {
-            Ok(S::from(value.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
-#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")]
-impl<S> GraphQLScalar for DateTime<Utc>
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.to_rfc3339())
+#[graphql_scalar(with = date_time_utc, parse_token(String))]
+type DateTimeUtc = chrono::DateTime<chrono::Utc>;
+
+mod date_time_utc {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &DateTimeUtc) -> Value<S> {
+        Value::scalar(v.to_rfc3339())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<DateTime<Utc>, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTimeUtc, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                s.parse::<DateTime<Utc>>()
+                s.parse::<DateTimeUtc>()
                     .map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(value) = value {
-            Ok(S::from(value.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 // Don't use `Date` as the docs say:
@@ -83,16 +65,17 @@ where
 // inherent lack of precision required for the time zone resolution.
 // For serialization and deserialization uses, it is best to use
 // `NaiveDate` instead."
-#[crate::graphql_scalar(description = "NaiveDate")]
-impl<S> GraphQLScalar for NaiveDate
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.format("%Y-%m-%d").to_string())
+#[graphql_scalar(with = naive_date, parse_token(String))]
+type NaiveDate = chrono::NaiveDate;
+
+mod naive_date {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &NaiveDate) -> Value<S> {
+        Value::scalar(v.format("%Y-%m-%d").to_string())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<NaiveDate, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveDate, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
@@ -100,27 +83,21 @@ where
                     .map_err(|e| format!("Failed to parse `NaiveDate`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(value) = value {
-            Ok(S::from(value.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 #[cfg(feature = "scalar-naivetime")]
-#[crate::graphql_scalar(description = "NaiveTime")]
-impl<S> GraphQLScalar for NaiveTime
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.format("%H:%M:%S").to_string())
+#[graphql_scalar(with = naive_time, parse_token(String))]
+type NaiveTime = chrono::NaiveTime;
+
+#[cfg(feature = "scalar-naivetime")]
+mod naive_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &NaiveTime) -> Value<S> {
+        Value::scalar(v.format("%H:%M:%S").to_string())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<NaiveTime, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveTime, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
@@ -128,28 +105,21 @@ where
                     .map_err(|e| format!("Failed to parse `NaiveTime`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(value) = value {
-            Ok(S::from(value.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond
 // datetimes. Values will be truncated to microsecond resolution.
-#[crate::graphql_scalar(description = "NaiveDateTime")]
-impl<S> GraphQLScalar for NaiveDateTime
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.timestamp() as f64)
+#[graphql_scalar(with = naive_date_time, parse_token(f64))]
+type NaiveDateTime = chrono::NaiveDateTime;
+
+mod naive_date_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &NaiveDateTime) -> Value<S> {
+        Value::scalar(v.timestamp() as f64)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<NaiveDateTime, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveDateTime, String> {
         v.as_float_value()
             .ok_or_else(|| format!("Expected `Float`, found: {}", v))
             .and_then(|f| {
@@ -158,10 +128,6 @@ where
                     .ok_or_else(|| format!("Out-of-range number of seconds: {}", secs))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <f64 as ParseScalarValue<S>>::from_str(value)
-    }
 }
 
 #[cfg(test)]
@@ -180,17 +146,17 @@ mod test {
     }
 
     #[test]
-    fn datetime_fixedoffset_from_input_value() {
+    fn datetime_fixedoffset_from_input() {
         datetime_fixedoffset_test("2014-11-28T21:00:09+09:00");
     }
 
     #[test]
-    fn datetime_fixedoffset_from_input_value_with_z_timezone() {
+    fn datetime_fixedoffset_from_input_with_z_timezone() {
         datetime_fixedoffset_test("2014-11-28T21:00:09Z");
     }
 
     #[test]
-    fn datetime_fixedoffset_from_input_value_with_fractional_seconds() {
+    fn datetime_fixedoffset_from_input_with_fractional_seconds() {
         datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00");
     }
 
@@ -206,22 +172,22 @@ mod test {
     }
 
     #[test]
-    fn datetime_utc_from_input_value() {
+    fn datetime_utc_from_input() {
         datetime_utc_test("2014-11-28T21:00:09+09:00")
     }
 
     #[test]
-    fn datetime_utc_from_input_value_with_z_timezone() {
+    fn datetime_utc_from_input_with_z_timezone() {
         datetime_utc_test("2014-11-28T21:00:09Z")
     }
 
     #[test]
-    fn datetime_utc_from_input_value_with_fractional_seconds() {
+    fn datetime_utc_from_input_with_fractional_seconds() {
         datetime_utc_test("2014-11-28T21:00:09.005+09:00");
     }
 
     #[test]
-    fn naivedate_from_input_value() {
+    fn naivedate_from_input() {
         let input: InputValue = graphql_input_value!("1996-12-19");
         let y = 1996;
         let m = 12;
@@ -239,7 +205,7 @@ mod test {
 
     #[test]
     #[cfg(feature = "scalar-naivetime")]
-    fn naivetime_from_input_value() {
+    fn naivetime_from_input() {
         let input: InputValue = graphql_input_value!("21:12:19");
         let [h, m, s] = [21, 12, 19];
         let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap();
@@ -251,7 +217,7 @@ mod test {
     }
 
     #[test]
-    fn naivedatetime_from_input_value() {
+    fn naivedatetime_from_input() {
         let raw = 1_000_000_000_f64;
         let input: InputValue = graphql_input_value!((raw));
 
diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs
index dd84aa7a8..cf7621392 100644
--- a/juniper/src/integrations/chrono_tz.rs
+++ b/juniper/src/integrations/chrono_tz.rs
@@ -3,25 +3,19 @@
 //! [`Tz`]: chrono_tz::Tz
 //! [1]: http://www.iana.org/time-zones
 
-use chrono_tz::Tz;
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
 
-use crate::{
-    graphql_scalar,
-    parser::{ParseError, ScalarToken, Token},
-    value::ParseScalarResult,
-    Value,
-};
+#[graphql_scalar(with = tz, parse_token(String))]
+type Tz = chrono_tz::Tz;
 
-#[graphql_scalar(name = "Tz", description = "Timezone")]
-impl<S> GraphQLScalar for Tz
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.name().to_owned())
+mod tz {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Tz) -> Value<S> {
+        Value::scalar(v.name().to_owned())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Tz, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Tz, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
@@ -29,32 +23,22 @@ where
                     .map_err(|e| format!("Failed to parse `Tz`: {}", e))
             })
     }
-
-    fn from_str<'a>(val: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = val {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(val)))
-        }
-    }
 }
 
 #[cfg(test)]
 mod test {
-    mod from_input_value {
-        use std::ops::Deref;
-
+    mod from_input {
         use chrono_tz::Tz;
 
-        use crate::{graphql_input_value, FromInputValue, InputValue};
+        use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError};
 
         fn tz_input_test(raw: &'static str, expected: Result<Tz, &str>) {
             let input: InputValue = graphql_input_value!((raw));
             let parsed = FromInputValue::from_input_value(&input);
 
             assert_eq!(
-                parsed.as_ref().map_err(Deref::deref),
-                expected.as_ref().map_err(Deref::deref),
+                parsed.as_ref(),
+                expected.map_err(IntoFieldError::into_field_error).as_ref(),
             );
         }
 
diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs
index 562763330..2b1e874a4 100644
--- a/juniper/src/integrations/time.rs
+++ b/juniper/src/integrations/time.rs
@@ -26,59 +26,45 @@ use time::{
     macros::format_description,
 };
 
-use crate::{
-    graphql_scalar,
-    parser::{ParseError, ScalarToken, Token},
-    value::ParseScalarResult,
-    Value,
-};
-
-pub use time::{
-    Date, OffsetDateTime as DateTime, PrimitiveDateTime as LocalDateTime, Time as LocalTime,
-    UtcOffset,
-};
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
 
 /// Format of a [`Date` scalar][1].
 ///
 /// [1]: https://graphql-scalars.dev/docs/scalars/date
 const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]");
 
+/// Date in the proleptic Gregorian calendar (without time zone).
+///
+/// Represents a description of the date (as used for birthdays, for example).
+/// It cannot represent an instant on the time-line.
+///                   
+/// [`Date` scalar][1] compliant.
+///
+/// See also [`time::Date`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/date
+/// [2]: https://docs.rs/time/*/time/struct.Date.html
 #[graphql_scalar(
-    description = "Date in the proleptic Gregorian calendar (without time \
-                   zone).\
-                   \n\n\
-                   Represents a description of the date (as used for birthdays,
-                   for example). It cannot represent an instant on the \
-                   time-line.\
-                   \n\n\
-                   [`Date` scalar][1] compliant.\
-                   \n\n\
-                   See also [`time::Date`][2] for details.\
-                   \n\n\
-                   [1]: https://graphql-scalars.dev/docs/scalars/date\n\
-                   [2]: https://docs.rs/time/*/time/struct.Date.html",
-    specified_by_url = "https://graphql-scalars.dev/docs/scalars/date"
+    with = date,
+    parse_token(String),
+    specified_by_url = "https://graphql-scalars.dev/docs/scalars/date",
 )]
-impl<S: ScalarValue> GraphQLScalar for Date {
-    fn resolve(&self) -> Value {
+pub type Date = time::Date;
+
+mod date {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> {
         Value::scalar(
-            self.format(DATE_FORMAT)
+            v.format(DATE_FORMAT)
                 .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)),
         )
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Self, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Date, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
-            .and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)))
-    }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = value {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
+            .and_then(|s| Date::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)))
     }
 }
 
@@ -99,137 +85,120 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &[FormatItem<'_>] =
 /// [1]: https://graphql-scalars.dev/docs/scalars/local-time
 const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]");
 
-#[graphql_scalar(
-    description = "Clock time within a given date (without time zone) in \
-                   `HH:mm[:ss[.SSS]]` format.\
-                   \n\n\
-                   All minutes are assumed to have exactly 60 seconds; no \
-                   attempt is made to handle leap seconds (either positive or \
-                   negative).\
-                   \n\n\
-                   [`LocalTime` scalar][1] compliant.\
-                   \n\n\
-                   See also [`time::Time`][2] for details.\
-                   \n\n\
-                   [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\
-                   [2]: https://docs.rs/time/*/time/struct.Time.html",
-    specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time"
-)]
-impl<S: ScalarValue> GraphQLScalar for LocalTime {
-    fn resolve(&self) -> Value {
+/// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]`
+/// format.
+///
+/// All minutes are assumed to have exactly 60 seconds; no attempt is made to
+/// handle leap seconds (either positive or negative).
+///
+/// [`LocalTime` scalar][1] compliant.
+///
+/// See also [`time::Time`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
+/// [2]: https://docs.rs/time/*/time/struct.Time.html
+#[graphql_scalar(with = local_time, parse_token(String))]
+pub type LocalTime = time::Time;
+
+mod local_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &LocalTime) -> Value<S> {
         Value::scalar(
-            if self.millisecond() == 0 {
-                self.format(LOCAL_TIME_FORMAT_NO_MILLIS)
+            if v.millisecond() == 0 {
+                v.format(LOCAL_TIME_FORMAT_NO_MILLIS)
             } else {
-                self.format(LOCAL_TIME_FORMAT)
+                v.format(LOCAL_TIME_FORMAT)
             }
             .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)),
         )
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Self, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalTime, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
                 // First, try to parse the most used format.
                 // At the end, try to parse the full format for the parsing
                 // error to be most informative.
-                Self::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
-                    .or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
-                    .or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT))
+                LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
+                    .or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
+                    .or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT))
                     .map_err(|e| format!("Invalid `LocalTime`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = value {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 /// Format of a [`LocalDateTime`] scalar.
 const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] =
     format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
 
-#[graphql_scalar(
-    description = "Combined date and time (without time zone) in `yyyy-MM-dd \
-                   HH:mm:ss` format.\
-                   \n\n\
-                   See also [`time::PrimitiveDateTime`][2] for details.\
-                   \n\n\
-                   [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html"
-)]
-impl<S: ScalarValue> GraphQLScalar for LocalDateTime {
-    fn resolve(&self) -> Value {
+/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format.
+///
+/// See also [`time::PrimitiveDateTime`][2] for details.
+///
+/// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html
+#[graphql_scalar(with = local_date_time, parse_token(String))]
+pub type LocalDateTime = time::PrimitiveDateTime;
+
+mod local_date_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> {
         Value::scalar(
-            self.format(LOCAL_DATE_TIME_FORMAT)
+            v.format(LOCAL_DATE_TIME_FORMAT)
                 .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)),
         )
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Self, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalDateTime, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                Self::parse(s, LOCAL_DATE_TIME_FORMAT)
+                LocalDateTime::parse(s, LOCAL_DATE_TIME_FORMAT)
                     .map_err(|e| format!("Invalid `LocalDateTime`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = value {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
+/// Combined date and time (with time zone) in [RFC 3339][0] format.
+///
+/// Represents a description of an exact instant on the time-line (such as the
+/// instant that a user account was created).
+///
+/// [`DateTime` scalar][1] compliant.
+///
+/// See also [`time::OffsetDateTime`][2] for details.
+///
+/// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
+/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
+/// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html
 #[graphql_scalar(
-    description = "Combined date and time (with time zone) in [RFC 3339][0] \
-                   format.\
-                   \n\n\
-                   Represents a description of an exact instant on the \
-                   time-line (such as the instant that a user account was \
-                   created).\
-                   \n\n\
-                   [`DateTime` scalar][1] compliant.\
-                   \n\n\
-                   See also [`time::OffsetDateTime`][2] for details.\
-                   \n\n\
-                   [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\
-                   [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\
-                   [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html",
-    specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time"
+    with = date_time,
+    parse_token(String),
+    specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
 )]
-impl<S: ScalarValue> GraphQLScalar for DateTime {
-    fn resolve(&self) -> Value {
+pub type DateTime = time::OffsetDateTime;
+
+mod date_time {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &DateTime) -> Value<S> {
         Value::scalar(
-            self.to_offset(UtcOffset::UTC)
+            v.to_offset(UtcOffset::UTC)
                 .format(&Rfc3339)
                 .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)),
         )
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Self, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTime, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                Self::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e))
+                DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e))
             })
             .map(|dt| dt.to_offset(UtcOffset::UTC))
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = value {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 /// Format of a [`UtcOffset` scalar][1].
@@ -238,42 +207,40 @@ impl<S: ScalarValue> GraphQLScalar for DateTime {
 const UTC_OFFSET_FORMAT: &[FormatItem<'_>] =
     format_description!("[offset_hour sign:mandatory]:[offset_minute]");
 
+/// Offset from UTC in `±hh:mm` format. See [list of database time zones][0].
+///
+/// [`UtcOffset` scalar][1] compliant.
+///
+/// See also [`time::UtcOffset`][2] for details.
+///
+/// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+/// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset
+/// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html
 #[graphql_scalar(
-    description = "Offset from UTC in `±hh:mm` format. See [list of database \
-                   time zones][0].\
-                   \n\n\
-                   [`UtcOffset` scalar][1] compliant.\
-                   \n\n\
-                   See also [`time::UtcOffset`][2] for details.\
-                   \n\n\
-                   [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\
-                   [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\
-                   [2]: https://docs.rs/time/*/time/struct.UtcOffset.html",
-    specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset"
+    with = utc_offset,
+    parse_token(String),
+    specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset",
 )]
-impl<S: ScalarValue> GraphQLScalar for UtcOffset {
-    fn resolve(&self) -> Value {
+pub type UtcOffset = time::UtcOffset;
+
+mod utc_offset {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &UtcOffset) -> Value<S> {
         Value::scalar(
-            self.format(UTC_OFFSET_FORMAT)
+            v.format(UTC_OFFSET_FORMAT)
                 .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)),
         )
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Self, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcOffset, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| {
-                Self::parse(s, UTC_OFFSET_FORMAT).map_err(|e| format!("Invalid `UtcOffset`: {}", e))
+                UtcOffset::parse(s, UTC_OFFSET_FORMAT)
+                    .map_err(|e| format!("Invalid `UtcOffset`: {}", e))
             })
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(s) = value {
-            Ok(S::from(s.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 #[cfg(test)]
@@ -295,7 +262,7 @@ mod date_test {
 
             assert!(
                 parsed.is_ok(),
-                "failed to parse `{}`: {}",
+                "failed to parse `{}`: {:?}",
                 raw,
                 parsed.unwrap_err(),
             );
@@ -364,7 +331,7 @@ mod local_time_test {
 
             assert!(
                 parsed.is_ok(),
-                "failed to parse `{}`: {}",
+                "failed to parse `{}`: {:?}",
                 raw,
                 parsed.unwrap_err(),
             );
@@ -436,7 +403,7 @@ mod local_date_time_test {
 
             assert!(
                 parsed.is_ok(),
-                "failed to parse `{}`: {}",
+                "failed to parse `{}`: {:?}",
                 raw,
                 parsed.unwrap_err(),
             );
@@ -524,7 +491,7 @@ mod date_time_test {
 
             assert!(
                 parsed.is_ok(),
-                "failed to parse `{}`: {}",
+                "failed to parse `{}`: {:?}",
                 raw,
                 parsed.unwrap_err(),
             );
@@ -608,7 +575,7 @@ mod utc_offset_test {
 
             assert!(
                 parsed.is_ok(),
-                "failed to parse `{}`: {}",
+                "failed to parse `{}`: {:?}",
                 raw,
                 parsed.unwrap_err(),
             );
diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs
index 97481bb20..e3764c6bc 100644
--- a/juniper/src/integrations/url.rs
+++ b/juniper/src/integrations/url.rs
@@ -1,30 +1,22 @@
 //! GraphQL support for [url](https://github.com/servo/rust-url) types.
 
-use url::Url;
-
-use crate::{
-    value::{ParseScalarResult, ParseScalarValue},
-    Value,
-};
-
-#[crate::graphql_scalar(description = "Url")]
-impl<S> GraphQLScalar for Url
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.as_str().to_owned())
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(with = url_scalar, parse_token(String))]
+type Url = url::Url;
+
+mod url_scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Url) -> Value<S> {
+        Value::scalar(v.as_str().to_owned())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Url, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Url, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e)))
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        <String as ParseScalarValue<S>>::from_str(value)
-    }
 }
 
 #[cfg(test)]
@@ -34,7 +26,7 @@ mod test {
     use crate::{graphql_input_value, InputValue};
 
     #[test]
-    fn url_from_input_value() {
+    fn url_from_input() {
         let raw = "https://example.net/";
         let input: InputValue = graphql_input_value!((raw));
 
diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs
index ae0c04a87..37f710424 100644
--- a/juniper/src/integrations/uuid.rs
+++ b/juniper/src/integrations/uuid.rs
@@ -2,36 +2,23 @@
 
 #![allow(clippy::needless_lifetimes)]
 
-use uuid::Uuid;
-
-use crate::{
-    parser::{ParseError, ScalarToken, Token},
-    value::ParseScalarResult,
-    Value,
-};
-
-#[crate::graphql_scalar(description = "Uuid")]
-impl<S> GraphQLScalar for Uuid
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.to_string())
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
+
+#[graphql_scalar(with = uuid_scalar, parse_token(String))]
+type Uuid = uuid::Uuid;
+
+mod uuid_scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Uuid) -> Value<S> {
+        Value::scalar(v.to_string())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<Uuid, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Uuid, String> {
         v.as_string_value()
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
             .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e)))
     }
-
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        if let ScalarToken::String(value) = value {
-            Ok(S::from(value.to_owned()))
-        } else {
-            Err(ParseError::UnexpectedToken(Token::Scalar(value)))
-        }
-    }
 }
 
 #[cfg(test)]
@@ -41,7 +28,7 @@ mod test {
     use crate::{graphql_input_value, FromInputValue, InputValue};
 
     #[test]
-    fn uuid_from_input_value() {
+    fn uuid_from_input() {
         let raw = "123e4567-e89b-12d3-a456-426655440000";
         let input: InputValue = graphql_input_value!((raw));
 
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index b976c9347..bd3dadd00 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -115,7 +115,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture};
 // functionality automatically.
 pub use juniper_codegen::{
     graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
-    GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
+    GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLUnion,
 };
 
 #[doc(hidden)]
@@ -168,7 +168,7 @@ pub use crate::{
         subscription::{ExtractTypeFromStream, IntoFieldResult},
         AsDynGraphQLValue,
     },
-    parser::{ParseError, Spanning},
+    parser::{ParseError, ScalarToken, Spanning},
     schema::{
         meta,
         model::{RootNode, SchemaType},
diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs
index 55da456b8..ab5383a4f 100644
--- a/juniper/src/types/containers.rs
+++ b/juniper/src/types/containers.rs
@@ -599,7 +599,7 @@ where
 
 #[cfg(test)]
 mod coercion {
-    use crate::{graphql_input_value, FromInputValue as _, InputValue};
+    use crate::{graphql_input_value, FromInputValue as _, InputValue, IntoFieldError as _};
 
     use super::{FromInputValueArrayError, FromInputValueVecError};
 
@@ -685,13 +685,13 @@ mod coercion {
         assert_eq!(
             <Vec<i32>>::from_input_value(&v),
             Err(FromInputValueVecError::Item(
-                "Expected `Int`, found: null".to_owned(),
+                "Expected `Int`, found: null".into_field_error(),
             )),
         );
         assert_eq!(
             <Option<Vec<i32>>>::from_input_value(&v),
             Err(FromInputValueVecError::Item(
-                "Expected `Int`, found: null".to_owned(),
+                "Expected `Int`, found: null".into_field_error(),
             )),
         );
         assert_eq!(
@@ -795,13 +795,13 @@ mod coercion {
         assert_eq!(
             <[i32; 3]>::from_input_value(&v),
             Err(FromInputValueArrayError::Item(
-                "Expected `Int`, found: null".to_owned(),
+                "Expected `Int`, found: null".into_field_error(),
             )),
         );
         assert_eq!(
             <Option<[i32; 3]>>::from_input_value(&v),
             Err(FromInputValueArrayError::Item(
-                "Expected `Int`, found: null".to_owned(),
+                "Expected `Int`, found: null".into_field_error(),
             )),
         );
         assert_eq!(
diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs
index 72b559398..52a21dc00 100644
--- a/juniper/src/types/scalars.rs
+++ b/juniper/src/types/scalars.rs
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
 use crate::{
     ast::{InputValue, Selection, ToInputValue},
     executor::{ExecutionResult, Executor, Registry},
+    graphql_scalar,
     macros::reflect,
     parser::{LexerError, ParseError, ScalarToken, Token},
     schema::meta::MetaType,
@@ -21,9 +22,25 @@ use crate::{
 /// An ID as defined by the GraphQL specification
 ///
 /// Represented as a string, but can be converted _to_ from an integer as well.
+// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[graphql_scalar(parse_token(String, i32))]
 pub struct ID(String);
 
+impl ID {
+    fn to_output<S: ScalarValue>(&self) -> Value<S> {
+        Value::scalar(self.0.clone())
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+        v.as_string_value()
+            .map(str::to_owned)
+            .or_else(|| v.as_int_value().map(|i| i.to_string()))
+            .map(Self)
+            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+    }
+}
+
 impl From<String> for ID {
     fn from(s: String) -> ID {
         ID(s)
@@ -51,47 +68,23 @@ impl fmt::Display for ID {
     }
 }
 
-#[crate::graphql_scalar(name = "ID")]
-impl<S> GraphQLScalar for ID
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.0.clone())
-    }
-
-    fn from_input_value(v: &InputValue) -> Result<ID, String> {
-        v.as_string_value()
-            .map(str::to_owned)
-            .or_else(|| v.as_int_value().map(|i| i.to_string()))
-            .map(ID)
-            .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
-    }
+#[graphql_scalar(with = impl_string_scalar)]
+type String = std::string::String;
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        match value {
-            ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())),
-            _ => Err(ParseError::UnexpectedToken(Token::Scalar(value))),
-        }
-    }
-}
+mod impl_string_scalar {
+    use super::*;
 
-#[crate::graphql_scalar(name = "String")]
-impl<S> GraphQLScalar for String
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(self.clone())
+    pub(super) fn to_output<S: ScalarValue>(v: &str) -> Value<S> {
+        Value::scalar(v.to_owned())
     }
 
-    fn from_input_value(v: &InputValue) -> Result<String, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<String, String> {
         v.as_string_value()
             .map(str::to_owned)
             .ok_or_else(|| format!("Expected `String`, found: {}", v))
     }
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
+    pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
         if let ScalarToken::String(value) = value {
             let mut ret = String::with_capacity(value.len());
             let mut char_iter = value.chars();
@@ -276,42 +269,44 @@ where
     }
 }
 
-#[crate::graphql_scalar(name = "Boolean")]
-impl<S> GraphQLScalar for bool
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(*self)
+#[graphql_scalar(with = impl_boolean_scalar)]
+type Boolean = bool;
+
+mod impl_boolean_scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Boolean) -> Value<S> {
+        Value::scalar(*v)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<bool, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, String> {
         v.as_scalar_value()
             .and_then(ScalarValue::as_boolean)
             .ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
     }
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
-        // Bools are parsed separately - they shouldn't reach this code path
+    pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+        // `Boolean`s are parsed separately, they shouldn't reach this code path.
         Err(ParseError::UnexpectedToken(Token::Scalar(value)))
     }
 }
 
-#[crate::graphql_scalar(name = "Int")]
-impl<S> GraphQLScalar for i32
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(*self)
+#[graphql_scalar(with = impl_int_scalar)]
+type Int = i32;
+
+mod impl_int_scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Int) -> Value<S> {
+        Value::scalar(*v)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<i32, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Int, String> {
         v.as_int_value()
             .ok_or_else(|| format!("Expected `Int`, found: {}", v))
     }
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
+    pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
         if let ScalarToken::Int(v) = value {
             v.parse()
                 .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
@@ -322,21 +317,22 @@ where
     }
 }
 
-#[crate::graphql_scalar(name = "Float")]
-impl<S> GraphQLScalar for f64
-where
-    S: ScalarValue,
-{
-    fn resolve(&self) -> Value {
-        Value::scalar(*self)
+#[graphql_scalar(with = impl_float_scalar)]
+type Float = f64;
+
+mod impl_float_scalar {
+    use super::*;
+
+    pub(super) fn to_output<S: ScalarValue>(v: &Float) -> Value<S> {
+        Value::scalar(*v)
     }
 
-    fn from_input_value(v: &InputValue) -> Result<f64, String> {
+    pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Float, String> {
         v.as_float_value()
             .ok_or_else(|| format!("Expected `Float`, found: {}", v))
     }
 
-    fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
+    pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
         match value {
             ScalarToken::Int(v) => v
                 .parse()
diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs
index d50172206..1359312f5 100644
--- a/juniper/src/value/scalar.rs
+++ b/juniper/src/value/scalar.rs
@@ -2,10 +2,7 @@ use std::{borrow::Cow, fmt};
 
 use serde::{de::DeserializeOwned, Serialize};
 
-use crate::{
-    parser::{ParseError, ScalarToken},
-    GraphQLScalarValue,
-};
+use crate::parser::{ParseError, ScalarToken};
 
 /// The result of converting a string into a scalar value
 pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result<S, ParseError<'a>>;
@@ -16,6 +13,7 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
     fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>;
 }
 
+// TODO: Revisit this doc, once `GraphQLScalarValue` macro is re-implemented.
 /// A trait marking a type that could be used as internal representation of
 /// scalar values in juniper
 ///
@@ -36,9 +34,9 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
 /// ```rust
 /// # use std::{fmt, convert::TryInto as _};
 /// # use serde::{de, Deserialize, Deserializer, Serialize};
-/// # use juniper::{GraphQLScalarValue, ScalarValue};
+/// # use juniper::ScalarValue;
 /// #
-/// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)]
+/// #[derive(Clone, Debug, PartialEq, Serialize)]
 /// #[serde(untagged)]
 /// enum MyScalarValue {
 ///     Int(i32),
@@ -48,6 +46,148 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
 ///     Boolean(bool),
 /// }
 ///
+/// impl From<i32> for MyScalarValue {
+///     fn from(v: i32) -> Self {
+///         Self::Int(v)
+///     }
+/// }
+///
+/// impl From<MyScalarValue> for Option<i32> {
+///     fn from(v: MyScalarValue) -> Self {
+///         if let MyScalarValue::Int(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl<'a> From<&'a MyScalarValue> for Option<&'a i32> {
+///     fn from(v: &'a MyScalarValue) -> Self {
+///         if let MyScalarValue::Int(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl From<i64> for MyScalarValue {
+///     fn from(v: i64) -> Self {
+///         Self::Long(v)
+///     }
+/// }
+///
+/// impl From<MyScalarValue> for Option<i64> {
+///     fn from(v: MyScalarValue) -> Self {
+///         if let MyScalarValue::Long(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl<'a> From<&'a MyScalarValue> for Option<&'a i64> {
+///     fn from(v: &'a MyScalarValue) -> Self {
+///         if let MyScalarValue::Long(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl From<f64> for MyScalarValue {
+///     fn from(v: f64) -> Self {
+///         Self::Float(v)
+///     }
+/// }
+///
+/// impl From<MyScalarValue> for Option<f64> {
+///     fn from(v: MyScalarValue) -> Self {
+///         if let MyScalarValue::Float(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl<'a> From<&'a MyScalarValue> for Option<&'a f64> {
+///     fn from(v: &'a MyScalarValue) -> Self {
+///         if let MyScalarValue::Float(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl From<String> for MyScalarValue {
+///     fn from(v: String) -> Self {
+///         Self::String(v)
+///     }
+/// }
+///
+/// impl From<MyScalarValue> for Option<String> {
+///     fn from(v: MyScalarValue) -> Self {
+///         if let MyScalarValue::String(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl<'a> From<&'a MyScalarValue> for Option<&'a String> {
+///     fn from(v: &'a MyScalarValue) -> Self {
+///         if let MyScalarValue::String(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl From<bool> for MyScalarValue {
+///     fn from(v: bool) -> Self {
+///         Self::Boolean(v)
+///     }
+/// }
+///
+/// impl From<MyScalarValue> for Option<bool> {
+///     fn from(v: MyScalarValue) -> Self {
+///         if let MyScalarValue::Boolean(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl<'a> From<&'a MyScalarValue> for Option<&'a bool> {
+///     fn from(v: &'a MyScalarValue) -> Self {
+///         if let MyScalarValue::Boolean(v) = v {
+///             Some(v)
+///         } else {
+///             None
+///         }
+///     }
+/// }
+///
+/// impl fmt::Display for MyScalarValue {
+///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+///         match self {
+///             Self::Int(v) => v.fmt(f),
+///             Self::Long(v) => v.fmt(f),
+///             Self::Float(v) => v.fmt(f),
+///             Self::String(v) => v.fmt(f),
+///             Self::Boolean(v) => v.fmt(f),
+///         }
+///     }
+/// }
+///
 /// impl ScalarValue for MyScalarValue {
 ///     fn as_int(&self) -> Option<i32> {
 ///         match self {
@@ -266,7 +406,7 @@ pub trait ScalarValue:
 /// These types closely follow the [GraphQL specification][0].
 ///
 /// [0]: https://spec.graphql.org/June2018
-#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)]
+#[derive(Clone, Debug, PartialEq, Serialize)]
 #[serde(untagged)]
 pub enum DefaultScalarValue {
     /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value.
@@ -293,6 +433,122 @@ pub enum DefaultScalarValue {
     Boolean(bool),
 }
 
+// TODO: Revisit these impls, once `GraphQLScalarValue` macro is re-implemented.
+impl From<i32> for DefaultScalarValue {
+    fn from(v: i32) -> Self {
+        Self::Int(v)
+    }
+}
+
+impl From<DefaultScalarValue> for Option<i32> {
+    fn from(v: DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Int(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a DefaultScalarValue> for Option<&'a i32> {
+    fn from(v: &'a DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Int(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<f64> for DefaultScalarValue {
+    fn from(v: f64) -> Self {
+        Self::Float(v)
+    }
+}
+
+impl From<DefaultScalarValue> for Option<f64> {
+    fn from(v: DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Float(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a DefaultScalarValue> for Option<&'a f64> {
+    fn from(v: &'a DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Float(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<String> for DefaultScalarValue {
+    fn from(v: String) -> Self {
+        Self::String(v)
+    }
+}
+
+impl From<DefaultScalarValue> for Option<String> {
+    fn from(v: DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::String(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a DefaultScalarValue> for Option<&'a String> {
+    fn from(v: &'a DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::String(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<bool> for DefaultScalarValue {
+    fn from(v: bool) -> Self {
+        Self::Boolean(v)
+    }
+}
+
+impl From<DefaultScalarValue> for Option<bool> {
+    fn from(v: DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Boolean(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a> From<&'a DefaultScalarValue> for Option<&'a bool> {
+    fn from(v: &'a DefaultScalarValue) -> Self {
+        if let DefaultScalarValue::Boolean(v) = v {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}
+
+impl fmt::Display for DefaultScalarValue {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Int(v) => v.fmt(f),
+            Self::Float(v) => v.fmt(f),
+            Self::String(v) => v.fmt(f),
+            Self::Boolean(v) => v.fmt(f),
+        }
+    }
+}
+
 impl ScalarValue for DefaultScalarValue {
     fn as_int(&self) -> Option<i32> {
         match self {
diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs
index 5f541eaa3..5781a6233 100644
--- a/juniper_codegen/src/common/parse/mod.rs
+++ b/juniper_codegen/src/common/parse/mod.rs
@@ -159,7 +159,7 @@ impl TypeExt for syn::Type {
                             ty.lifetimes_iter_mut(func)
                         }
                         if let syn::ReturnType::Type(_, ty) = &mut args.output {
-                            (&mut *ty).lifetimes_iter_mut(func)
+                            (*ty).lifetimes_iter_mut(func)
                         }
                     }
                     syn::PathArguments::None => {}
@@ -172,7 +172,7 @@ impl TypeExt for syn::Type {
             | T::Group(syn::TypeGroup { elem, .. })
             | T::Paren(syn::TypeParen { elem, .. })
             | T::Ptr(syn::TypePtr { elem, .. })
-            | T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_iter_mut(func),
+            | T::Slice(syn::TypeSlice { elem, .. }) => (*elem).lifetimes_iter_mut(func),
 
             T::Tuple(syn::TypeTuple { elems, .. }) => {
                 for ty in elems.iter_mut() {
@@ -199,7 +199,7 @@ impl TypeExt for syn::Type {
                 if let Some(lt) = ref_ty.lifetime.as_mut() {
                     func(lt)
                 }
-                (&mut *ref_ty.elem).lifetimes_iter_mut(func)
+                (*ref_ty.elem).lifetimes_iter_mut(func)
             }
 
             T::Path(ty) => iter_path(&mut ty.path, func),
@@ -228,7 +228,7 @@ impl TypeExt for syn::Type {
     fn topmost_ident(&self) -> Option<&syn::Ident> {
         match self.unparenthesized() {
             syn::Type::Path(p) => Some(&p.path),
-            syn::Type::Reference(r) => match (&*r.elem).unparenthesized() {
+            syn::Type::Reference(r) => match (*r.elem).unparenthesized() {
                 syn::Type::Path(p) => Some(&p.path),
                 syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() {
                     syn::TypeParamBound::Trait(b) => Some(&b.path),
diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs
deleted file mode 100644
index 3888d757b..000000000
--- a/juniper_codegen/src/derive_scalar_value.rs
+++ /dev/null
@@ -1,347 +0,0 @@
-use crate::{
-    common::parse::ParseBufferExt as _,
-    result::GraphQLScope,
-    util::{self, span_container::SpanContainer},
-};
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant};
-use url::Url;
-
-#[derive(Debug, Default)]
-struct TransparentAttributes {
-    transparent: Option<bool>,
-    name: Option<String>,
-    description: Option<String>,
-    specified_by_url: Option<Url>,
-    scalar: Option<syn::Type>,
-}
-
-impl syn::parse::Parse for TransparentAttributes {
-    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
-        let mut output = Self {
-            transparent: None,
-            name: None,
-            description: None,
-            specified_by_url: None,
-            scalar: None,
-        };
-
-        while !input.is_empty() {
-            let ident: syn::Ident = input.parse()?;
-            match ident.to_string().as_str() {
-                "name" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::LitStr>()?;
-                    output.name = Some(val.value());
-                }
-                "description" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::LitStr>()?;
-                    output.description = Some(val.value());
-                }
-                "specified_by_url" => {
-                    input.parse::<token::Eq>()?;
-                    let val: syn::LitStr = input.parse::<syn::LitStr>()?;
-                    output.specified_by_url =
-                        Some(val.value().parse().map_err(|e| {
-                            syn::Error::new(val.span(), format!("Invalid URL: {}", e))
-                        })?);
-                }
-                "transparent" => {
-                    output.transparent = Some(true);
-                }
-                "scalar" | "Scalar" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::Type>()?;
-                    output.scalar = Some(val);
-                }
-                _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
-            }
-            input.try_parse::<token::Comma>()?;
-        }
-
-        Ok(output)
-    }
-}
-
-impl TransparentAttributes {
-    fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result<Self> {
-        match util::find_graphql_attr(attrs) {
-            Some(attr) => {
-                let mut parsed: TransparentAttributes = attr.parse_args()?;
-                if parsed.description.is_none() {
-                    parsed.description =
-                        util::get_doc_comment(attrs).map(SpanContainer::into_inner);
-                }
-                Ok(parsed)
-            }
-            None => Ok(Default::default()),
-        }
-    }
-}
-
-pub fn impl_scalar_value(ast: &syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
-    let ident = &ast.ident;
-
-    match ast.data {
-        Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, error),
-        Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, error),
-        Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")),
-    }
-}
-
-fn impl_scalar_struct(
-    ast: &syn::DeriveInput,
-    data: &syn::DataStruct,
-    error: GraphQLScope,
-) -> syn::Result<TokenStream> {
-    let field = match data.fields {
-        syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
-            fields.unnamed.first().unwrap()
-        }
-        _ => {
-            return Err(error.custom_error(
-                data.fields.span(),
-                "requires exact one field, e.g., Test(i32)",
-            ))
-        }
-    };
-    let ident = &ast.ident;
-    let attrs = TransparentAttributes::from_attrs(&ast.attrs)?;
-    let inner_ty = &field.ty;
-    let name = attrs.name.unwrap_or_else(|| ident.to_string());
-
-    let description = attrs.description.map(|val| quote!(.description(#val)));
-    let specified_by_url = attrs.specified_by_url.map(|url| {
-        let url_lit = url.as_str();
-        quote!(.specified_by_url(#url_lit))
-    });
-
-    let scalar = attrs
-        .scalar
-        .as_ref()
-        .map(|s| quote!( #s ))
-        .unwrap_or_else(|| quote!(__S));
-
-    let impl_generics = attrs
-        .scalar
-        .as_ref()
-        .map(|_| quote!())
-        .unwrap_or_else(|| quote!(<__S>));
-
-    let _async = quote!(
-        impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident
-        where
-            Self: Sync,
-            Self::TypeInfo: Sync,
-            Self::Context: Sync,
-            #scalar: ::juniper::ScalarValue + Send + Sync,
-        {
-            fn resolve_async<'a>(
-                &'a self,
-                info: &'a Self::TypeInfo,
-                selection_set: Option<&'a [::juniper::Selection<#scalar>]>,
-                executor: &'a ::juniper::Executor<Self::Context, #scalar>,
-            ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> {
-                use ::juniper::futures::future;
-                let v = ::juniper::GraphQLValue::<#scalar>::resolve(self, info, selection_set, executor);
-                Box::pin(future::ready(v))
-            }
-        }
-    );
-
-    let content = quote!(
-        #_async
-
-        impl#impl_generics ::juniper::GraphQLType<#scalar> for #ident
-        where
-            #scalar: ::juniper::ScalarValue,
-        {
-            fn name(_: &Self::TypeInfo) -> Option<&'static str> {
-                Some(#name)
-            }
-
-            fn meta<'r>(
-                info: &Self::TypeInfo,
-                registry: &mut ::juniper::Registry<'r, #scalar>,
-            ) -> ::juniper::meta::MetaType<'r, #scalar>
-            where
-                #scalar: 'r,
-            {
-                registry.build_scalar_type::<Self>(info)
-                    #description
-                    #specified_by_url
-                    .into_meta()
-            }
-        }
-
-        impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ident
-        where
-            #scalar: ::juniper::ScalarValue,
-        {
-            type Context = ();
-            type TypeInfo = ();
-
-            fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
-                <Self as ::juniper::GraphQLType<#scalar>>::name(info)
-            }
-
-            fn resolve(
-                &self,
-                info: &(),
-                selection: Option<&[::juniper::Selection<#scalar>]>,
-                executor: &::juniper::Executor<Self::Context, #scalar>,
-            ) -> ::juniper::ExecutionResult<#scalar> {
-                ::juniper::GraphQLValue::<#scalar>::resolve(&self.0, info, selection, executor)
-            }
-        }
-
-        impl#impl_generics ::juniper::ToInputValue<#scalar> for #ident
-        where
-            #scalar: ::juniper::ScalarValue,
-        {
-            fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
-                ::juniper::ToInputValue::<#scalar>::to_input_value(&self.0)
-            }
-        }
-
-        impl#impl_generics ::juniper::FromInputValue<#scalar> for #ident
-        where
-            #scalar: ::juniper::ScalarValue,
-        {
-            type Error = <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error;
-
-            fn from_input_value(
-                v: &::juniper::InputValue<#scalar>
-            ) -> Result<#ident, <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error> {
-                let inner: #inner_ty = ::juniper::FromInputValue::<#scalar>::from_input_value(v)?;
-                Ok(#ident(inner))
-            }
-        }
-
-        impl#impl_generics ::juniper::ParseScalarValue<#scalar> for #ident
-        where
-            #scalar: ::juniper::ScalarValue,
-        {
-            fn from_str<'a>(
-                value: ::juniper::parser::ScalarToken<'a>,
-            ) -> ::juniper::ParseScalarResult<'a, #scalar> {
-                <#inner_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(value)
-            }
-        }
-
-        impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ident
-            where #scalar: ::juniper::ScalarValue,
-        { }
-        impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident
-            where #scalar: ::juniper::ScalarValue,
-        { }
-
-        impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident
-            where #scalar: ::juniper::ScalarValue,
-        {
-            const NAME: ::juniper::macros::reflect::Type = #name;
-        }
-
-        impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident
-            where #scalar: ::juniper::ScalarValue,
-        {
-            const NAMES: ::juniper::macros::reflect::Types =
-                &[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
-        }
-
-        impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident
-            where #scalar: ::juniper::ScalarValue,
-        {
-            const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
-        }
-    );
-
-    Ok(content)
-}
-
-fn impl_scalar_enum(
-    ident: &syn::Ident,
-    data: &syn::DataEnum,
-    error: GraphQLScope,
-) -> syn::Result<TokenStream> {
-    let froms = data
-        .variants
-        .iter()
-        .map(|v| derive_from_variant(v, ident, &error))
-        .collect::<Result<Vec<_>, _>>()?;
-
-    let display = derive_display(data.variants.iter(), ident);
-
-    Ok(quote! {
-        #(#froms)*
-
-        #display
-    })
-}
-
-fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
-where
-    I: Iterator<Item = &'a Variant>,
-{
-    let arms = variants.map(|v| {
-        let variant = &v.ident;
-        quote!(#ident::#variant(ref v) => write!(f, "{}", v),)
-    });
-
-    quote! {
-        impl std::fmt::Display for #ident {
-            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-                match *self {
-                    #(#arms)*
-                }
-            }
-        }
-    }
-}
-
-fn derive_from_variant(
-    variant: &Variant,
-    ident: &Ident,
-    error: &GraphQLScope,
-) -> syn::Result<TokenStream> {
-    let ty = match variant.fields {
-        Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,
-
-        _ => {
-            return Err(error.custom_error(
-                variant.fields.span(),
-                "requires exact one field, e.g., Test(i32)",
-            ))
-        }
-    };
-
-    let variant = &variant.ident;
-
-    Ok(quote! {
-        impl ::std::convert::From<#ty> for #ident {
-            fn from(t: #ty) -> Self {
-                #ident::#variant(t)
-            }
-        }
-
-        impl<'a> ::std::convert::From<&'a #ident> for std::option::Option<&'a #ty> {
-            fn from(t: &'a #ident) -> Self {
-                match *t {
-                    #ident::#variant(ref t) => std::option::Option::Some(t),
-                    _ => std::option::Option::None
-                }
-            }
-        }
-
-        impl ::std::convert::From<#ident> for std::option::Option<#ty> {
-            fn from(t: #ident) -> Self {
-                match t {
-                    #ident::#variant(t) => std::option::Option::Some(t),
-                    _ => std::option::Option::None
-                }
-            }
-        }
-    })
-}
diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs
new file mode 100644
index 000000000..1405b236f
--- /dev/null
+++ b/juniper_codegen/src/graphql_scalar/attr.rs
@@ -0,0 +1,154 @@
+//! Code generation for `#[graphql_scalar]` macro.
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, ToTokens};
+use syn::{parse_quote, spanned::Spanned};
+
+use crate::{
+    common::{parse, scalar},
+    graphql_scalar::TypeOrIdent,
+    GraphQLScope,
+};
+
+use super::{Attr, Definition, GraphQLScalarMethods, ParseToken};
+
+const ERR: GraphQLScope = GraphQLScope::ScalarAttr;
+
+/// Expands `#[graphql_scalar]` macro into generated code.
+pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
+    if let Ok(mut ast) = syn::parse2::<syn::ItemType>(body.clone()) {
+        let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs);
+        ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs);
+        return expand_on_type_alias(attrs, ast);
+    } else if let Ok(mut ast) = syn::parse2::<syn::DeriveInput>(body) {
+        let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs);
+        ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs);
+        return expand_on_derive_input(attrs, ast);
+    }
+
+    Err(syn::Error::new(
+        Span::call_site(),
+        "#[graphql_scalar] attribute is applicable to type aliases, structs, \
+         enums and unions only",
+    ))
+}
+
+/// Expands `#[graphql_scalar]` macro placed on a type alias.
+fn expand_on_type_alias(
+    attrs: Vec<syn::Attribute>,
+    ast: syn::ItemType,
+) -> syn::Result<TokenStream> {
+    let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
+
+    let field = match (
+        attr.to_output.as_deref().cloned(),
+        attr.from_input.as_deref().cloned(),
+        attr.parse_token.as_deref().cloned(),
+        attr.with.as_deref().cloned(),
+    ) {
+        (Some(to_output), Some(from_input), Some(parse_token), None) => {
+            GraphQLScalarMethods::Custom {
+                to_output,
+                from_input,
+                parse_token,
+            }
+        }
+        (to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom {
+            to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
+            from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
+            parse_token: parse_token
+                .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
+        },
+        _ => {
+            return Err(ERR.custom_error(
+                ast.span(),
+                "all custom resolvers have to be provided via `with` or \
+                 combination of `to_output_with`, `from_input_with`, \
+                 `parse_token_with` attribute arguments",
+            ));
+        }
+    };
+
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    let def = Definition {
+        ty: TypeOrIdent::Type(ast.ty.clone()),
+        where_clause: attr
+            .where_clause
+            .map_or_else(Vec::new, |cl| cl.into_inner()),
+        generics: ast.generics.clone(),
+        methods: field,
+        name: attr
+            .name
+            .as_deref()
+            .cloned()
+            .unwrap_or_else(|| ast.ident.to_string()),
+        description: attr.description.as_deref().cloned(),
+        specified_by_url: attr.specified_by_url.as_deref().cloned(),
+        scalar,
+    }
+    .to_token_stream();
+
+    Ok(quote! {
+        #ast
+        #def
+    })
+}
+
+// TODO: Support `#[graphql(transparent)]`.
+/// Expands `#[graphql_scalar]` macro placed on a struct/enum/union.
+fn expand_on_derive_input(
+    attrs: Vec<syn::Attribute>,
+    ast: syn::DeriveInput,
+) -> syn::Result<TokenStream> {
+    let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
+
+    let field = match (
+        attr.to_output.as_deref().cloned(),
+        attr.from_input.as_deref().cloned(),
+        attr.parse_token.as_deref().cloned(),
+        attr.with.as_deref().cloned(),
+    ) {
+        (Some(to_output), Some(from_input), Some(parse_token), None) => {
+            GraphQLScalarMethods::Custom {
+                to_output,
+                from_input,
+                parse_token,
+            }
+        }
+        (to_output, from_input, parse_token, module) => {
+            let module = module.unwrap_or_else(|| parse_quote! { Self });
+            GraphQLScalarMethods::Custom {
+                to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
+                from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
+                parse_token: parse_token
+                    .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
+            }
+        }
+    };
+
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    let def = Definition {
+        ty: TypeOrIdent::Ident(ast.ident.clone()),
+        where_clause: attr
+            .where_clause
+            .map_or_else(Vec::new, |cl| cl.into_inner()),
+        generics: ast.generics.clone(),
+        methods: field,
+        name: attr
+            .name
+            .as_deref()
+            .cloned()
+            .unwrap_or_else(|| ast.ident.to_string()),
+        description: attr.description.as_deref().cloned(),
+        specified_by_url: attr.specified_by_url.as_deref().cloned(),
+        scalar,
+    }
+    .to_token_stream();
+
+    Ok(quote! {
+        #ast
+        #def
+    })
+}
diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs
new file mode 100644
index 000000000..08dad1259
--- /dev/null
+++ b/juniper_codegen/src/graphql_scalar/mod.rs
@@ -0,0 +1,888 @@
+//! Code generation for [GraphQL scalar][1].
+//!
+//! [1]: https://spec.graphql.org/October2021#sec-Scalars
+
+use proc_macro2::{Literal, TokenStream};
+use quote::{format_ident, quote, ToTokens, TokenStreamExt};
+use syn::{
+    ext::IdentExt as _,
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned as _,
+    token,
+    visit_mut::VisitMut,
+};
+use url::Url;
+
+use crate::{
+    common::{
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _,
+        },
+        scalar,
+    },
+    util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
+};
+
+pub mod attr;
+
+/// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when
+/// generating code for [GraphQL scalar][1].
+///
+/// [1]: https://spec.graphql.org/October2021#sec-Scalars
+#[derive(Debug, Default)]
+struct Attr {
+    /// Name of this [GraphQL scalar][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    name: Option<SpanContainer<String>>,
+
+    /// Description of this [GraphQL scalar][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    description: Option<SpanContainer<String>>,
+
+    /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    specified_by_url: Option<SpanContainer<Url>>,
+
+    /// Explicitly specified type (or type parameter with its bounds) of
+    /// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with.
+    ///
+    /// If [`None`], then generated code will be generic over any
+    /// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to
+    /// be generic over any [`ScalarValue`] type too. That's why this type
+    /// should be specified only if one of the variants implements
+    /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    scalar: Option<SpanContainer<scalar::AttrValue>>,
+
+    /// Explicitly specified function to be used as
+    /// [`ToInputValue::to_input_value`] implementation.
+    ///
+    /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
+    to_output: Option<SpanContainer<syn::ExprPath>>,
+
+    /// Explicitly specified function to be used as
+    /// [`FromInputValue::from_input_value`] implementation.
+    ///
+    /// [`FromInputValue::from_input_value`]: juniper::FromInputValue::from_input_value
+    from_input: Option<SpanContainer<syn::ExprPath>>,
+
+    /// Explicitly specified resolver to be used as
+    /// [`ParseScalarValue::from_str`] implementation.
+    ///
+    /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
+    parse_token: Option<SpanContainer<ParseToken>>,
+
+    /// Explicitly specified module with all custom resolvers for
+    /// [`Self::to_output`], [`Self::from_input`] and [`Self::parse_token`].
+    with: Option<SpanContainer<syn::ExprPath>>,
+
+    /// Explicit where clause added to [`syn::WhereClause`].
+    where_clause: Option<SpanContainer<Vec<syn::WherePredicate>>>,
+}
+
+impl Parse for Attr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = Self::default();
+        while !input.is_empty() {
+            let ident = input.parse_any_ident()?;
+            match ident.to_string().as_str() {
+                "name" => {
+                    input.parse::<token::Eq>()?;
+                    let name = input.parse::<syn::LitStr>()?;
+                    out.name
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(name.span()),
+                            name.value(),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "desc" | "description" => {
+                    input.parse::<token::Eq>()?;
+                    let desc = input.parse::<syn::LitStr>()?;
+                    out.description
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(desc.span()),
+                            desc.value(),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "specified_by_url" => {
+                    input.parse::<token::Eq>()?;
+                    let lit = input.parse::<syn::LitStr>()?;
+                    let url = lit.value().parse::<Url>().map_err(|err| {
+                        syn::Error::new(lit.span(), format!("Invalid URL: {}", err))
+                    })?;
+                    out.specified_by_url
+                        .replace(SpanContainer::new(ident.span(), Some(lit.span()), url))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "scalar" | "Scalar" | "ScalarValue" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<scalar::AttrValue>()?;
+                    out.scalar
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "to_output_with" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::ExprPath>()?;
+                    out.to_output
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "from_input_with" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::ExprPath>()?;
+                    out.from_input
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "parse_token_with" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::ExprPath>()?;
+                    out.parse_token
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(scl.span()),
+                            ParseToken::Custom(scl),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "parse_token" => {
+                    let types;
+                    let _ = syn::parenthesized!(types in input);
+                    let parsed_types =
+                        types.parse_terminated::<_, token::Comma>(syn::Type::parse)?;
+
+                    if parsed_types.is_empty() {
+                        return Err(syn::Error::new(ident.span(), "expected at least 1 type."));
+                    }
+
+                    out.parse_token
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(parsed_types.span()),
+                            ParseToken::Delegated(parsed_types.into_iter().collect()),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "with" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::ExprPath>()?;
+                    out.with
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "where" => {
+                    let (span, parsed_predicates) = if input.parse::<token::Eq>().is_ok() {
+                        let pred = input.parse::<syn::WherePredicate>()?;
+                        (pred.span(), vec![pred])
+                    } else {
+                        let predicates;
+                        let _ = syn::parenthesized!(predicates in input);
+                        let parsed_predicates = predicates
+                            .parse_terminated::<_, token::Comma>(syn::WherePredicate::parse)?;
+
+                        if parsed_predicates.is_empty() {
+                            return Err(syn::Error::new(
+                                ident.span(),
+                                "expected at least 1 where predicate.",
+                            ));
+                        }
+
+                        (
+                            parsed_predicates.span(),
+                            parsed_predicates.into_iter().collect(),
+                        )
+                    };
+
+                    out.where_clause
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(span),
+                            parsed_predicates,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+        Ok(out)
+    }
+}
+
+impl Attr {
+    /// Tries to merge two [`Attr`]s into a single one, reporting about
+    /// duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        Ok(Self {
+            name: try_merge_opt!(name: self, another),
+            description: try_merge_opt!(description: self, another),
+            specified_by_url: try_merge_opt!(specified_by_url: self, another),
+            scalar: try_merge_opt!(scalar: self, another),
+            to_output: try_merge_opt!(to_output: self, another),
+            from_input: try_merge_opt!(from_input: self, another),
+            parse_token: try_merge_opt!(parse_token: self, another),
+            with: try_merge_opt!(with: self, another),
+            where_clause: try_merge_opt!(where_clause: self, another),
+        })
+    }
+
+    /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a trait definition.
+    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let mut attr = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if attr.description.is_none() {
+            attr.description = get_doc_comment(attrs);
+        }
+
+        Ok(attr)
+    }
+}
+
+/// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of
+/// `#[derive(GraphQLScalar)]`.
+#[derive(Clone)]
+enum TypeOrIdent {
+    /// [`syn::Type`].
+    Type(Box<syn::Type>),
+
+    /// [`syn::Ident`].
+    Ident(syn::Ident),
+}
+
+/// Definition of [GraphQL scalar][1] for code generation.
+///
+/// [1]: https://spec.graphql.org/October2021#sec-Scalars
+struct Definition {
+    /// Name of this [GraphQL scalar][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    name: String,
+
+    /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    ty: TypeOrIdent,
+
+    /// Additional [`Self::generics`] [`syn::WhereClause`] predicates.
+    where_clause: Vec<syn::WherePredicate>,
+
+    /// Generics of the Rust type that this [GraphQL scalar][1] is implemented
+    /// for.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    generics: syn::Generics,
+
+    /// [`GraphQLScalarMethods`] representing [GraphQL scalar][1].
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    methods: GraphQLScalarMethods,
+
+    /// Description of this [GraphQL scalar][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    description: Option<String>,
+
+    /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    specified_by_url: Option<Url>,
+
+    /// [`ScalarValue`] parametrization to generate [`GraphQLType`]
+    /// implementation with for this [GraphQL scalar][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    scalar: scalar::Type,
+}
+
+impl ToTokens for Definition {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.impl_output_and_input_type_tokens().to_tokens(into);
+        self.impl_type_tokens().to_tokens(into);
+        self.impl_value_tokens().to_tokens(into);
+        self.impl_value_async_tokens().to_tokens(into);
+        self.impl_to_input_value_tokens().to_tokens(into);
+        self.impl_from_input_value_tokens().to_tokens(into);
+        self.impl_parse_scalar_value_tokens().to_tokens(into);
+        self.impl_reflection_traits_tokens().to_tokens(into);
+    }
+}
+
+impl Definition {
+    /// Returns generated code implementing [`marker::IsInputType`] and
+    /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1].
+    ///
+    /// [`marker::IsInputType`]: juniper::marker::IsInputType
+    /// [`marker::IsOutputType`]: juniper::marker::IsOutputType
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    #[must_use]
+    fn impl_output_and_input_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ty
+                #where_clause { }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty
+                #where_clause { }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLType`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let name = &self.name;
+
+        let description = self
+            .description
+            .as_ref()
+            .map(|val| quote! { .description(#val) });
+        let specified_by_url = self.specified_by_url.as_ref().map(|url| {
+            let url_lit = url.as_str();
+            quote! { .specified_by_url(#url_lit) }
+        });
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty
+                #where_clause
+            {
+                fn name(_: &Self::TypeInfo) -> Option<&'static str> {
+                    Some(#name)
+                }
+
+                fn meta<'r>(
+                    info: &Self::TypeInfo,
+                    registry: &mut ::juniper::Registry<'r, #scalar>,
+                ) -> ::juniper::meta::MetaType<'r, #scalar>
+                where
+                    #scalar: 'r,
+                {
+                    registry.build_scalar_type::<Self>(info)
+                        #description
+                        #specified_by_url
+                        .into_meta()
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let resolve = self.methods.expand_resolve(scalar);
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ty
+                #where_clause
+            {
+                type Context = ();
+                type TypeInfo = ();
+
+                fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
+                    <Self as ::juniper::GraphQLType<#scalar>>::name(info)
+                }
+
+                fn resolve(
+                    &self,
+                    info: &(),
+                    selection: Option<&[::juniper::Selection<#scalar>]>,
+                    executor: &::juniper::Executor<Self::Context, #scalar>,
+                ) -> ::juniper::ExecutionResult<#scalar> {
+                    #resolve
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_value_async_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (ty, generics) = self.impl_self_and_generics(true);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty
+                #where_clause
+            {
+                fn resolve_async<'b>(
+                    &'b self,
+                    info: &'b Self::TypeInfo,
+                    selection_set: Option<&'b [::juniper::Selection<#scalar>]>,
+                    executor: &'b ::juniper::Executor<Self::Context, #scalar>,
+                ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
+                    use ::juniper::futures::future;
+                    let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor);
+                    Box::pin(future::ready(v))
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`InputValue`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`InputValue`]: juniper::InputValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_to_input_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let to_input_value = self.methods.expand_to_input_value(scalar);
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::ToInputValue<#scalar> for #ty
+                #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
+                    #to_input_value
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`FromInputValue`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`FromInputValue`]: juniper::FromInputValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_from_input_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let from_input_value = self.methods.expand_from_input_value(scalar);
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty
+                #where_clause
+            {
+                type Error = ::juniper::executor::FieldError<#scalar>;
+
+                fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result<Self, Self::Error> {
+                    #from_input_value
+                        .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error)
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`ParseScalarValue`] trait for this
+    /// [GraphQL scalar][1].
+    ///
+    /// [`ParseScalarValue`]: juniper::ParseScalarValue
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_parse_scalar_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let from_str = self.methods.expand_parse_scalar_value(scalar);
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty
+                #where_clause
+           {
+               fn from_str(
+                    token: ::juniper::parser::ScalarToken<'_>,
+               ) -> ::juniper::ParseScalarResult<'_, #scalar> {
+                    #from_str
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
+    /// [`WrappedType`] traits for this [GraphQL scalar][1].
+    ///
+    /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes
+    /// [`BaseType`]: juniper::macros::reflection::BaseType
+    /// [`WrappedType`]: juniper::macros::reflection::WrappedType
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_reflection_traits_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let name = &self.name;
+
+        let (ty, generics) = self.impl_self_and_generics(false);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty
+                #where_clause
+            {
+                const NAME: ::juniper::macros::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::macros::reflect::Types =
+                    &[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty
+                #where_clause
+            {
+                const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
+            }
+        }
+    }
+
+    /// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`]
+    /// trait (and similar) implementation.
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
+    ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
+    /// [`GraphQLType`]: juniper::GraphQLType
+    #[must_use]
+    fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) {
+        let mut generics = self.generics.clone();
+
+        let ty = match &self.ty {
+            TypeOrIdent::Type(ty) => ty.into_token_stream(),
+            TypeOrIdent::Ident(ident) => {
+                let (_, ty_gen, _) = self.generics.split_for_impl();
+                quote! { #ident#ty_gen }
+            }
+        };
+
+        if !self.where_clause.is_empty() {
+            generics
+                .make_where_clause()
+                .predicates
+                .extend(self.where_clause.clone())
+        }
+
+        let scalar = &self.scalar;
+        if scalar.is_implicit_generic() {
+            generics.params.push(parse_quote! { #scalar });
+        }
+        if scalar.is_generic() {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
+        }
+        if let Some(bound) = scalar.bounds() {
+            generics.make_where_clause().predicates.push(bound);
+        }
+
+        if for_async {
+            let self_ty = if self.generics.lifetimes().next().is_some() {
+                let mut generics = self.generics.clone();
+                ModifyLifetimes.visit_generics_mut(&mut generics);
+
+                let lifetimes = generics.lifetimes().map(|lt| &lt.lifetime);
+                let ty = match self.ty.clone() {
+                    TypeOrIdent::Type(mut ty) => {
+                        ModifyLifetimes.visit_type_mut(&mut ty);
+                        ty.into_token_stream()
+                    }
+                    TypeOrIdent::Ident(ident) => {
+                        let (_, ty_gens, _) = generics.split_for_impl();
+                        quote! { #ident#ty_gens }
+                    }
+                };
+
+                quote! { for<#( #lifetimes ),*> #ty }
+            } else {
+                quote! { Self }
+            };
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #self_ty: Sync });
+
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
+            }
+        }
+
+        (ty, generics)
+    }
+}
+
+/// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a
+/// lifetime name that is already in scope" error.
+struct ModifyLifetimes;
+
+impl VisitMut for ModifyLifetimes {
+    fn visit_lifetime_mut(&mut self, lf: &mut syn::Lifetime) {
+        lf.ident = format_ident!("__fa__{}", lf.ident.unraw());
+    }
+}
+
+/// Methods representing [GraphQL scalar][1].
+///
+/// [1]: https://spec.graphql.org/October2021#sec-Scalars
+#[allow(dead_code)]
+enum GraphQLScalarMethods {
+    /// [GraphQL scalar][1] represented with only custom resolvers.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    Custom {
+        /// Function provided with `#[graphql(to_output_with = ...)]`.
+        to_output: syn::ExprPath,
+
+        /// Function provided with `#[graphql(from_input_with = ...)]`.
+        from_input: syn::ExprPath,
+
+        /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]`
+        /// or `#[graphql(parse_token(...))]`.
+        parse_token: ParseToken,
+    },
+
+    /// [GraphQL scalar][1] maybe partially represented with custom resolver.
+    /// Other methods are used from [`Field`].
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    Delegated {
+        /// Function provided with `#[graphql(to_output_with = ...)]`.
+        to_output: Option<syn::ExprPath>,
+
+        /// Function provided with `#[graphql(from_input_with = ...)]`.
+        from_input: Option<syn::ExprPath>,
+
+        /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]`
+        /// or `#[graphql(parse_token(...))]`.
+        parse_token: Option<ParseToken>,
+
+        /// [`Field`] to resolve not provided methods.
+        field: Box<Field>,
+    },
+}
+
+impl GraphQLScalarMethods {
+    /// Expands [`GraphQLValue::resolve`] method.
+    ///
+    /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve
+    fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom { to_output, .. }
+            | Self::Delegated {
+                to_output: Some(to_output),
+                ..
+            } => {
+                quote! { Ok(#to_output(self)) }
+            }
+            Self::Delegated { field, .. } => {
+                quote! {
+                    ::juniper::GraphQLValue::<#scalar>::resolve(
+                        &self.#field,
+                        info,
+                        selection,
+                        executor,
+                    )
+                }
+            }
+        }
+    }
+
+    /// Expands [`ToInputValue::to_input_value`] method.
+    ///
+    /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
+    fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom { to_output, .. }
+            | Self::Delegated {
+                to_output: Some(to_output),
+                ..
+            } => {
+                quote! {
+                    let v = #to_output(self);
+                    ::juniper::ToInputValue::to_input_value(&v)
+                }
+            }
+            Self::Delegated { field, .. } => {
+                quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) }
+            }
+        }
+    }
+
+    /// Expands [`FromInputValue::from_input_value`][1] method.
+    ///
+    /// [1]: juniper::FromInputValue::from_input_value
+    fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom { from_input, .. }
+            | Self::Delegated {
+                from_input: Some(from_input),
+                ..
+            } => {
+                quote! { #from_input(input) }
+            }
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let self_constructor = field.closure_constructor();
+                quote! {
+                    <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input)
+                        .map(#self_constructor)
+                }
+            }
+        }
+    }
+
+    /// Expands [`ParseScalarValue::from_str`] method.
+    ///
+    /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
+    fn expand_parse_scalar_value(&self, scalar: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom { parse_token, .. }
+            | Self::Delegated {
+                parse_token: Some(parse_token),
+                ..
+            } => {
+                let parse_token = parse_token.expand_from_str(scalar);
+                quote! { #parse_token }
+            }
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }
+            }
+        }
+    }
+}
+
+/// Representation of [`ParseScalarValue::from_str`] method.
+///
+/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
+#[derive(Clone, Debug)]
+enum ParseToken {
+    /// Custom method.
+    Custom(syn::ExprPath),
+
+    /// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until
+    /// first success.
+    ///
+    /// [`ParseScalarValue`]: juniper::ParseScalarValue
+    Delegated(Vec<syn::Type>),
+}
+
+impl ParseToken {
+    /// Expands [`ParseScalarValue::from_str`] method.
+    ///
+    /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
+    fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom(parse_token) => {
+                quote! { #parse_token(token) }
+            }
+            Self::Delegated(delegated) => delegated
+                .iter()
+                .fold(None, |acc, ty| {
+                    acc.map_or_else(
+                        || Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }),
+                        |prev| {
+                            Some(quote! {
+                                #prev.or_else(|_| {
+                                    <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token)
+                                })
+                            })
+                        }
+                    )
+                })
+                .unwrap_or_default(),
+        }
+    }
+}
+
+/// Struct field to resolve not provided methods.
+#[allow(dead_code)]
+enum Field {
+    /// Named [`Field`].
+    Named(syn::Field),
+
+    /// Unnamed [`Field`].
+    Unnamed(syn::Field),
+}
+
+impl ToTokens for Field {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Named(f) => f.ident.to_tokens(tokens),
+            Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)),
+        }
+    }
+}
+
+impl Field {
+    /// [`syn::Type`] of this [`Field`].
+    fn ty(&self) -> &syn::Type {
+        match self {
+            Self::Named(f) | Self::Unnamed(f) => &f.ty,
+        }
+    }
+
+    /// Closure to construct [GraphQL scalar][1] struct from [`Field`].
+    ///
+    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    fn closure_constructor(&self) -> TokenStream {
+        match self {
+            Field::Named(syn::Field { ident, .. }) => {
+                quote! { |v| Self { #ident: v } }
+            }
+            Field::Unnamed(_) => quote! { Self },
+        }
+    }
+}
diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs
deleted file mode 100644
index e40aa41de..000000000
--- a/juniper_codegen/src/impl_scalar.rs
+++ /dev/null
@@ -1,352 +0,0 @@
-#![allow(clippy::collapsible_if)]
-
-use crate::{
-    result::GraphQLScope,
-    util::{self, span_container::SpanContainer},
-};
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::spanned::Spanned;
-
-#[derive(Debug)]
-struct ScalarCodegenInput {
-    impl_for_type: Option<syn::PathSegment>,
-    custom_data_type: Option<syn::PathSegment>,
-    custom_data_type_is_struct: bool,
-    resolve_body: Option<syn::Block>,
-    from_input_value_arg: Option<syn::Ident>,
-    from_input_value_body: Option<syn::Block>,
-    from_input_value_result: Option<syn::Type>,
-    from_str_arg: Option<syn::Ident>,
-    from_str_body: Option<syn::Block>,
-    from_str_result: Option<syn::Type>,
-}
-
-fn get_first_method_arg(
-    inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
-) -> Option<syn::Ident> {
-    if let Some(syn::FnArg::Typed(pat_type)) = inputs.first() {
-        if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
-            return Some(pat_ident.ident.clone());
-        }
-    }
-
-    None
-}
-
-fn get_method_return_type(output: syn::ReturnType) -> Option<syn::Type> {
-    match output {
-        syn::ReturnType::Type(_, return_type) => Some(*return_type),
-        _ => None,
-    }
-}
-
-// Find the enum type by inspecting the type parameter on the return value
-fn get_enum_type(return_type: &Option<syn::Type>) -> Option<syn::PathSegment> {
-    if let Some(syn::Type::Path(type_path)) = return_type {
-        let path_segment = type_path
-            .path
-            .segments
-            .iter()
-            .find(|ps| matches!(ps.arguments, syn::PathArguments::AngleBracketed(_)));
-
-        if let Some(path_segment) = path_segment {
-            if let syn::PathArguments::AngleBracketed(generic_args) = &path_segment.arguments {
-                let generic_type_arg = generic_args.args.iter().find(|generic_type_arg| {
-                    matches!(generic_type_arg, syn::GenericArgument::Type(_))
-                });
-
-                if let Some(syn::GenericArgument::Type(syn::Type::Path(type_path))) =
-                    generic_type_arg
-                {
-                    if let Some(path_segment) = type_path.path.segments.first() {
-                        return Some(path_segment.clone());
-                    }
-                }
-            }
-        }
-    }
-
-    None
-}
-
-impl syn::parse::Parse for ScalarCodegenInput {
-    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
-        let mut impl_for_type: Option<syn::PathSegment> = None;
-        let mut enum_data_type: Option<syn::PathSegment> = None;
-        let mut resolve_body: Option<syn::Block> = None;
-        let mut from_input_value_arg: Option<syn::Ident> = None;
-        let mut from_input_value_body: Option<syn::Block> = None;
-        let mut from_input_value_result: Option<syn::Type> = None;
-        let mut from_str_arg: Option<syn::Ident> = None;
-        let mut from_str_body: Option<syn::Block> = None;
-        let mut from_str_result: Option<syn::Type> = None;
-
-        let parse_custom_scalar_value_impl: syn::ItemImpl = input.parse()?;
-        // To implement a custom scalar for a struct, it's required to
-        // specify a generic type and a type bound
-        let custom_data_type_is_struct: bool =
-            !parse_custom_scalar_value_impl.generics.params.is_empty();
-
-        let mut self_ty = *parse_custom_scalar_value_impl.self_ty;
-
-        while let syn::Type::Group(type_group) = self_ty {
-            self_ty = *type_group.elem;
-        }
-
-        if let syn::Type::Path(type_path) = self_ty {
-            if let Some(path_segment) = type_path.path.segments.first() {
-                impl_for_type = Some(path_segment.clone());
-            }
-        }
-
-        for impl_item in parse_custom_scalar_value_impl.items {
-            if let syn::ImplItem::Method(method) = impl_item {
-                match method.sig.ident.to_string().as_str() {
-                    "resolve" => {
-                        resolve_body = Some(method.block);
-                    }
-                    "from_input_value" => {
-                        from_input_value_arg = get_first_method_arg(method.sig.inputs);
-                        from_input_value_result = get_method_return_type(method.sig.output);
-                        from_input_value_body = Some(method.block);
-                    }
-                    "from_str" => {
-                        from_str_arg = get_first_method_arg(method.sig.inputs);
-                        from_str_result = get_method_return_type(method.sig.output);
-
-                        if !custom_data_type_is_struct {
-                            enum_data_type = get_enum_type(&from_str_result);
-                        }
-
-                        from_str_body = Some(method.block);
-                    }
-                    _ => (),
-                }
-            }
-        }
-
-        let custom_data_type = if custom_data_type_is_struct {
-            impl_for_type.clone()
-        } else {
-            enum_data_type
-        };
-
-        Ok(ScalarCodegenInput {
-            impl_for_type,
-            custom_data_type,
-            custom_data_type_is_struct,
-            resolve_body,
-            from_input_value_arg,
-            from_input_value_body,
-            from_input_value_result,
-            from_str_arg,
-            from_str_body,
-            from_str_result,
-        })
-    }
-}
-
-/// Generate code for the juniper::graphql_scalar proc macro.
-pub fn build_scalar(
-    attributes: TokenStream,
-    body: TokenStream,
-    error: GraphQLScope,
-) -> syn::Result<TokenStream> {
-    let body_span = body.span();
-
-    let attrs = syn::parse2::<util::FieldAttributes>(attributes)?;
-    let input = syn::parse2::<ScalarCodegenInput>(body)?;
-
-    let impl_for_type = input.impl_for_type.ok_or_else(|| {
-        error.custom_error(
-            body_span,
-            "unable to find target for implementation target for `GraphQLScalar`",
-        )
-    })?;
-    let custom_data_type = input
-        .custom_data_type
-        .ok_or_else(|| error.custom_error(body_span, "unable to find custom scalar data type"))?;
-    let resolve_body = input
-        .resolve_body
-        .ok_or_else(|| error.custom_error(body_span, "unable to find body of `resolve` method"))?;
-    let from_input_value_arg = input.from_input_value_arg.ok_or_else(|| {
-        error.custom_error(
-            body_span,
-            "unable to find argument for `from_input_value` method",
-        )
-    })?;
-    let from_input_value_body = input.from_input_value_body.ok_or_else(|| {
-        error.custom_error(
-            body_span,
-            "unable to find body of `from_input_value` method",
-        )
-    })?;
-    let from_input_value_result = input.from_input_value_result.ok_or_else(|| {
-        error.custom_error(
-            body_span,
-            "unable to find return type of `from_input_value` method",
-        )
-    })?;
-    let from_str_arg = input.from_str_arg.ok_or_else(|| {
-        error.custom_error(body_span, "unable to find argument for `from_str` method")
-    })?;
-    let from_str_body = input
-        .from_str_body
-        .ok_or_else(|| error.custom_error(body_span, "unable to find body of `from_str` method"))?;
-    let from_str_result = input.from_str_result.ok_or_else(|| {
-        error.custom_error(body_span, "unable to find return type of `from_str` method")
-    })?;
-
-    let name = attrs
-        .name
-        .map(SpanContainer::into_inner)
-        .unwrap_or_else(|| impl_for_type.ident.to_string());
-    let description = attrs.description.map(|val| quote!(.description(#val)));
-    let specified_by_url = attrs.specified_by_url.map(|url| {
-        let url_lit = url.as_str();
-        quote!(.specified_by_url(#url_lit))
-    });
-    let async_generic_type = match input.custom_data_type_is_struct {
-        true => quote!(__S),
-        _ => quote!(#custom_data_type),
-    };
-    let async_generic_type_decl = match input.custom_data_type_is_struct {
-        true => quote!(<#async_generic_type>),
-        _ => quote!(),
-    };
-    let generic_type = match input.custom_data_type_is_struct {
-        true => quote!(S),
-        _ => quote!(#custom_data_type),
-    };
-    let generic_type_decl = match input.custom_data_type_is_struct {
-        true => quote!(<#generic_type>),
-        _ => quote!(),
-    };
-    let generic_type_bound = match input.custom_data_type_is_struct {
-        true => quote!(where #generic_type: ::juniper::ScalarValue,),
-        _ => quote!(),
-    };
-
-    let _async = quote!(
-        impl#async_generic_type_decl ::juniper::GraphQLValueAsync<#async_generic_type> for #impl_for_type
-        where
-            Self: Sync,
-            Self::TypeInfo: Sync,
-            Self::Context: Sync,
-            #async_generic_type: ::juniper::ScalarValue + Send + Sync,
-        {
-            fn resolve_async<'a>(
-                &'a self,
-                info: &'a Self::TypeInfo,
-                selection_set: Option<&'a [::juniper::Selection<#async_generic_type>]>,
-                executor: &'a ::juniper::Executor<Self::Context, #async_generic_type>,
-            ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#async_generic_type>> {
-                use ::juniper::futures::future;
-                let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor);
-                Box::pin(future::ready(v))
-            }
-        }
-    );
-
-    let content = quote!(
-        #_async
-
-        impl#generic_type_decl ::juniper::marker::IsInputType<#generic_type> for #impl_for_type
-            #generic_type_bound { }
-
-        impl#generic_type_decl ::juniper::marker::IsOutputType<#generic_type> for #impl_for_type
-            #generic_type_bound { }
-
-        impl#generic_type_decl ::juniper::GraphQLType<#generic_type> for #impl_for_type
-        #generic_type_bound
-        {
-            fn name(_: &Self::TypeInfo) -> Option<&'static str> {
-                Some(#name)
-            }
-
-            fn meta<'r>(
-                info: &Self::TypeInfo,
-                registry: &mut ::juniper::Registry<'r, #generic_type>,
-            ) -> ::juniper::meta::MetaType<'r, #generic_type>
-            where
-                #generic_type: 'r,
-            {
-                registry.build_scalar_type::<Self>(info)
-                    #description
-                    #specified_by_url
-                    .into_meta()
-            }
-        }
-
-        impl#generic_type_decl ::juniper::GraphQLValue<#generic_type> for #impl_for_type
-        #generic_type_bound
-        {
-            type Context = ();
-            type TypeInfo = ();
-
-            fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
-                <Self as ::juniper::GraphQLType<#generic_type>>::name(info)
-            }
-
-            fn resolve(
-                &self,
-                info: &(),
-                selection: Option<&[::juniper::Selection<#generic_type>]>,
-                executor: &::juniper::Executor<Self::Context, #generic_type>,
-            ) -> ::juniper::ExecutionResult<#generic_type> {
-                Ok(#resolve_body)
-            }
-        }
-
-        impl#generic_type_decl ::juniper::ToInputValue<#generic_type> for #impl_for_type
-        #generic_type_bound
-        {
-            fn to_input_value(&self) -> ::juniper::InputValue<#generic_type> {
-                let v = #resolve_body;
-                ::juniper::ToInputValue::to_input_value(&v)
-            }
-        }
-
-        impl#generic_type_decl ::juniper::FromInputValue<#generic_type> for #impl_for_type
-        #generic_type_bound
-        {
-            type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error;
-
-            fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#generic_type>) -> #from_input_value_result {
-                #from_input_value_body
-            }
-        }
-
-        impl#generic_type_decl ::juniper::ParseScalarValue<#generic_type> for #impl_for_type
-        #generic_type_bound
-            {
-                fn from_str<'a>(
-                    #from_str_arg: ::juniper::parser::ScalarToken<'a>,
-                ) -> #from_str_result {
-                #from_str_body
-            }
-        }
-
-        impl#generic_type_decl ::juniper::macros::reflect::BaseType<#generic_type> for #impl_for_type
-            #generic_type_bound
-        {
-            const NAME: ::juniper::macros::reflect::Type = #name;
-        }
-
-        impl#generic_type_decl ::juniper::macros::reflect::BaseSubTypes<#generic_type> for #impl_for_type
-            #generic_type_bound
-        {
-            const NAMES: ::juniper::macros::reflect::Types =
-                &[<Self as ::juniper::macros::reflect::BaseType<#generic_type>>::NAME];
-        }
-
-        impl#generic_type_decl ::juniper::macros::reflect::WrappedType<#generic_type> for #impl_for_type
-            #generic_type_bound
-        {
-            const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
-        }
-    );
-
-    Ok(content)
-}
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index a1015a115..2be163853 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -108,12 +108,11 @@ macro_rules! try_merge_hashset {
 
 mod derive_enum;
 mod derive_input_object;
-mod derive_scalar_value;
-mod impl_scalar;
 
 mod common;
 mod graphql_interface;
 mod graphql_object;
+mod graphql_scalar;
 mod graphql_subscription;
 mod graphql_union;
 
@@ -143,126 +142,116 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
     }
 }
 
-/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
-/// derive.
+/// `#[graphql_scalar]` is interchangeable with `#[derive(`[`GraphQLScalar`]`)]`
+/// macro:
 ///
-/// This can be used for two purposes.
-///
-/// ## Transparent Newtype Wrapper
-///
-/// Sometimes, you want to create a custerm scalar type by wrapping
-/// an existing type. In Rust, this is often called the "newtype" pattern.
-/// Thanks to this custom derive, this becomes really easy:
-///
-/// ```rust
-/// // Deriving GraphQLScalar is all that is required.
-/// #[derive(juniper::GraphQLScalarValue)]
+/// ```rust,ignore
+/// /// Doc comments are used for the GraphQL type description.
+/// #[derive(juniper::GraphQLScalar)]
+/// #[graphql(
+///     // Set a custom GraphQL name.
+///     name = "MyUserId",
+///     // A description can also specified in the attribute.
+///     // This will the doc comment, if one exists.
+///     description = "...",
+///     // A specification URL.
+///     specified_by_url = "https://tools.ietf.org/html/rfc4122",
+///     // Explicit generic scalar.
+///     scalar = S: juniper::ScalarValue,
+///     transparent,
+/// )]
 /// struct UserId(String);
-///
-/// #[derive(juniper::GraphQLObject)]
-/// struct User {
-///   id: UserId,
-/// }
 /// ```
 ///
-/// The type can also be customized.
+/// Is transformed into:
 ///
-/// ```rust
+/// ```rust,ignore
 /// /// Doc comments are used for the GraphQL type description.
-/// #[derive(juniper::GraphQLScalarValue)]
-/// #[graphql(
-///    transparent,
-///    // Set a custom GraphQL name.
-///    name= "MyUserId",
-///    // A description can also specified in the attribute.
-///    // This will the doc comment, if one exists.
-///    description = "...",
-///    // A specification URL.
-///    specified_by_url = "https://tools.ietf.org/html/rfc4122",
+/// #[juniper::graphql_scalar(
+///     // Set a custom GraphQL name.
+///     name = "MyUserId",
+///     // A description can also specified in the attribute.
+///     // This will the doc comment, if one exists.
+///     description = "...",
+///     // A specification URL.
+///     specified_by_url = "https://tools.ietf.org/html/rfc4122",
+///     // Explicit generic scalar.
+///     scalar = S: juniper::ScalarValue,
+///     transparent,
 /// )]
 /// struct UserId(String);
 /// ```
 ///
-/// ### Base ScalarValue Enum
-///
-/// TODO: write documentation.
-///
-#[proc_macro_error]
-#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
-pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
-    let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
-    let gen = derive_scalar_value::impl_scalar_value(&ast, GraphQLScope::DeriveScalar);
-    match gen {
-        Ok(gen) => gen.into(),
-        Err(err) => proc_macro_error::abort!(err),
-    }
-}
-
-/// Expose GraphQL scalars
-///
-/// The GraphQL language defines a number of built-in scalars: strings, numbers, and
-/// booleans. This macro can be used either to define new types of scalars (e.g.
-/// timestamps), or expose other types as one of the built-in scalars (e.g. bigints
-/// as numbers or strings).
+/// In addition to that `#[graphql_scalar]` can be used in case
+/// [`GraphQLScalar`] isn't applicable because type located in other crate and
+/// you don't want to wrap it in a newtype. This is done by placing
+/// `#[graphql_scalar]` on a type alias.
 ///
-/// Since the preferred transport protocol for GraphQL responses is JSON, most
-/// custom scalars will be transferred as strings. You therefore need to ensure that
-/// the client library you are sending data to can parse the custom value into a
-/// datatype appropriate for that platform.
+/// All attributes are mirroring [`GraphQLScalar`] derive macro.
 ///
-/// By default the trait is implemented in terms of the default scalar value
-/// representation provided by juniper. If that does not fit your needs it is
-/// possible to specify a custom representation.
+/// > __NOTE:__ To satisfy [orphan rules] you should provide local
+/// >           [`ScalarValue`] implementation.
 ///
 /// ```rust
-/// // The data type
-/// struct UserID(String);
-///
-/// #[juniper::graphql_scalar(
-///     // You can rename the type for GraphQL by specifying the name here.
-///     name = "MyName",
-///     // You can also specify a description here.
-///     // If present, doc comments will be ignored.
-///     description = "An opaque identifier, represented as a string",
-///    // A specification URL.
-///    specified_by_url = "https://tools.ietf.org/html/rfc4122",
+/// # mod date {
+/// #    pub struct Date;
+/// #
+/// #    impl std::str::FromStr for Date {
+/// #        type Err = String;
+/// #
+/// #        fn from_str(_value: &str) -> Result<Self, Self::Err> {
+/// #            unimplemented!()
+/// #        }
+/// #    }
+/// #
+/// #    impl std::fmt::Display for Date {
+/// #        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+/// #            unimplemented!()
+/// #        }
+/// #    }
+/// # }
+/// #
+/// # use juniper::DefaultScalarValue as CustomScalarValue;
+/// use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
+///
+/// #[graphql_scalar(
+///     with = date_scalar,
+///     parse_token(String),
+///     scalar = CustomScalarValue,
+/// //           ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
 /// )]
-/// impl<S> GraphQLScalar for UserID
-/// where
-///     S: juniper::ScalarValue
-///  {
-///     fn resolve(&self) -> juniper::Value {
-///         juniper::Value::scalar(self.0.to_owned())
-///     }
+/// type Date = date::Date;
+/// //          ^^^^^^^^^^ Type from another crate.
 ///
-///     // NOTE: The error type should implement `IntoFieldError<S>`.
-///     fn from_input_value(value: &juniper::InputValue) -> Result<UserID, String> {
-///         value.as_string_value()
-///             .map(|s| UserID(s.to_owned()))
-///             .ok_or_else(|| format!("Expected `String`, found: {}", value))
+/// mod date_scalar {
+///     use super::*;
+///
+///     // Define how to convert your custom scalar into a primitive type.
+///     pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
+///         Value::scalar(v.to_string())
 ///     }
 ///
-///     fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
-///         <String as juniper::ParseScalarValue<S>>::from_str(value)
+///     // Define how to parse a primitive type into your custom scalar.
+///     // NOTE: The error type should implement `IntoFieldError<S>`.
+///     pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
+///       v.as_string_value()
+///           .ok_or_else(|| format!("Expected `String`, found: {}", v))
+///           .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
 ///     }
 /// }
-///
+/// #
 /// # fn main() { }
 /// ```
 ///
-/// In addition to implementing `GraphQLType` for the type in question,
-/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type
-/// usable as arguments and default values.
+/// [orphan rules]: https://bit.ly/3glAGC2
+/// [`GraphQLScalar`]: juniper::GraphQLScalar
+/// [`ScalarValue`]: juniper::ScalarValue
 #[proc_macro_error]
 #[proc_macro_attribute]
-pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
-    let args = proc_macro2::TokenStream::from(args);
-    let input = proc_macro2::TokenStream::from(input);
-    let gen = impl_scalar::build_scalar(args, input, GraphQLScope::ImplScalar);
-    match gen {
-        Ok(gen) => gen.into(),
-        Err(err) => proc_macro_error::abort!(err),
-    }
+pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream {
+    graphql_scalar::attr::expand(attr.into(), body.into())
+        .unwrap_or_abort()
+        .into()
 }
 
 /// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs
index f15e180ef..93db3e4a4 100644
--- a/juniper_codegen/src/result.rs
+++ b/juniper_codegen/src/result.rs
@@ -13,12 +13,13 @@ pub enum GraphQLScope {
     InterfaceAttr,
     ObjectAttr,
     ObjectDerive,
+    ScalarAttr,
+    #[allow(dead_code)]
+    ScalarDerive,
     UnionAttr,
     UnionDerive,
     DeriveInputObject,
     DeriveEnum,
-    DeriveScalar,
-    ImplScalar,
 }
 
 impl GraphQLScope {
@@ -26,10 +27,10 @@ impl GraphQLScope {
         match self {
             Self::InterfaceAttr => "#sec-Interfaces",
             Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
+            Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
             Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
             Self::DeriveInputObject => "#sec-Input-Objects",
             Self::DeriveEnum => "#sec-Enums",
-            Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
         }
     }
 }
@@ -39,12 +40,11 @@ impl fmt::Display for GraphQLScope {
         let name = match self {
             Self::InterfaceAttr => "interface",
             Self::ObjectAttr | Self::ObjectDerive => "object",
+            Self::ScalarAttr | Self::ScalarDerive => "scalar",
             Self::UnionAttr | Self::UnionDerive => "union",
             Self::DeriveInputObject => "input object",
             Self::DeriveEnum => "enum",
-            Self::DeriveScalar | Self::ImplScalar => "scalar",
         };
-
         write!(f, "GraphQL {}", name)
     }
 }
@@ -119,7 +119,7 @@ impl GraphQLScope {
         duplicates
             .into_iter()
             .for_each(|dup| {
-                (&dup.spanned[1..])
+                dup.spanned[1..]
                     .iter()
                     .for_each(|spanned| {
                         Diagnostic::spanned(
diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs
index 726fce44c..6c2a31bc6 100644
--- a/juniper_codegen/src/util/mod.rs
+++ b/juniper_codegen/src/util/mod.rs
@@ -17,7 +17,6 @@ use syn::{
     spanned::Spanned,
     token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
 };
-use url::Url;
 
 use crate::common::parse::ParseBufferExt as _;
 
@@ -455,7 +454,6 @@ pub enum FieldAttributeParseMode {
 enum FieldAttribute {
     Name(SpanContainer<syn::LitStr>),
     Description(SpanContainer<syn::LitStr>),
-    SpecifiedByUrl(SpanContainer<syn::LitStr>),
     Deprecation(SpanContainer<DeprecationAttr>),
     Skip(SpanContainer<syn::Ident>),
     Arguments(HashMap<String, FieldAttributeArgument>),
@@ -490,15 +488,6 @@ impl Parse for FieldAttribute {
                     lit,
                 )))
             }
-            "specified_by_url" => {
-                input.parse::<token::Eq>()?;
-                let lit = input.parse::<syn::LitStr>()?;
-                Ok(FieldAttribute::SpecifiedByUrl(SpanContainer::new(
-                    ident.span(),
-                    Some(lit.span()),
-                    lit,
-                )))
-            }
             "deprecated" | "deprecation" => {
                 let reason = if input.peek(token::Eq) {
                     input.parse::<token::Eq>()?;
@@ -553,8 +542,6 @@ pub struct FieldAttributes {
     pub name: Option<SpanContainer<String>>,
     pub description: Option<SpanContainer<String>>,
     pub deprecation: Option<SpanContainer<DeprecationAttr>>,
-    /// Only relevant for scalar impl macro.
-    pub specified_by_url: Option<SpanContainer<Url>>,
     /// Only relevant for GraphQLObject derive.
     pub skip: Option<SpanContainer<syn::Ident>>,
     /// Only relevant for object macro.
@@ -577,18 +564,6 @@ impl Parse for FieldAttributes {
                 FieldAttribute::Description(name) => {
                     output.description = Some(name.map(|val| val.value()));
                 }
-                FieldAttribute::SpecifiedByUrl(url) => {
-                    output.specified_by_url = Some(
-                        url.map(|val| Url::parse(&val.value()))
-                            .transpose()
-                            .map_err(|e| {
-                                syn::Error::new(
-                                    e.span_ident(),
-                                    format!("Invalid URL: {}", e.inner()),
-                                )
-                            })?,
-                    );
-                }
                 FieldAttribute::Deprecation(attr) => {
                     output.deprecation = Some(attr);
                 }
diff --git a/juniper_codegen/src/util/span_container.rs b/juniper_codegen/src/util/span_container.rs
index 2040a48ff..370f17a74 100644
--- a/juniper_codegen/src/util/span_container.rs
+++ b/juniper_codegen/src/util/span_container.rs
@@ -58,15 +58,6 @@ impl<T> SpanContainer<T> {
     }
 }
 
-impl<T, E> SpanContainer<Result<T, E>> {
-    pub fn transpose(self) -> Result<SpanContainer<T>, SpanContainer<E>> {
-        match self.val {
-            Ok(v) => Ok(SpanContainer::new(self.ident, self.expr, v)),
-            Err(e) => Err(SpanContainer::new(self.ident, self.expr, e)),
-        }
-    }
-}
-
 impl<T> AsRef<T> for SpanContainer<T> {
     fn as_ref(&self) -> &T {
         &self.val