Skip to content

Chaining filters across Family, FamilySymbol, and FamilyInstance

This filter combines all previously explained techniques into one complete, production-grade pattern.

(
  /* Filter families */
  $familyIds := $[
    type = "Family"
    and $match(name, /^NLRS/)
    and values.isEditable = true
    and values.familyCategory.type = "Model"
  ].id;

  /* Category inclusion */
  $categoryInclusion := ["OST_Doors", "OST_Windows"];

  /* Filter FamilySymbols */
  $familySymbols := $[
    type = "FamilySymbol"
    and values.category.label in $categoryInclusion
    and parent.id in $familyIds
  ].id;

  /* Filter FamilyInstances */
  $[
    type = "FamilyInstance"
    and parent.id in $familySymbols
  ].{
    "id": id,
    "type": type,
    "name": name,
    "parent": parent.id
  }
)

What this filter does (plain language)

“Select all placed door and window instances that belong to editable NLRS families, and only if those families are model families.”

This is a three-level selection chain.


Step 1 — filter Families (definition level)

$familyIds := $[
  type = "Family"
  and $match(name, /^NLRS/)
  and values.isEditable = true
  and values.familyCategory.type = "Model"
].id;

This step answers:

“Which families are allowed to participate in this rule?”

Conditions explained

  • type = "Family" We are working at the family definition level.

  • $match(name, /^NLRS/) Only families whose name starts with NLRS. This enforces a naming convention.

  • values.isEditable = true Excludes:

  • System families

  • Read-only content
  • Loaded content that cannot be modified

  • values.familyCategory.type = "Model" Excludes:

  • Annotation families

  • Non-model content

The result is a list of valid Family IDs.


Step 2 — define category scope (type level)

$CategoryInclusion := ["OST_Doors", "OST_Windows"];

This defines which Revit categories are in scope.

  • Uses API category labels
  • Is reusable
  • Keeps logic readable

This answers:

“Which categories does this rule apply to?”


Step 3 — filter FamilySymbols (family types)

$familySymbols := $[
  type = "FamilySymbol"
  and values.category.label in $CategoryInclusion
  and parent.id in $familyIds
].id;

This step answers:

“Which family types belong to the allowed families and are of the correct category?”

Important details:

  • Category filtering happens here, not on instances
  • parent.id in $familyIds links:

  • FamilySymbol → Family

  • This ensures:

  • Only NLRS families

  • Only editable families
  • Only model families
  • Only doors and windows

The result is a clean list of allowed FamilySymbol IDs.


Step 4 — filter FamilyInstances (placed elements)

$[
  type = "FamilyInstance"
  and parent.id in $familySymbols
]

This answers:

“Which placed elements use those allowed family types?”

Only instances that:

  • Are doors or windows
  • Belong to NLRS families
  • Are placed in the model

Nothing else gets through.


Why this structure matters

This pattern:

  • Matches the actual Revit data hierarchy
  • Prevents duplicate or misleading results
  • Makes every assumption explicit
  • Scales without becoming unreadable

If you try to shortcut this logic:

  • You will either miss elements
  • Or include elements you should not validate
  • Or silently validate the wrong data

What each level is responsible for

Level Responsibility
Family Naming, editability, content ownership
FamilySymbol Category, classification, type data
FamilyInstance Placement, level, host, instance data

Each filter step enforces rules only where the data lives.


Common mistakes this avoids

  • Filtering instances directly by naming convention
  • Filtering categories on instances instead of types
  • Validating system or read-only families
  • Mixing model and annotation content
  • Repeating the same error hundreds of times per instance

Key takeaway

Complex rules are built by chaining simple, correct filters across the Revit hierarchy.

If a rule feels complicated, it’s usually because:

  • One of these steps is missing
  • Or logic is applied at the wrong level