windmill-labs/windmill · error

`properties` field should be an object

Error message

`properties` field should be an object

What it means

Raised by from_schema when the schema's `properties` key is missing or is not a JSON object. Windmill's restricted schema format derives per-property validation rules from `properties`, so anything else cannot be compiled into rules and the schema is rejected at parse time.

Source

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

        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"))?
            .as_object()
            .ok_or(anyhow!("`properties` field should be an object"))?;

        let mut rules = vec![];

        for (key, val) in properties {
            rules.push((
                key.clone(),
                SchemaValidationRule::from_value(val)
                    .map_err(|e| anyhow!("Problem making rule for {key}: {e}"))?,
            ));
        }

        Ok(Self { required, rules })
    }
}

impl JsonPrimitiveType {
    fn from_str(typ: &str) -> Result<Self, anyhow::Error> {
        match typ {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Convert `properties` to an object keyed by property name: "properties": {"a": {"type": "string"}}.
  2. If you have an array of {name, schema} entries, transform it into a keyed object before passing the schema.
  3. Use "properties": {} if the object genuinely has no properties.

Example fix

// before
"properties": ["a", "b"]
// after
"properties": {"a": {"type": "string"}, "b": {"type": "number"}}
Defensive patterns

Strategy: type-guard

Validate before calling

fn validate_properties_is_object(schema: &serde_json::Value) -> Result<(), String> {
    match schema.get("properties") {
        Some(p) if p.is_object() => Ok(()),
        Some(_) => Err("`properties` must be an object keyed by property name".into()),
        None => Err("missing `properties`".into()),
    }
}

Type guard

fn properties_is_object(schema: &serde_json::Value) -> bool {
    schema.get("properties").map(|p| p.is_object()).unwrap_or(false)
}

Try / catch

match Schema::from_schema(&schema_str) {
    Ok(s) => s,
    Err(e) if e.to_string().contains("properties` field should be an object") => {
        eprintln!("convert `properties` from a list into a name->schema object");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling Schema::from_schema with "properties": ["a","b"] (an array of names) or "properties": "..." — any non-object value at the `properties` key.

Common situations: Confusing a list of field names with a property map, generators emitting an array of {name, schema} pairs instead of a keyed object, and hand-edited JSON that collapsed the object.

Related errors


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