windmill-labs/windmill · error

No draft version supplied

Error message

No draft version supplied

What it means

from_schema requires the `$schema` keyword declaring the draft version; a schema without it is rejected rather than assumed. Windmill deliberately refuses to guess the draft so validation semantics are unambiguous.

Source

Thrown at backend/windmill-common/src/schema.rs:521

                for rule in rules {
                    rule.apply_rule(key, &parsed_val, self.required.contains(key))?;
                }
            }
        }

        Ok(())
    }

    pub fn from_schema(schema: &str) -> Result<Self, Error> {
        let schema: Value = serde_json::from_str(schema)?;

        if let Some(draft_version) = schema.get("$schema") {
            match draft_version.as_str() {
                Some("https://json-schema.org/draft/2020-12/schema") => (),
                _ => return Err(anyhow!("Supplied schema draft version is unsuported").into()),
            }
        } else {
            return Err(anyhow!("No draft version supplied").into());
        }

        let required: Vec<String> = schema
            .get("required")
            .ok_or(anyhow!("Missing `required` field on schema"))?
            .as_array()
            .ok_or(anyhow!("`required` field should be an array of strings"))?
            .into_iter()
            .map(|v| {
                v.as_str()
                    .map(|s| s.to_string())
                    .ok_or(anyhow!("required field key is not a string"))
            })
            .collect::<Result<Vec<String>, anyhow::Error>>()?;

        let properties = schema
            .get("properties")
            .ok_or(anyhow!("Missing `properties` field on schema"))?

View on GitHub (pinned to e474e8803c)

Solutions

  1. Add "$schema": "https://json-schema.org/draft/2020-12/schema" as a top-level key of the schema.
  2. If the schema is generated in code, ensure the generator always injects the `$schema` field before serialization.

Example fix

// before
{"type": "object", "properties": {...}, "required": [...]}
// after
{"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": {...}, "required": [...]}
Defensive patterns

Strategy: validation

Validate before calling

fn require_draft_field(schema: &serde_json::Value) -> Result<(), String> {
    if schema.get("$schema").is_none() {
        Err("add top-level $schema: https://json-schema.org/draft/2020-12/schema".into())
    } else { Ok(()) }
}

Type guard

fn has_draft(schema: &serde_json::Value) -> bool {
    schema.get("$schema").is_some()
}

Try / catch

match Schema::from_schema(&schema_str) {
    Ok(s) => s,
    Err(e) if e.to_string().contains("No draft version supplied") => {
        eprintln!("inject $schema into the schema before calling from_schema");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling Schema::from_schema with a JSON schema string that has no top-level `$schema` key — common for minimal schemas like {"type":"object","properties":{...},"required":[...]}.

Common situations: Hand-written minimal schemas, schemas copied from tools that omit `$schema`, and dynamically built schemas serialized from objects without adding the keyword.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/522b38695655ba52. Report an issue: GitHub.