github/spec-kit · error · PresetError

Invalid preset catalog format from {url}: 'presets' must be

Error message

Invalid preset catalog format from {url}: 'presets' must be a JSON object

What it means

The catalog's top-level `presets` key exists but is not a JSON object — the schema requires presets to be a mapping keyed by preset id, not an array. This is a distinct, later check from the missing-keys case so the author knows exactly what shape to fix.

Source

Thrown at src/specify_cli/presets/__init__.py:4255

            catalog_data: Parsed JSON payload from the catalog source.
            url: Source URL — used in the error message so the user can
                tell which catalog in a multi-catalog stack is malformed.

        Raises:
            PresetError: If the payload's shape is invalid.
        """
        if not isinstance(catalog_data, dict):
            raise PresetError(
                f"Invalid preset catalog format from {url}: "
                "expected a JSON object"
            )
        if (
            "schema_version" not in catalog_data
            or "presets" not in catalog_data
        ):
            raise PresetError(f"Invalid preset catalog format from {url}")
        if not isinstance(catalog_data.get("presets"), dict):
            raise PresetError(
                f"Invalid preset catalog format from {url}: "
                "'presets' must be a JSON object"
            )

    def _load_catalog_config(self, config_path: Path) -> Optional[List[PresetCatalogEntry]]:
        """Load catalog stack configuration from a YAML file.

        Args:
            config_path: Path to preset-catalogs.yml

        Returns:
            Ordered list of PresetCatalogEntry objects, or None if file
            doesn't exist or contains no valid catalog entries.

        Raises:
            PresetValidationError: If any catalog entry has an invalid URL,
                the file cannot be parsed, or a priority value is invalid.
        """

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Convert presets to an object keyed by preset id: {"presets": {"my-preset": {...}}} instead of an array.
  2. If publishing via a script, dump a dict (yaml/json safe_dump of a mapping), not a list.

Example fix

# before
{
  "schema_version": 1,
  "presets": [ {"id": "my-preset", "name": "My Preset"} ]
}

# after
{
  "schema_version": 1,
  "presets": {
    "my-preset": {"name": "My Preset", "description": "..."}
  }
}
Defensive patterns

Strategy: type-guard

Validate before calling

data = json.load(urllib.request.urlopen(url))
assert isinstance(data.get("presets"), dict), "catalog 'presets' must be an object keyed by preset id"

Type guard

def presets_is_mapping(payload: dict) -> bool:
    return isinstance(payload.get("presets"), dict)

Try / catch

try:
    manager.refresh_catalogs()
except PresetError as e:
    if "'presets' must be a JSON object" in str(e):
        # convert the array to an id-keyed object and republish the catalog
        ...

Prevention

When it happens

Trigger: A catalog document with `"presets": [ ... ]` (a JSON array of preset entries), or presets as a string/number. schema_version is present and correct, so only the presets shape is wrong.

Common situations: Authoring a catalog as a list because that feels natural, or converting from a format where presets were array-valued.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/b5720f2d5a625cfc. Report an issue: GitHub.