actix/actix-web · error · syn::Error
#[scope] macro must be attached to a module
Error message
#[scope] macro must be attached to a module
What it means
This compile-time error fires when the `#[scope("...")]` macro is attached to an item that is not a Rust module (`mod`). The macro attempts `syn::parse::<syn::ItemMod>(input)` at line 44 and if the annotated item is a function, struct, impl block, or anything else, parsing fails with this message.
Solutions
- Move the routes into a module and apply `#[scope]` to the module: `#[scope("/api")] mod api { ... }`
- If you need to scope a single handler, use the runtime `web::scope("/api").route(...)` API instead
- Ensure the annotated item is declared with `mod`, not `fn`, `struct`, etc.
Example fix
// before
#[scope("/api")]
async fn handler() -> impl Responder { ... }
// after
#[scope("/api")]
mod api {
use actix_web::*;
#[get("/users")]
pub async fn users() -> impl Responder {
HttpResponse::Ok()
}
} Defensive patterns
Strategy: type-guard
Type guard
// Compile-time error. #[scope(...)] must be on a module:
// #[scope("/api")]
// mod api { ... } — correct
//
// #[scope("/api")]
// fn handler() { ... } — incorrect (must be a module) Prevention
- Only attach #[scope(...)] to mod declarations
- For scoping individual handlers at runtime, use App::new().service(web::scope("/api").route(...))
- Review the actix-web scope module documentation for correct usage patterns
When it happens
Trigger: Attaching `#[scope("/api")]` to a function, struct, enum, or impl block instead of a `mod` block. For example, `#[scope("/api")] async fn handler() { ... }` will fail because `scope` expects a module.
Common situations: Developers who confuse `#[scope]` (module-level macro) with `web::scope()` (runtime API), or who try to scope a single handler function instead of a group of routes in a module.
Related errors
- argument to scope macro is not a string literal, expected…
- missing arguments for scope macro, expected…
- scopes should not have trailing slashes; see…
- invalid service definition, expected #
- Multiple paths specified! There should be only one.
AI-assisted analysis of actix/actix-web@4d435abc28 (2026-08-09).
Data as JSON: /api/errors/c42410fb7e27174e.
Report an issue: GitHub.
Appendix: source
Thrown at actix-web-codegen/src/scope.rs:45
err.span(),
"argument to scope macro is not a string literal, expected: #[scope(\"/prefix\")]",
)
})?;
let scope_prefix_value = scope_prefix.value();
if scope_prefix_value.ends_with('/') {
// trailing slashes cause non-obvious problems
// it's better to point them out to developers rather than
return Err(syn::Error::new(
scope_prefix.span(),
"scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes",
));
}
let mut module = syn::parse::<syn::ItemMod>(input).map_err(|err| {
syn::Error::new(err.span(), "#[scope] macro must be attached to a module")
})?;
// modify any routing macros (method or route[s]) attached to
// functions by prefixing them with this scope macro's argument
if let Some((_, items)) = &mut module.content {
for item in items {
if let syn::Item::Fn(fun) = item {
fun.attrs = fun
.attrs
.iter()
.map(|attr| modify_attribute_with_scope(attr, &scope_prefix_value))
.collect();
}
}
}
Ok(module.to_token_stream().into())
}View on GitHub (pinned to 4d435abc28)