Skip to main content

SHACL Validation

SHACL (Shapes Constraint Language) is a W3C standard for validating RDF graphs. Fluree supports SHACL shapes to enforce data integrity constraints on your ledger.


Overview​

SHACL shapes define constraints that data must conform to when inserted or updated. When a transaction violates a SHACL constraint, Fluree rejects the transaction with a detailed validation report.

Key concepts:

  • Shapes define constraints on data
  • Target classes specify which entities a shape applies to
  • Property constraints validate specific properties
  • Validation reports explain what went wrong

Defining Shapes​

Shapes are defined by inserting entities of type sh:NodeShape:


{
"@context": {
"sh": "http://www.w3.org/ns/shacl#",
"schema": "http://schema.org/",
"ex": "http://example.org/",
"xsd": "http://www.w3.org/2001/XMLSchema#"
},
"insert": {
"@id": "ex:UserShape",
"@type": "sh:NodeShape",
"sh:targetClass": {"@id": "ex:User"},
"sh:property": [
{
"sh:path": {"@id": "schema:name"},
"sh:minCount": 1,
"sh:maxCount": 1,
"sh:datatype": {"@id": "xsd:string"}
}
]
}
}

Once defined, the shape applies to all entities of the target class.

Property Constraints​

Cardinality​

Control how many values a property can have:

ConstraintDescriptionExample
sh:minCountMinimum number of values"sh:minCount": 1 (required)
sh:maxCountMaximum number of values"sh:maxCount": 1 (single value)

{
"sh:property": [{
"sh:path": {"@id": "schema:email"},
"sh:minCount": 1,
"sh:maxCount": 1
}]
}

Datatype​

Require values to be a specific datatype:


{
"sh:property": [{
"sh:path": {"@id": "schema:age"},
"sh:datatype": {"@id": "xsd:integer"}
}]
}

Common datatypes:

  • xsd:string — Text values
  • xsd:integer — Whole numbers
  • xsd:decimal — Decimal numbers
  • xsd:boolean — True/false
  • xsd:date — Date values
  • xsd:dateTime — Date and time values

Numeric Ranges​

Constrain numeric values to a range:

ConstraintDescription
sh:minInclusiveMinimum value (inclusive)
sh:maxInclusiveMaximum value (inclusive)
sh:minExclusiveMinimum value (exclusive)
sh:maxExclusiveMaximum value (exclusive)

{
"sh:property": [{
"sh:path": {"@id": "schema:age"},
"sh:minInclusive": 0,
"sh:maxInclusive": 150
}]
}

String Constraints​

Validate string properties:

ConstraintDescription
sh:minLengthMinimum string length
sh:maxLengthMaximum string length
sh:patternRegex pattern to match

