squidfunk/mkdocs-material · error · ValidationError

Expected string, but received: {url}

Error message

Expected string, but received: {url}

What it means

After confirming the mapping item is a dictionary, _mapping_item_from_json requires its 'url' field to be a string so it can construct an mkdocs Link. This ValidationError is thrown when 'url' is missing (None) or of another JSON type (number, object, array, bool).

Source

Thrown at src/plugins/tags/structure/mapping/storage/__init__.py:199

    When loading a mapping, we must always return a link, as the sources of
    pages might not be available because we're building another project.

    Arguments:
        data: Serialized representation.

    Returns:
        The link.
    """
    if not isinstance(data, dict):
        raise ValidationError(
            f"Expected dictionary, but received: {data}"
        )

    # Ensure item has URL
    url = data.get("url")
    if not isinstance(url, str):
        raise ValidationError(
            f"Expected string, but received: {url}"
        )

    # Ensure item has title
    title = data.get("title")
    if not isinstance(title, str):
        raise ValidationError(
            f"Expected string, but received: {title}"
        )

    # Create and return item
    return Link(title, url)

View on GitHub (pinned to e2136532f4)

Solutions

  1. Add or fix the 'url' field inside each mapping's item so it is a plain string URL path, e.g. "url": "guide/setup/"
  2. Regenerate the mapping file by rebuilding the source project with MappingStorage.save()
  3. Search the JSON for items missing "url" (e.g. with jq: .mappings[] | select(.item.url | type != "string"))
  4. If a script writes the file, make it serialize item.url as str(item.url)

Example fix

// before
{"item": {"title": "Setup"}, "tags": ["setup"]}
// after
{"item": {"url": "setup/", "title": "Setup"}, "tags": ["setup"]}
Defensive patterns

Strategy: validation

Validate before calling

import json
with open(path) as f:
    data = json.load(f)
for m in data.get("mappings", []):
    url = (m.get("item") or {}).get("url")
    if not isinstance(url, str):
        raise ValueError(f"entry {m!r}: item.url must be a string")

Type guard

def item_has_url(entry: dict) -> bool:
    item = entry.get("item")
    return isinstance(item, dict) and isinstance(item.get("url"), str)

Try / catch

from mkdocs.config.base import ValidationError
try:
    mappings = list(storage.load(path))
except ValidationError as err:
    log.error("Item missing/invalid url in %s: %s", path, err)
    mappings = []

Prevention

When it happens

Trigger: MappingStorage.load(path) reads a mapping entry where data['item']['url'] is absent, null, or e.g. a nested object/array instead of a string like "guide/setup/".

Common situations: Hand-edited mapping files where the url key was renamed or deleted; a custom exporter that emitted url as a list of path segments; partially written JSON from a crashed build.

Related errors


AI-assisted analysis of squidfunk/mkdocs-material@e2136532f4 (2026-08-29). Data as JSON: /api/errors/796946116d2f827f. Report an issue: GitHub.