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 withNLRS. This enforces a naming convention. -
values.isEditable = trueExcludes: -
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 $familyIdslinks: -
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