{
"sh:property": [{
"sh:path": {"@id": "schema:email"},
"sh:pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}]
}

Enumerated Values​

Restrict values to a specific set using sh:in:


{
"sh:property": [{
"sh:path": {"@id": "ex:status"},
"sh:in": ["active", "inactive", "pending"]
}]
}

Class Membership​

Require referenced entities to be of a specific class:


{
"sh:property": [{
"sh:path": {"@id": "schema:author"},
"sh:class": {"@id": "schema:Person"}
}]
}

Node Kind​

Specify whether values should be IRIs or literals:


{
"sh:property": [{
"sh:path": {"@id": "schema:knows"},
"sh:nodeKind": {"@id": "sh:IRI"}
}]
}

Node kinds:

  • sh:IRI — Must be a reference (IRI)
  • sh:Literal — Must be a literal value
  • sh:BlankNode — Must be a blank node

Property Pairs​

Compare values between properties:

sh:equals​

Require two properties to have the same values:


{
"sh:property": [{
"sh:path": {"@id": "ex:displayName"},
"sh:equals": {"@id": "schema:name"}
}]
}

sh:disjoint​

Require two properties to have no common values:


{
"sh:property": [{
"sh:path": {"@id": "ex:primaryEmail"},
"sh:disjoint": {"@id": "ex:secondaryEmail"}
}]
}

Closed Shapes​

Prevent entities from having properties not declared in the shape:


{
"@id": "ex:StrictUserShape",
"@type": "sh:NodeShape",
"sh:targetClass": {"@id": "ex:User"},
"sh:closed": true,
"sh:ignoredProperties": [{"@id": "rdf:type"}],
"sh:property": [
{"sh:path": {"@id": "schema:name"}},
{"sh:path": {"@id": "schema:email"}}
]
}

With sh:closed: true, any property not listed in sh:property or sh:ignoredProperties will cause a validation error.

NOTE: The rdf:type property (or @type in JSON-LD) should typically be in sh:ignoredProperties since it's used to trigger the shape itself.

Logical Constraints​

Combine constraints using logical operators:

sh:not​

Ensure data does NOT conform to a shape:


{
"@id": "ex:NonEmployeeShape",
"@type": "sh:NodeShape",
"sh:targetClass": {"@id": "ex:Person"},
"sh:not": [{
"sh:path": {"@id": "ex:employeeId"},
"sh:minCount": 1
}]
}

This shape ensures persons do NOT have an employee ID.

sh:or​

Require at least one of several constraints to be satisfied:


{
"sh:property": [{
"sh:path": {"@id": "ex:contact"},
"sh:or": [
{"sh:datatype": {"@id": "xsd:string"}},
{"sh:nodeKind": {"@id": "sh:IRI"}}
]
}]
}

sh:and​

Require all constraints to be satisfied (useful for combining):


{
"sh:property": [{
"sh:path": {"@id": "schema:age"},
"sh:and": [
{"sh:minInclusive": 18},
{"sh:maxInclusive": 65}
]
}]
}

Validation Reports​

When a transaction violates SHACL constraints, Fluree returns an error with a validation report:


{
"status": 422,
"error": "shacl/violation",
"report": {
"@type": "sh:ValidationReport",
"sh:conforms": false,
"sh:result": [
{
"@type": "sh:ValidationResult",
"sh:resultSeverity": "sh:Violation",
"sh:focusNode": "ex:john",
"sh:constraintComponent": "sh:minCount",
"sh:sourceShape": "ex:UserShape",
"sh:resultPath": ["schema:name"],
"sh:value": 0,
"f:expectation": 1,
"sh:resultMessage": "count 0 is less than minimum count of 1"
}
]
}
}

Report Fields​

FieldDescription
sh:focusNodeThe entity that failed validation
sh:constraintComponentThe constraint that was violated
sh:sourceShapeThe shape that defined the constraint
sh:resultPathThe property path that failed
sh:valueThe actual value found
f:expectationThe expected value
sh:resultMessageHuman-readable error message

Complete Example​

Here's a comprehensive example with multiple constraints:


{
"@context": {
"sh": "http://www.w3.org/ns/shacl#",
"schema": "http://schema.org/",
"ex": "http://example.org/",
"xsd": "http://www.w3.org/2001/XMLSchema#"
},
"insert": {
"@id": "ex:PersonShape",
"@type": "sh:NodeShape",
"sh:targetClass": {"@id": "ex:Person"},
"sh:property": [
{
"@id": "ex:nameConstraint",
"sh:path": {"@id": "schema:name"},
"sh:minCount": 1,
"sh:maxCount": 1,
"sh:datatype": {"@id": "xsd:string"},
"sh:minLength": 2,
"sh:maxLength": 100
},
{
"@id": "ex:emailConstraint",
"sh:path": {"@id": "schema:email"},
"sh:maxCount": 3,
"sh:pattern": "^[^@]+@[^@]+\\.[^@]+$"
},
{
"@id": "ex:ageConstraint",
"sh:path": {"@id": "schema:age"},
"sh:datatype": {"@id": "xsd:integer"},
"sh:minInclusive": 0,
"sh:maxInclusive": 150
},
{
"@id": "ex:statusConstraint",
"sh:path": {"@id": "ex:status"},
"sh:in": ["active", "inactive", "pending"]
}
]
}
}

This shape ensures:

  • Name is required, single-valued, string between 2-100 characters
  • Email is optional but max 3 values, must match email pattern
  • Age is optional but must be integer 0-150
  • Status must be one of the allowed values

Best Practices​

  1. Give shapes and property constraints IDs — Makes validation reports more readable
  2. Use sh:message — Add custom error messages for better debugging
  3. Start permissive, add constraints gradually — Easier to tighten than loosen
  4. Test constraints before deploying — Ensure they don't block valid data
  5. Consider sh:severity — Use sh:Warning for non-blocking validation