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

`#[scope]` is designed to prefix routes defined inside a module. At actix-web-codegen/src/scope.rs:44-46 the macro parses the item as `syn::ItemMod`; if it isn't a module the parse error is replaced with this clear message.

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 937960ca67)

Solutions

  1. Move the annotated routes into a module and put `#[scope(...)]` on the `mod`.
  2. If you want runtime prefixing for a single route, use `web::scope("/api").route(...)` in your `App` setup instead of the macro.

Example fix

// before
#[scope("/api")]
async fn handler() -> HttpResponse { ... }

// after
#[scope("/api")]
mod api {
    use super::*;
    #[get("/users")]
    pub async fn users() -> HttpResponse { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// Compile-time only. #[scope(...)] must decorate a `mod` item.
// If you need runtime prefixing of a single handler, use:
//   web::scope("/api").route("/x", web::get().to(handler))
// in your App configuration instead.

Prevention

When it happens

Trigger: Placing `#[scope("/api")]` on a function, struct, or `impl` block instead of a `mod`.

Common situations: Expecting `scope` to work like the runtime `Scope::new()` builder on a single handler, rather than as a module-level macro.

Related errors


AI-assisted analysis of actix/actix-web@937960ca67 (2026-08-06). Data as JSON: /data/errors/c42410fb7e27174e.json. Report an issue: GitHub.