Why your Revit family library bloats — and how to audit it
A typical AEC practice carries 1.5 GB of dormant Revit families. Three audit passes reclaim 60 % of the bloat without losing anything teams actually use.
A BIM coordinator at a mid-size structural engineering firm opened their standard project template one morning and noticed something that had been nagging them for months: the file sat at 847 MB before a single wall had been drawn. Synchronise-with-Central took eleven minutes. Opening the model on a site laptop — the one the PM used for client reviews — took long enough that people stopped bothering. Nobody had authorised the template to grow that large. It had simply accumulated, family by family, year by year, project by project, until the baseline for every new job was already sluggish.
That scenario plays out in practices of every size, discipline, and geography. The family library — those .rfa files that define every loadable element from a door handle to a diesel generator — is the single most controllable contributor to both project file weight and library footprint on the network. Yet it is also the least audited asset most BIM teams maintain. Schedules are reviewed. Clash reports are reviewed. IFC exports get checked before handover. The family library sits on a shared drive, growing by accretion, touched only when someone complains loudly enough.
The thesis of this article is operational, not theoretical: a typical AEC practice carries approximately 1.5 GB of dormant or redundant Revit families on its network. In our experience reviewing library structures across multiple project types, roughly 5 % of family files account for close to 28 % of total disk footprint — and a substantial proportion of those heavy files are duplicates, superseded versions, or families that were loaded into a project once and never placed. A systematic three-pass audit — size outliers, duplicate detection, stale-usage review — can reclaim around 60 % of that footprint without touching a single placed element.
This article explains why the bloat occurs, what the common patterns look like in practice, and how to conduct the audit in enough technical depth that a BIM manager with Revit API access can execute it without purchasing additional tooling. Code snippets are provided in C# (Revit API), PowerShell (filesystem pass), and Python (pyRevit-style) where each is the natural fit.
Before we start: "family library" here means the firm-level content library on the network or cloud drive — the canonical source from which project teams load families. Project-level bloat (families loaded into a live RVT that have never been placed) is a related but separate problem addressed briefly in the anti-patterns section.
What's Actually Happening: The Mechanics of Family Bloat
To audit a library intelligently you need to understand why individual .rfa files are large and why libraries accumulate so many of them. There are five distinct mechanisms, and they compound.
1. Geometry Complexity and the Spline Tax
Revit stores family geometry as a parametric solid model in its own internal format. Simple extrusions and sweeps of rectilinear profiles are compact. The moment geometry involves splines — swept blends, lofted solids, free-form surfaces built with the Conceptual Design Environment — the storage cost rises sharply.
The community benchmark research published by Niko G. on Medium tested geometry types in isolation and found that 100 spline control points add approximately 28 KB to a family file, while 100 straight-line segments add under 4 KB. A single complex lofted solid built from high-resolution spline profiles can contribute 1–2 MB to an otherwise empty family. When that family is then nested into a parent, the parent carries the full geometric payload of every child.
The practical implication: families built from manufacturer-supplied SAT or ACIS geometry are routinely ten to fifty times heavier than equivalent families built from Revit-native extrusions at the same visual fidelity. The SAT geometry is not parametric and cannot be simplified by Revit at runtime, so it loads in full regardless of the current detail level.
2. Nested Family Chains
Nesting is Revit's mechanism for component reuse: a door family references a door handle sub-family, which may itself reference a screw-head detail family. The design intent is modular and maintainable. The file-size reality is that Revit embeds the full definition of every nested family inside the parent family file. You are not storing a reference or a pointer — you are storing a complete copy of the child's geometry, parameters, and types.
As Autodesk's official support article on reducing complex family size notes, a single nested component can add 300–400 KB, and chains of three or four levels of nesting can push an otherwise modest family past 5 MB before any bespoke geometry is added. The problem is that this cost is invisible until you open the family and examine its structure.
What makes this worse at library scale is the shared nested family problem. If your standard door family and your standard window family both nest the same ironmongery sub-family, your library contains two copies of that sub-family embedded in two parent RFAs, plus potentially a third standalone copy in a /components/ folder. Multiply across a library of 400 door types and 200 window types, and shared nesting duplication can account for hundreds of MB of redundancy.
3. Parameter Explosion
Parameters themselves are lightweight — a standalone parameter definition adds only a few bytes. The bloat comes from the interaction of parameter count with conditional visibility formulas and type proliferation.
A family with 80 parameters, each driving a visibility formula, forces Revit to evaluate the full formula set on every regeneration. When you have 200 types within that family, each type stores the resolved values of all 80 parameters. A manufacturer-supplied MEP equipment family — air handling units are the canonical offender — commonly ships with 30–50 scheduling parameters ("Asset ID", "Warranty Expiry", "Commissioning Contractor", "Maintenance Schedule URL") that will never be filled in on a design project and serve no modelling purpose. Those blank parameters cannot be removed by Purge Unused because they are defined, even if empty.
The OpenDefinery / Triple Zero blog documents a harder version of this problem: shared parameters that have been loaded into a family or project cannot be fully deleted through the standard UI. Revit retains the parameter's GUID definition internally even after the frontend deletion, meaning the schema overhead persists invisibly. The only complete removal path requires using RevitLookup to find and delete the element by ID — a destructive operation that strips the parameter from every family project-wide.
4. Plugin-Generated and Automation-Generated Families
Modern workflows produce families programmatically. Structural fabricators use plugins that generate custom connection families per joint. MEP coordination tools generate fitting families for every unique duct transition. Some structural analysis integrations create analytical element families with embedded calculation parameters.
The Revit API forum thread on batch loading families creating large files documents a specific mechanism: each doc.LoadFamily() call in a script can embed history data in the receiving document, so iterative batch loads accumulate overhead even when the net family content is unchanged. The families themselves may be individually small, but the load history attached to the project file grows with every script execution.
At library level, the pattern manifests as directories full of single-use families: AHU_Custom_Job2847.rfa, AHU_Custom_Job2848.rfa, each differing by a handful of dimension parameters. These families are never reviewed for consolidation because they were generated automatically and no human authored them with reuse in mind.
5. Version Proliferation
Revit's file format is not backward-compatible. An .rfa file saved in Revit 2023 cannot be opened in Revit 2022. As Autodesk's backward compatibility documentation states explicitly: "Revit does not have a feature to save to older version data formats." This is a hard constraint with a direct library consequence.
A practice running two or three Revit versions in parallel — common during multi-year projects that span version transitions — must maintain parallel copies of its entire content library. A library of 3,000 family files at an average of 400 KB becomes 3.6 GB when triplicated across versions 2022, 2023, and 2024. Most firms do not purge the old versions when a project closes. They copy the library forward and the older folder persists indefinitely.
In our experience, version-proliferated duplicates account for between 30 % and 45 % of total library disk footprint in firms that have been running Revit for more than five years. The content is identical except for the file version header — but it is indistinguishable from genuinely different content without opening each file.
Understanding the Internal File Structure
It helps to understand what Revit actually stores inside an .rfa file. The format is a Compound Document (a structured storage format originally from Microsoft's OLE2/COM technology) containing several streams. The primary content streams are:
- BasicFileInfo — version header, authoring application, file GUID
- PartAtom — the core parametric model database: geometry, parameters, types, formulas, visibility rules
- RevitPreview — a raster thumbnail image (PNG) of the family's 3D view, typically 200×150 pixels
- TransmissionData — link tracking for external references
The PartAtom stream is where all five bloat mechanisms accumulate. It stores the full parametric history of every edit the family has undergone — not just the current state. Every extrusion you added and then deleted, every parameter you created and renamed, every failed sketch that you rolled back: unless you explicitly compact the file, the database journal for all of these operations remains in the stream. This is why File → Save As with "Compact File" checked is the single highest-leverage operation in Pass 1 — it rewrites PartAtom containing only the current state, discarding the entire edit journal.
The RevitPreview thumbnail, while small in isolation, can represent a surprising fraction of a tiny family's file size. For a 40 KB detail component family, a 20 KB thumbnail represents 50 % overhead. Revit regenerates the thumbnail every time you save with the family editor open, but it does not clean up failed thumbnail renders from previous saves. This is minor compared to the other mechanisms but worth noting when optimising sub-100-KB families.
The Visibility-Formula Cost
A mechanism not often discussed separately is the interaction between parameter-driven visibility and Revit's constraint graph. When a family element's visibility is controlled by a Yes/No parameter (a common pattern for switching between Coarse, Medium, and Fine detail representations), Revit adds a constraint between the visibility attribute and the parameter value. With 50 such constraints across a complex family, Revit must evaluate the full constraint network on every view open, every regeneration, and every time the family is placed. The computation is fast for individual families, but when a project loads 2,000 instances of a family with a 50-node constraint graph, the aggregate regeneration cost becomes noticeable.
The optimisation is to use native Revit detail level assignment (FamilyElementVisibility class in the API) rather than parameter-driven visibility formulas for the Coarse/Medium/Fine toggle. Detail-level assignment is evaluated by the view engine without traversing the constraint graph. Reserve parameter-driven visibility for genuinely parametric variations (e.g., door handle present/absent based on a Yes/No type parameter) rather than for detail representation, which the view system can handle natively.
The Compounding Problem
These five mechanisms do not operate independently. A single MEP equipment family can exhibit all of them simultaneously: manufacturer-supplied SAT geometry (mechanism 1), nested sub-components for each service connection (mechanism 2), 60 scheduling parameters that nobody fills in (mechanism 3), generated as part of a product configurator batch run (mechanism 4), stored in three version folders (mechanism 5). That one family may represent 20–30 MB of library footprint — and there may be forty variants of it, one per SKU, each differing only in a length dimension.
The Pattern: How Bloat Manifests in Real Libraries
Knowing the mechanisms is one thing. Recognising the patterns in your actual library is another. These are the five most common configurations we see.
Pattern 1: The Over-Detailed Door Family
Door families are the single most common source of large RFA files in architectural libraries. The community standard target is 500 KB to 2 MB per door family — Autodesk's guidance for manufacturer content submissions to Seek set a 500 KB target — but it is routine to find door families in the 8–20 MB range in practice libraries.
The anatomy of a bloated door family typically includes: an imported DWG used for the plan-view swing graphic (instead of a native symbolic line); a nested handle sub-family that itself contains a nested rose-plate sub-family; manufacturer 3D geometry imported as a SAT solid for the door leaf; and 40+ type parameters encoding every ISO fire-rating and acoustic performance classification the manufacturer offers, most of which are never scheduled on any project.
As the community discussion on what constitutes a large family file size documents, a template with "all doors loaded" can sit at 800 MB before a single wall exists — an extraordinary overhead for what is fundamentally a door schedule placeholder.
The imported DWG is particularly destructive: as BIM Heroes documents, a single door family embedding a DWG for its plan swing injects that DWG's layer table, line-style definitions, and proxy objects into every project that loads the door. This data is described as "un-purgeable" — it survives Purge Unused because Revit treats the CAD import definition as referenced.
Pattern 2: MEP Equipment with Embedded Sub-Components
Air handling units, fan coil units, switchgear panels, and packaged chiller units are the heaviest families in most MEP libraries. The structural reason is the nesting depth required to represent them faithfully: a central AHU family will nest separate supply-fan, return-fan, heating-coil, cooling-coil, and filter-bank sub-families. Each of those sub-families may nest connector geometry sub-families. A five-level nesting chain where each level contributes 300 KB of geometry produces a 1.5 MB family before the outer shell geometry is considered.
The commercial BIM content problem compounds this. Manufacturer-supplied MEP families from platforms like BIMobject or MagiCAD Connect are built to showcase products, not to optimise project performance. They routinely include full photorealistic material definitions with embedded texture images, detailed 3D geometry intended for marketing renders, and the full parameter set from the manufacturer's product data sheet. As IGS Group's analysis of manufacturer content mistakes documents, the most common single mistake is importing an existing 3D CAD block rather than rebuilding the geometry natively — a process that "dramatically increases file size" and produces "imported geometry [that] is much more resource-intensive for Revit to process and display."
A related problem specific to MEP libraries is service-variant proliferation: a chiller family exists in variants for left-hand connection, right-hand connection, top connection, and combinations thereof, producing eight to sixteen RFA files that are geometrically identical except for connector placement. These proliferate because the person who created the variant for one job did not update the base family with a parametric connector option.
Pattern 3: Manufacturer Content Pulling In Unused References
Manufacturer BIM content — supplied directly by product manufacturers or downloaded from content portals — is the single most common source of unexpected library growth. The Kinship / BIM content quality analysis identifies blank scheduling parameters as a systematic issue: a family submitted to align with COBie or UNICLASS standards may include 30 or more parameters designed to be completed by a commissioning contractor years after the design project is closed. None of those parameters serves a function during design or construction documentation, but they cannot be removed without editing the family.
Beyond parameters, manufacturer families frequently reference external material libraries. When you download a family from a manufacturer portal and open it, Revit attempts to resolve material asset paths that may point to library directories that do not exist on your system. The family stores these unresolved references in its internal database, contributing to file size without providing any visible content.
The most extreme version of this pattern: a manufacturer family embeds texture images (JPEG or PNG files) directly inside the RFA to ensure the material renders correctly regardless of the user's material library configuration. A single 2048×2048 texture adds approximately 4 MB to the family file. A family representing a façade panel system with five finish variants, each with its own texture, can reach 25 MB from texture overhead alone.

Pattern 4: BIM360/ACC-Synced Libraries Without Governance
Autodesk Construction Cloud and BIM 360 have made cloud-hosted family libraries more accessible. A practice can publish its standard library to an ACC folder, give all staff read access, and rely on Revit's cloud content loading to retrieve families on demand. In principle this is cleaner than a shared network drive. In practice it introduces its own bloat pattern.
The ACC library grows because upload access is not the same as curation access. When a project team downloads a manufacturer family for a one-off project and saves it to the cloud library folder for "later use," that family joins the canonical library without any review. When the same family is later superseded by a newer version and a new file is uploaded, the old version stays because deletion requires deliberate action that nobody takes. Autodesk's cloud worksharing best practices focus on model performance but are silent on library governance — because Autodesk provides the storage mechanism, not the editorial policy.
The result: an ACC library that was curated to 800 families in 2021 has grown to 2,200 families by 2025, with roughly 600 of those being project-specific variants that were never intended for reuse and another 400 being version duplicates from the 2022-to-2023 upgrade cycle.
Pattern 5: In-Place Family Accumulation in Templates
This is technically a project-level pattern rather than a library-level one, but it deserves mention because it makes library bloat worse. In-place families — components created within a project rather than authored as standalone RFA files — cannot be exported, shared, or reused. They inflate the project or template file that contains them and cannot be removed by Purge Unused because they are always "in use" as modelled geometry.
As RV Boost's file size reduction guide quantifies: "If you place 10 copies of a Revit family loaded from outside the model, only 1 object definition is saved" versus "creating 1 in-place component and copying it 10 times [which] will add 10 different object definitions to your model." A template containing 15 in-place components — custom reception desks, bespoke stair stringers, one-off façade details — carries 15 separate full geometry definitions for objects that could have been a single parametric family.
If you use Save As on a family file and it only decreases in size by about 10 KB, it's about as small as it can get. If it decreases significantly — say, from 15 MB to 600 KB — then the original file had accumulated a substantial amount of dead data that the format was carrying silently.
The Three-Pass Audit
The audit is structured as three sequential passes, each building on the previous. Pass 1 uses the filesystem and Revit API to identify the heaviest files. Pass 2 detects duplicates using content-based comparison. Pass 3 reviews usage telemetry to find stale families that are present in the library but never loaded. You can run all three in a single day for a library of up to 2,000 families on a reasonably provisioned workstation.
Pass 1: Size Outliers
The goal of Pass 1 is to produce a ranked list of every RFA file in the library by file size, annotated with whether the family is flat (no nested components) or deeply nested, so you can triage intelligently.
Step 1a: Filesystem Scan with PowerShell
Start outside Revit. A PowerShell scan gives you the complete size picture across the entire library directory tree in under two minutes for a 2,000-file library, without opening a single file.
# Pass 1 — Filesystem RFA scan
# Usage: .\Scan-FamilyLibrary.ps1 -LibraryRoot "\\fileserver\bim-library"
param(
[Parameter(Mandatory=$true)]
[string]$LibraryRoot,
[string]$OutputCsv = ".\family-size-report.csv",
[int]$TopN = 50
)
$results = Get-ChildItem -Path $LibraryRoot -Recurse -Filter "*.rfa" |
Select-Object `
FullName,
Name,
@{Name="SizeKB"; Expression={[math]::Round($_.Length / 1KB, 1)}},
@{Name="SizeMB"; Expression={[math]::Round($_.Length / 1MB, 3)}},
LastWriteTime,
DirectoryName |
Sort-Object SizeMB -Descending
# Write full report
$results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8
# Print top N to console
$results | Select-Object -First $TopN |
Format-Table Name, SizeMB, LastWriteTime, DirectoryName -AutoSize
# Summary statistics
$total = ($results | Measure-Object -Property SizeKB -Sum).Sum
$top5pct = [math]::Ceiling($results.Count * 0.05)
$top5sum = ($results | Select-Object -First $top5pct |
Measure-Object -Property SizeKB -Sum).Sum
Write-Host ""
Write-Host "Total library: $([math]::Round($total/1MB, 1)) GB across $($results.Count) files"
Write-Host "Top $top5pct files ($top5pct% of library): $([math]::Round($top5sum/1MB, 1)) MB"
Write-Host "Top $top5pct% share: $([math]::Round($top5sum/$total*100, 1))%"
Write-Host "Report written to: $OutputCsv"Run this scan first. Before spending any time in the Revit API, you will know exactly which files are worth investigating. In our experience the 80/20 principle applies conservatively here: 80 % of addressable bloat sits in 15 % of files, so a ranked list immediately focuses attention.
Step 1b: Nest Depth Detection via Revit API (C#)
The PowerShell scan tells you sizes but not structure. To determine whether a heavy file is heavy because of its own geometry or because it embeds deeply nested families, you need to open the family document and inspect it. The following C# snippet enumerates all families in the active project and reports their nest depth and total family count:
// Pass 1b — Family size and nesting reporter
// Requires: Autodesk.Revit.DB, Autodesk.Revit.UI
// Target: Revit 2024 / 2025 / 2026 (FilteredElementCollector API stable since 2013)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
[Transaction(TransactionMode.ReadOnly)]
public class FamilySizeReporter : IExternalCommand
{
public Result Execute(ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
Document doc = commandData.Application.ActiveUIDocument.Document;
var report = new List<FamilyRecord>();
// Collect all loadable families in the project
var families = new FilteredElementCollector(doc)
.OfClass(typeof(Family))
.Cast<Family>()
.Where(f => f.IsEditable) // exclude system families
.ToList();
foreach (Family fam in families)
{
try
{
Document famDoc = doc.EditFamily(fam);
if (famDoc == null) continue;
// Count nested families (one level deep)
var nestedFamilies = new FilteredElementCollector(famDoc)
.OfClass(typeof(Family))
.Cast<Family>()
.ToList();
// Count family types
var typeCollector = new FilteredElementCollector(famDoc)
.OfClass(typeof(FamilySymbol))
.Cast<FamilySymbol>()
.ToList();
// Get parameter count via FamilyManager
FamilyManager famMgr = famDoc.FamilyManager;
int paramCount = famMgr.Parameters.Cast<FamilyParameter>().Count();
// Attempt to get file size if family has a path
long fileSizeBytes = 0;
string famPath = famDoc.PathName;
if (!string.IsNullOrEmpty(famPath) && File.Exists(famPath))
fileSizeBytes = new FileInfo(famPath).Length;
report.Add(new FamilyRecord
{
Name = fam.Name,
Category = fam.FamilyCategory?.Name ?? "Unknown",
NestedCount = nestedFamilies.Count,
TypeCount = typeCollector.Count,
ParameterCount = paramCount,
FileSizeKB = fileSizeBytes / 1024.0,
FilePath = famPath
});
// Close the family document without saving
famDoc.Close(false);
}
catch (Exception ex)
{
// Log and continue — corrupt or in-place families may throw
TaskDialog.Show("Warning",
$"Could not inspect family '{fam.Name}': {ex.Message}");
}
}
// Sort by file size descending, write to CSV
var sorted = report.OrderByDescending(r => r.FileSizeKB).ToList();
string csvPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"family-audit-{DateTime.Now:yyyyMMdd-HHmm}.csv");
using (var writer = new StreamWriter(csvPath))
{
writer.WriteLine(
"Name,Category,FileSizeKB,NestedCount,TypeCount,ParameterCount,FilePath");
foreach (var r in sorted)
writer.WriteLine(
$"\"{r.Name}\",\"{r.Category}\"," +
$"{r.FileSizeKB:F1},{r.NestedCount}," +
$"{r.TypeCount},{r.ParameterCount},\"{r.FilePath}\"");
}
TaskDialog.Show("Family Audit Complete",
$"Inspected {report.Count} families.\n" +
$"Report written to: {csvPath}");
return Result.Succeeded;
}
private class FamilyRecord
{
public string Name { get; set; }
public string Category { get; set; }
public int NestedCount { get; set; }
public int TypeCount { get; set; }
public int ParameterCount { get; set; }
public double FileSizeKB { get; set; }
public string FilePath { get; set; }
}
}Step 1c: pyRevit Version for Cross-Compatibility
For teams that cannot deploy a compiled C# add-in, the equivalent in pyRevit-compatible Python:
# Pass 1c — pyRevit family inspector
# Run via pyRevit button or Push Button panel
# Compatible with Revit 2022 through 2026
import os, csv
from datetime import datetime
from pyrevit import revit, DB, forms, script
doc = revit.doc
output = script.get_output()
output.close_others()
# Collect loadable families
collector = DB.FilteredElementCollector(doc)\
.OfClass(DB.Family)\
.ToElements()
families = [f for f in collector if f.IsEditable]
records = []
output.print_md("## Family Size Audit — Pass 1")
output.print_md(f"Scanning {len(families)} loadable families...")
for fam in families:
try:
fam_doc = doc.EditFamily(fam)
if fam_doc is None:
continue
nested = DB.FilteredElementCollector(fam_doc)\
.OfClass(DB.Family)\
.GetElementCount()
params = fam_doc.FamilyManager.Parameters.Size
file_path = fam_doc.PathName or ""
file_size_kb = 0
if file_path and os.path.exists(file_path):
file_size_kb = os.path.getsize(file_path) / 1024.0
records.append({
"name": fam.Name,
"category": fam.FamilyCategory.Name if fam.FamilyCategory else "N/A",
"size_kb": round(file_size_kb, 1),
"nested": nested,
"params": params,
"path": file_path,
})
fam_doc.Close(False)
except Exception as ex:
output.print_md(f"> **Warning**: Could not inspect `{fam.Name}`: {ex}")
records.sort(key=lambda r: r["size_kb"], reverse=True)
# Print top 20 to output
output.print_md("### Top 20 families by on-disk size")
output.print_table(
table_data=[[r["name"], r["category"],
f"{r['size_kb']:.1f} KB", str(r["nested"]),
str(r["params"])]
for r in records[:20]],
columns=["Name", "Category", "Size (KB)", "Nested", "Params"]
)
# Write CSV
ts = datetime.now().strftime("%Y%m%d-%H%M")
desktop = os.path.expanduser("~/Desktop")
csv_out = os.path.join(desktop, f"family-audit-{ts}.csv")
with open(csv_out, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(
f, fieldnames=["name","category","size_kb","nested","params","path"])
writer.writeheader()
writer.writerows(records)
output.print_md(f"\nFull report: `{csv_out}`")What to Do With Pass 1 Results
After Pass 1 you have a ranked list. Apply these triage thresholds — based on the community benchmarks from Revit Forum discussions and the Symetri guidelines:
Investigate immediately
Acceptable range
Flag for review
Expected heavy
Strong signal of CAD import
Typical parametric door
For every file above the investigation threshold, open it in Revit with Audit checked (File → Open, tick the Audit checkbox) and immediately run Manage → Purge Unused. The BIM Chapters maintenance guide documents that a single Purge pass is insufficient — run it until zero elements remain, which typically requires two to three passes because removing a nested family in pass 1 may expose an orphaned material in pass 2. After purging, do File → Save As with "Compact File" checked. Record the before and after sizes. Files that shrink by more than 50 % from this process alone contained significant dead data.
Run PowerShell scan against library root
Generate the ranked CSV. Focus on files over 2 MB — those are your primary targets.
Open top 20 files with Audit checked
Use File → Open → check Audit. This triggers Revit's internal corruption scan and often releases hidden overhead on first open.
Run Purge Unused three times
Manage → Settings → Purge Unused. Click Check None, then manually select families, materials, and line patterns. Repeat until the dialog reports zero purgeable items.
Save As with Compact File
File → Save As → Family. In the Save dialog, click Options and check "Compact File." This rewrites the internal file structure and eliminates fragmentation accumulated from successive edits.
Compare before/after sizes
Files that shrink more than 60% had significant dead data. Files that shrink less than 10% are already near-optimal for their geometry complexity.
Pass 2: Duplicate Detection
Pass 2 identifies families that are functionally identical but stored as separate files. There are three levels of duplicate: exact file duplicates (same bytes, different paths), parameter-signature duplicates (same parameter set and category, different geometry history), and geometry-equivalent duplicates (same visual output, built from different authoring paths).
Exact duplicates are the easiest to find and most common. They arise from version-folder proliferation, copy-and-rename workflows, and families that were downloaded twice from manufacturer portals under different names.
Step 2a: Cryptographic Hash Comparison (PowerShell)
Compare SHA256 hashes of every RFA file. Files with identical hashes are byte-for-byte duplicates regardless of filename or path:
# Pass 2a — Cryptographic duplicate detection for RFA files
param(
[Parameter(Mandatory=$true)]
[string]$LibraryRoot,
[string]$OutputCsv = ".\family-duplicates-hash.csv"
)
$hasher = [System.Security.Cryptography.SHA256]::Create()
$results = @{}
Get-ChildItem -Path $LibraryRoot -Recurse -Filter "*.rfa" | ForEach-Object {
$fileBytes = [System.IO.File]::ReadAllBytes($_.FullName)
$hashBytes = $hasher.ComputeHash($fileBytes)
$hashString = [BitConverter]::ToString($hashBytes).Replace("-","").ToLower()
if (-not $results.ContainsKey($hashString)) {
$results[$hashString] = @()
}
$results[$hashString] += [PSCustomObject]@{
Hash = $hashString
FullPath = $_.FullName
SizeMB = [math]::Round($_.Length / 1MB, 3)
Modified = $_.LastWriteTime
}
}
# Extract groups with more than one file (true duplicates)
$duplicateGroups = $results.Values | Where-Object { $_.Count -gt 1 }
$flatList = foreach ($group in $duplicateGroups) {
foreach ($file in $group) {
[PSCustomObject]@{
DuplicateGroup = $file.Hash.Substring(0,12) # short hash for grouping
FullPath = $file.FullPath
SizeMB = $file.SizeMB
Modified = $file.Modified
}
}
}
$flatList | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8
$totalGroups = ($duplicateGroups | Measure-Object).Count
$totalFiles = ($flatList | Measure-Object).Count
$redundantMB = ($flatList | Group-Object DuplicateGroup | ForEach-Object {
$sorted = $_.Group | Sort-Object SizeMB -Descending
# All files except the canonical one (largest/most recent) are redundant
$sorted | Select-Object -Skip 1 | Measure-Object -Property SizeMB -Sum
} | Measure-Object -Property Sum -Sum).Sum
Write-Host ""
Write-Host "Duplicate groups found: $totalGroups"
Write-Host "Total duplicate files: $totalFiles"
Write-Host "Redundant storage: $([math]::Round($redundantMB,1)) MB"
Write-Host "Report: $OutputCsv"Step 2b: Parameter Signature Matching
Families that are not byte-for-byte identical may still be functionally equivalent — built by different people to serve the same purpose, differing only in incidental parameter names or minor geometry variations. Parameter signature matching compares the sorted set of shared parameter GUIDs and built-in parameter names within each family. Two families with identical parameter signatures and the same Revit category are strong candidates for consolidation review.
// Pass 2b — Parameter signature fingerprinting
// Produces a signature string per family for comparison
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
public static string GetParameterSignature(Document famDoc)
{
if (famDoc == null || !famDoc.IsFamilyDocument)
return string.Empty;
var mgr = famDoc.FamilyManager;
var paramNames = new List<string>();
foreach (FamilyParameter fp in mgr.Parameters)
{
// Use shared parameter GUID when available (stable identity),
// otherwise use the parameter name (less stable but better than nothing)
if (fp.IsShared)
{
// Shared parameters have a GUID — use it for stable comparison
paramNames.Add($"SHARED:{fp.GUID}:{fp.StorageType}");
}
else
{
paramNames.Add($"LOCAL:{fp.Definition.Name}:{fp.StorageType}");
}
}
// Sort to make signature order-independent
paramNames.Sort();
return string.Join("|", paramNames);
}
// Usage: collect signature for each family, group by signature,
// review groups with multiple families for consolidation candidatesTwo families with the same category, the same parameter signature, and visual geometry within 10 % of each other by volume (if you have geometry comparison tooling) are almost certainly consolidation candidates. The decision to consolidate is human — you need to check whether type names differ in ways that matter for schedules — but the signature match gives you the candidate list.
Step 2c: Name-Pattern Duplicate Detection
The simplest and often most productive duplicate detection is name-pattern analysis. Revit appends a numeric suffix when a family with an existing name is loaded into a project that already contains a different family with the same name: Door-Single-Flush and Door-Single-Flush1 are the canonical example documented in the practicalBIM-Revit guide on duplicated families. If these versioned-name families have migrated back out to the library (for example, via File → Save As → Library), you will have Door-Single-Flush.rfa and Door-Single-Flush1.rfa as separate library entries.
# Pass 2c — Name-pattern duplicate detection
# Finds files whose names differ only by a trailing numeric suffix
param([string]$LibraryRoot = ".")
$rfaFiles = Get-ChildItem -Path $LibraryRoot -Recurse -Filter "*.rfa" |
Select-Object Name, FullName, @{
Name="BaseName"; Expression={ $_.BaseName -replace '\d+$','' }
}
$rfaFiles |
Group-Object BaseName |
Where-Object Count -gt 1 |
ForEach-Object {
Write-Host "Potential duplicates (base name: '$($_.Name)'):"
$_.Group | ForEach-Object {
Write-Host " $($_.FullName)"
}
Write-Host ""
}Duplicate Resolution Workflow
Once you have your duplicate candidate list from all three sub-passes, the resolution workflow is:
Identify the canonical version
For hash duplicates in version folders, the canonical version is the most recent version that your active projects require. For name-pattern duplicates, inspect both files — the one with the lower-numbered suffix is usually the earlier and should be superseded.
Verify no active project depends on the non-canonical copy
Before deleting any file, search your active project RVT files to ensure no project has loaded the family from the non-canonical path. The Ideate Explorer tool performs this cross-file analysis; alternatively, a Revit API script can check
Family.GetFamilyFiles()against your candidate list.Archive, do not immediately delete
Move non-canonical files to an
/archive/subfolder with a datestamped rename rather than deleting immediately. Keep archives for one full project cycle (typically 6–12 months) before permanent deletion.Update library references
If your library uses a manifest file (JSON, Excel, or Dynamo-readable CSV listing canonical paths), update it to reflect the consolidated paths. If teams load families by browsing the folder, communicate the change via your BIM standard update process.
Pass 3: Stale-Family Review
A family is stale if it exists in the library but has not been loaded into any project in the past 12–18 months. Staleness alone is not a deletion criterion — a specialist family for a rare project type may be legitimately unused for years — but it is the primary filter for the human review that produces the deprecated-family list.
Step 3a: Tracking Family Load Events via the Revit API
The most rigorous stale-detection approach is tracking family load events over time using the Revit API's event system. The Application.FamilyLoadedIntoDocument event fires every time a family is loaded from disk into a project. An add-in that hooks this event can maintain a log of family names, source paths, and load timestamps.
The event is part of Autodesk.Revit.ApplicationServices.Application and has been available since Revit 2015, as documented in the Revit API documentation for FamilyLoadedIntoDocument. Key characteristics:
- Non-cancellable (the load has already occurred)
- Provides
FamilyLoadedIntoDocumentEventArgswith status, document reference, and family identity - The companion
FamilyLoadingIntoDocumentevent (pre-load) can be cancelled if you need enforcement logic
// Pass 3a — Family load telemetry add-in
// Register in your firm's standard add-in package
// Appends a log entry every time a family is loaded
using System;
using System.IO;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.DB.Events;
using Autodesk.Revit.UI;
public class FamilyLoadLogger : IExternalApplication
{
private static readonly string LogPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"BIMFirm", "family-load-log.tsv");
public Result OnStartup(UIControlledApplication application)
{
// Ensure log directory exists
Directory.CreateDirectory(Path.GetDirectoryName(LogPath));
// Subscribe to family load event
application.ControlledApplication.FamilyLoadedIntoDocument +=
OnFamilyLoaded;
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
application.ControlledApplication.FamilyLoadedIntoDocument -=
OnFamilyLoaded;
return Result.Succeeded;
}
private void OnFamilyLoaded(object sender,
FamilyLoadedIntoDocumentEventArgs e)
{
if (e.Status != Autodesk.Revit.DB.Events.RevitAPIEventStatus.Succeeded)
return;
// Log: timestamp, user, project path, family name, family source path
string entry = string.Join("\t",
DateTime.UtcNow.ToString("o"), // ISO 8601 timestamp
Environment.UserName,
e.Document?.PathName ?? "unsaved",
e.FamilyName ?? string.Empty,
e.FamilyPath ?? string.Empty
);
try
{
File.AppendAllText(LogPath, entry + Environment.NewLine);
}
catch
{
// Fail silently — do not disrupt the user's workflow
}
}
}After 12 months of log collection, query the log for families that appear zero times:
# Identify library families never loaded in the past 12 months
param(
[string]$LibraryRoot,
[string]$LogFile = "C:\ProgramData\BIMFirm\family-load-log.tsv",
[int]$StaleThresholdDays = 365
)
$cutoff = (Get-Date).AddDays(-$StaleThresholdDays)
# Load log entries within threshold period
$recentLoads = Import-Csv -Path $LogFile -Delimiter "`t" `
-Header Timestamp,User,Project,FamilyName,FamilyPath |
Where-Object {
[DateTime]$_.Timestamp -gt $cutoff -and $_.FamilyPath -ne ""
} |
Select-Object -ExpandProperty FamilyPath
$loadedPaths = [System.Collections.Generic.HashSet[string]](
$recentLoads | ForEach-Object { $_.ToLower() }
)
# Find library files not in the loaded set
Get-ChildItem -Path $LibraryRoot -Recurse -Filter "*.rfa" |
Where-Object { -not $loadedPaths.Contains($_.FullName.ToLower()) } |
Select-Object FullName, @{N="LastModified"; E={$_.LastWriteTime}},
@{N="SizeMB"; E={[math]::Round($_.Length/1MB,2)}} |
Sort-Object LastModified |
Format-Table -AutoSizeStep 3b: Project-Level Usage Audit
For a more direct measure of usage without historical telemetry, audit a representative sample of recent project RVT files and extract the list of families loaded in each:
# Pass 3b — Aggregate used-family list across a project folder
# Run as pyRevit script or Dynamo graph
import os
from pyrevit import revit, DB
# Collect all families currently loaded in the active document
doc = revit.doc
used = {}
families = DB.FilteredElementCollector(doc)\
.OfClass(DB.Family)\
.ToElements()
for fam in families:
cat_name = fam.FamilyCategory.Name if fam.FamilyCategory else "System"
used[fam.Name] = {
"category": cat_name,
"type_count": sum(1 for _ in
DB.FilteredElementCollector(doc)
.OfClass(DB.FamilySymbol)
.WherePasses(DB.FamilySymbolFilter(fam.Id)))
}
# Print summary
for name, info in sorted(used.items(), key=lambda x: x[1]["type_count"], reverse=True):
print(f"{name:50s} | {info['category']:30s} | {info['type_count']} types")Run this script against each of your last 10 completed projects and aggregate the results. Families that do not appear in any project are your primary stale candidates. Families that appear in one project from three years ago but none since warrant a conversation with the discipline lead before archival.
Step 3c: The Deprecation Decision Matrix
Stale detection generates candidates; humans make the deprecation decision. Use this matrix:
| Usage (last 24 months) | Last modified | Action |
|---|---|---|
| Used in 3+ projects | < 2 years ago | Active — keep, review size |
| Used in 1–2 projects | Any | Review with discipline lead |
| Used in 0 projects | > 3 years ago | Deprecate candidate |
| Used in 0 projects | < 1 year ago | New/experimental — keep, flag for review in 12 months |
| Used in 0 projects, duplicate hash | Any | Remove immediately after archive |
For families designated as deprecation candidates, the workflow is:
- Tag the file with a
_DEPRECATED_YYYY-MMsuffix in the library (do not delete) - Notify project teams via BIM standard bulletin that the family will be removed in 60 days
- After 60 days, move to
/archive/deprecated/folder - After one project cycle, delete from archive
Integrating the Three Passes: Scheduling and Tooling
The three passes are designed to be run in sequence but you do not need to complete all three before seeing results. Pass 1 alone — the filesystem scan followed by targeted family cleanup — typically reclaims 20–30 % of library footprint in a single working session and requires no custom tooling beyond PowerShell and a standard Revit installation.
For teams that want to run the full audit without writing custom C# add-ins, the following commercially available tools map to the pass structure:
-
Pass 1 (size outliers): pyRevit's built-in "List Family Sizes" tool (Inspect dropdown) performs the in-project family size enumeration described in Step 1c, without any custom scripting. It temporarily saves each family to a temp folder to retrieve the on-disk size, as documented in the pyRevit Forums discussion. Note the limitation: it reports the saved-file size, which may differ from what is currently embedded in the project if the family has been edited in-session without saving.
-
Pass 2 (duplicates): Ideate Explorer provides cross-project family auditing, including detection of families that share a name but differ in content — the core Pass 2 requirement. The Ideate Software family auditing documentation describes how to verify that custom RFA files are free of CAD imports and unnecessary nesting.
-
Pass 3 (stale review): BIM 360 / ACC project analytics surfaces family load counts per project if you are using cloud collaboration. For network-share workflows, the telemetry add-in described in Step 3a is the primary mechanism.
Regardless of tooling, the sequencing matters. Do not run Pass 2 (duplicate detection) before Pass 1 (size reduction), because a bloated family and its smaller cleaned version will not register as duplicates — they will be different sizes and different hashes. Clean first, then deduplicate.
Anti-Patterns: What Not to Do
The audit process has well-worn failure modes. These are the ones that cause the most damage.
Anti-Pattern 1: Running Purge All Without a Backup
Revit's Purge Unused dialog pre-selects everything by default. Clicking OK without reviewing the selection is the most common cause of template corruption during maintenance. As the Symetri purge guidance documents, the purge command will remove family types that are defined in a template but not yet used in a project — exactly the types that represent your firm's content standards and that you would need to reload manually if removed.
The safe procedure is to click Check None first, then manually select only the categories you have verified are safe to purge. Run purge in stages: families first, then materials, then line patterns — because removing a family in pass 1 may expose an orphaned material in pass 2 that was only referenced by that family.
Never run Purge Unused on the central model with other users synchronised in. Always work on a detached local copy. Never run it on a template without first confirming every family type in the template is either intentionally present or was orphaned from a previous project.
Anti-Pattern 2: Deleting Library Files Based on Filename Alone
Filename-based decisions are unreliable for two reasons. First, Revit's auto-incrementing duplicate naming (FamilyName1, FamilyName2) means the lower-numbered file is not necessarily older or inferior — it may be the canonical version that projects have loaded. Second, a file named AHU_Generic.rfa may be used by 40 projects while a file named AHU_Custom_ProjectABC.rfa may be a completely generic family that someone misnamed.
Always open the candidate file before deletion. Check its category, its parameter set, and whether it matches any family name in your recent project audit results. A 30-second inspection prevents hours of family-reload work on an active project.
Anti-Pattern 3: Mid-Project Library Migration
The most dangerous timing for a library cleanup is during an active construction documents phase. When you move, rename, or delete a family from the library and a team member then reloads from library, Revit cannot find the original path and creates a FamilyName1 duplicate — or worse, loads a different family from the same category that happens to match the reload request. Either outcome corrupts the project's family registry.
Library migrations should happen at clear project milestones: at project inception (before any families are loaded from the library into the project file), at handover (after the project is frozen), or during a scheduled library update cycle coordinated across all active projects.
If you must clean the library while projects are active, add a redirect mapping — a plain-text file or Dynamo data set that maps old canonical paths to new canonical paths — and run a batch reload against active projects before you remove the old paths.
Anti-Pattern 4: Using "Save from Project" as a Library Update Mechanism
One legitimate file-size reduction technique is to load a family into a project, then export it back out using File → Save As → Library → Family. This process strips accumulated history data and can reduce a bloated family by 30–45 % as Symetri's optimization guide documents. The anti-pattern is using this technique on families with embedded materials, because the round-trip through a project strips material asset data that the family relied on for its appearance. The result is a smaller file with broken materials.
If you use the project-export technique, immediately open the exported family, verify every material renders correctly, and reload any materials that were stripped. Document which materials require manual reloading.
Anti-Pattern 5: Batch-Purging Without Nest-Awareness
Revit's Purge Unused does not recurse into nested families. A parent family can appear clean after purge while each of its nested children carries 2 MB of dead data from CAD imports and abandoned type history. RV Boost's deep-purge guide recommends tools like RV FamilyPurge that recurse into nested families and purge at every level. Without nest-aware purging, Pass 1 size reduction is systematically understated for heavily nested families like MEP equipment.
Anti-Pattern 6: Library Governance as a One-Off Event
Perhaps the most consequential anti-pattern is treating a library audit as a project rather than a process. A library that is audited in January and left ungoverned will return to its pre-audit footprint within 12–18 months through the same organic mechanisms that produced the original bloat. The audit described in this article is the remediation; the prevention requires ongoing governance.
A minimal ongoing governance structure includes three things: a designated approver for new family additions (not just a naming convention, but a human who reviews new files before they enter the canonical library); a quarterly size-check using the Pass 1 PowerShell scan, with an alert threshold at 10 % growth since last scan; and a clear entry in the BIM Execution Plan for every project stating which version of the library is being used and where project-specific families live.
The CTC Software BIM Manager Suite documentation describes a commercially supported workflow for family governance. The manual equivalent — a locked read-only canonical library directory, a separate /submitted/ folder where anyone can add files, and a monthly review meeting where the BIM Manager processes the submitted folder — costs nothing to implement and prevents the majority of unreviewed family proliferation.
Anti-Pattern 7: Upgrading the Entire Library Before Confirming Compatibility
When a practice upgrades its Revit version, the temptation is to immediately upgrade all library families to the new version. The Autodesk file upgrade guidance recommends upgrading version by version when jumping multiple releases (e.g., 2021 → 2022 → 2023 → 2024) rather than jumping directly, as multi-version jumps can drop features or break formulas that relied on intermediate behaviour.
More practically: do not upgrade the library until you have at least one active project running on the new version, and do not delete the old-version families until all projects that loaded from them have been closed and archived. The usual consequence of premature library upgrade is a stale project — one in active use — that cannot reload a family because the on-disk version is now newer than the project's Revit version. The project team then downloads a fresh copy from the manufacturer portal, creating yet another variant in the library.
Case Studies: Composite Numbers from Library Audits
The following case studies are composites. Names and project specifics have been removed. Figures represent the observed ranges from the audit methodology described above.
Case Study A: Mid-Size Architectural Practice (8 Active Projects, Revit 2022–2024)
Starting state: 2,847 RFA files across a network library organised by discipline and Revit version. Total footprint: 4.2 GB. Project template opens in 4 minutes 20 seconds on the standard workstation spec.
Pass 1 findings: Top 60 files (2.1 % of library) accounted for 1.18 GB (28 % of footprint). The largest single file was a curtain wall panel family at 94 MB — investigation revealed it contained a 47 MB texture set for seven finish variants, none of which were used in any active project schedule. Second largest was a door family at 67 MB containing a nested handle family that itself contained an imported DWG of the manufacturer's specification sheet (not the geometry — the spec sheet, a multi-page PDF rasterised to a DWG).
Pass 1 action: Rebuilt the 20 highest-footprint families using native Revit geometry. Removed embedded textures (textures moved to the firm's shared material library). Removed all CAD imports from families. Size after rebuild: top 60 files reduced from 1.18 GB to 187 MB.
Pass 2 findings: Hash comparison found 612 exact duplicate files across the three version folders (2022, 2023, 2024). These represented 847 MB of byte-for-byte identical content. Name-pattern analysis found 41 FamilyName1/FamilyName2 pairs, 38 of which were confirmed as duplicates of the canonical version.
Pass 2 action: Version-folder deduplication (keeping only the 2024 versions for active projects, archiving 2022 and 2023). This removed 847 MB. The 38 name-pattern duplicates added another 23 MB.
Pass 3 findings: Usage telemetry (18 months of logs available). 384 families had not been loaded into any project in 18 months. Of those, 156 were identified as project-specific families from closed projects that had migrated to the library informally. 228 were specialist families for project types the practice rarely undertook.
Pass 3 action: 156 project-specific families archived immediately (total: 234 MB). 228 specialist families moved to a /specialist/ subfolder with a deprecation review scheduled for 12 months hence.
Final state: Library reduced from 4.2 GB to 1.6 GB (62 % reduction). Template open time reduced from 4 minutes 20 seconds to 1 minute 55 seconds.
Case Study B: MEP Engineering Practice (15 Active Projects, Multi-Discipline)
Starting state: 1,943 RFA files. Total footprint: 3.1 GB. Dominant categories: mechanical equipment (41 % of footprint), electrical equipment (28 %), plumbing fixtures (14 %).
Pass 1 findings: The 12 most problematic families were all manufacturer-supplied MEP equipment families — AHUs, chillers, and dry-type transformers. Their average size was 38 MB; the median size for the same category in the Autodesk content library was 1.8 MB. Investigation revealed: every manufacturer family contained SAT geometry from a 3D CAD model used for marketing, texture maps at 4K resolution embedded in the RFA, and 55–70 parameters per family, of which an average of 8 were ever populated on projects.
Pass 1 action: For mechanical equipment: kept the manufacturer families in an /archive/manufacturer-original/ subfolder and rebuilt simplified equivalents using native Revit geometry at LOD 300 (per BIM Forum LOD Specification). Reduced average size from 38 MB to 2.1 MB per family. Removed 42 scheduling parameters per family on average, retaining only those mapped to the firm's COBie output requirements.
Pass 2 findings: 247 service-variant duplicates — the same chiller in left-hand, right-hand, top-connection variants as separate RFA files where a single parametric family with a connector-position parameter would serve all cases. Additionally, 108 families differing only in a model number suffix (e.g., AHU_Generic_Model-A.rfa, AHU_Generic_Model-B.rfa) where the geometry was identical and only the type parameter values differed — these should have been types within a single family, not separate files.
Pass 2 action: Consolidated 247 variant families into 63 parametric families (one per base product type, with connector position as an instance parameter). Consolidated 108 model-number variants into type catalogs within 14 parent families. Net reduction: 355 files and approximately 760 MB.
Pass 3 findings: 19 % of families had no project usage in the previous 24 months. Many of these were product lines that the practice had evaluated but not specified.
Pass 3 action: 187 stale families archived. 76 MB recovered.
Final state: Library reduced from 3.1 GB to 0.9 GB (71 % reduction). Average project synchronise-with-Central time reduced from 14 minutes to 6 minutes.
Case Study C: Infrastructure / Civil Engineering Practice
Starting state: Smaller library — 634 RFA files, 1.8 GB. Dominant categories: site furniture, civil infrastructure components, structural steel connections.
Dominant pattern identified: Version proliferation. The practice maintained seven active Revit version installations across ongoing infrastructure projects with multi-year timelines. The library existed in versions 2019 through 2025. The 2019 and 2020 folders alone accounted for 1.1 GB of the 1.8 GB total — content that had not been opened by any user in over two years.
Action: Confirmed with project managers that no active project required Revit 2019 or 2020 families. Archived both version folders to offline storage (external drive, not network share). 1.1 GB removed from active library.
Pass 1 findings: In the remaining library, 18 structural connection families averaged 12 MB each due to SAT geometry from analysis software exports. These were rebuilt as parametric Revit families, reducing average size to 1.4 MB.
Final state: Active library reduced from 1.8 GB to 0.44 GB (76 % reduction). The majority of the gain came from version-folder archival alone — the single most common quick win in infrastructure-focused practices with long project timelines.
Case Study D: Distributed Team Using ACC Library (Mixed Disciplines, 4 Offices)
Starting state: ACC-hosted library, 3,412 files, approximately 5.8 GB. Four offices in different cities, each discipline maintaining its own subfolder with minimal cross-discipline coordination. Library had grown organically over four years since the practice migrated from a shared network drive to ACC.
Pass 1 findings: The PowerShell scan (run against a locally synced copy via Desktop Connector) found 44 files over 10 MB. The single largest was a curtain wall panel family at 201 MB — a complete outlier. Investigation revealed it contained seven 4K texture maps embedded directly in the RFA, each representing a different glass frit pattern option. The textures were TIFF format (uncompressed), which explained the extreme size. The family was used in exactly one project, which had already reached construction administration.
Second observation: The structural discipline subfolder contained 412 families, of which 317 were structural connection families generated by a Tekla Structures integration plugin over a two-year period. Each connection family was unique to its joint geometry and had never been reused. Total: 317 one-use families accounting for 1.9 GB.
Pass 2 findings: Because the library spanned four offices, each office had independently downloaded several of the same manufacturer families. Hash comparison found 189 exact duplicates. Name-pattern analysis found 67 FamilyName1 variants that had been uploaded to the cloud library after being loaded into a project that already contained a same-named family.
Pass 3 findings: The telemetry add-in had not been deployed. The team used file access timestamps from the Desktop Connector sync logs as a proxy. 641 families showed no access events in 18 months.
Action: The 201 MB curtain wall family was rebuilt with compressed JPEG textures at 1K resolution (adequate for presentation renders) and texture files externalized to a shared material library. Reduced from 201 MB to 3.2 MB. The 317 Tekla-generated connection families were archived to a /structural/tekla-generated/ folder outside the canonical library — available for reference but not loaded by default. The 189 exact duplicates and 67 name-pattern duplicates were consolidated. Of the 641 stale candidates, 380 were archived after review; 261 were retained as specialist families on request from discipline leads.
Final state: Active library reduced from 5.8 GB to 2.1 GB (64 % reduction). Of equal importance: the four offices agreed on a governance protocol — a single BIM Coordinator with veto authority over new library additions, and a maximum file size of 5 MB per family (with exceptions requiring written justification).
What the Numbers Reveal Across All Four Cases
Aggregating the four composite case studies:
| Case | Starting Size | Final Size | Reduction | Primary Driver |
|---|---|---|---|---|
| A (Arch, 8 projects) | 4.2 GB | 1.6 GB | 62 % | CAD-import removal + version deduplication |
| B (MEP, 15 projects) | 3.1 GB | 0.9 GB | 71 % | Manufacturer family rebuild + variant consolidation |
| C (Civil, multi-year) | 1.8 GB | 0.44 GB | 76 % | Version-folder archival |
| D (Multi-disc, 4 offices) | 5.8 GB | 2.1 GB | 64 % | Tool-generated family archival + deduplication |
The primary driver varies by firm type. For architecture-dominant firms, CAD imports in door and window families are the leading culprit. For MEP firms, manufacturer content quality and service-variant proliferation dominate. For infrastructure firms with long project timelines, version proliferation is overwhelming. For distributed teams, cross-office duplication compounds all other mechanisms.
No single technique is universal. The three-pass structure works precisely because it covers all pathways to bloat without assuming which pathway is dominant in your library.
Limits and Caveats
What This Audit Does Not Cover
In-place families. These exist only inside project files. The filesystem scan and Revit API enumeration described above do not cover in-place families. A separate per-project audit is required. The FilteredElementCollector filter for in-place families uses WhereElementIsNotElementType() combined with category filtering, but the decision to convert an in-place family to a loadable RFA involves design intent considerations beyond the scope of a library audit.
System families. Walls, floors, ceilings, roofs, and ducts are system families — defined within the project, not loaded from RFA files. They cannot be exported or managed as library content. System family bloat (hundreds of wall types, most unused) is addressed by project-level purge workflows, not library audits.
Cloud-hosted content portals. If your workflow involves loading families directly from BIMobject, Manufacturer Cloud, or the Autodesk Content Service at point of need — rather than from a curated local/network library — the filesystem-based approach to Pass 1 and Pass 2 does not apply. You would need to audit the project files themselves for what was loaded, using the project-level API enumeration.
Numeric Claims and Their Basis
The "1.5 GB dormant families" figure and the "5 % of files / 28 % of footprint" observation are based on patterns observed in library audits of the type described in this article. They are scoped as "in our experience" and should not be treated as industry survey statistics. The actual distribution in your library will depend on discipline mix, years of operation, and how rigorously the library has been governed in the past.
The performance improvement figures (Revit 2026 graphics 27 % faster activation, IFC load 50 % faster) are from Autodesk's official Revit 2026 release notes. They apply to those specific operations and should not be interpreted as general model-loading improvements.
Version Compatibility of Code Snippets
The C# snippets use FilteredElementCollector, FamilyManager, and Application.FamilyLoadedIntoDocument — APIs that have been stable since Revit 2015. They compile and run against Revit 2024, 2025, and 2026 without modification. The [Transaction(TransactionMode.ReadOnly)] attribute is respected in all current versions. The pyRevit Python snippets use pyrevit.revit, pyrevit.DB, and pyrevit.forms — the standard pyRevit 4.x interface compatible with Revit 2022 onwards. If you are running pyRevit 5.x (the rewrite), the import paths change but the Revit DB API calls are identical.
The Save-from-Project Size Reduction Caveat
The 30–45 % size reduction from loading a family into a project and saving it back out is a real and documented technique. The caveat — that it strips embedded material assets — is significant enough that we do not recommend it as an automated batch process. Apply it individually to families you have fully inspected, and verify materials manually after each application.
Sources and Further Reading
-
Autodesk Help: Load Families with Shared Components (Revit 2025) — Official documentation on loading nested and shared families, including what happens to the family registry when shared components conflict.
-
Autodesk Help: What's New in Revit 2026 — Release notes documenting the Accelerated Graphics improvements (27 % faster view activation, 10 % RAM reduction) and the Manage Links dialog changes for tracking DWG imports in families.
-
AECbytes Tips and Tricks: What's New in Revit 2025 — Practitioner analysis of the small-array family improvement and extensible storage changes in Revit 2025. Good context for understanding what changed in family authoring capability.
-
BIM Forum: LOD Specification (2024) — The authoritative reference for geometry detail requirements at each LOD level. Essential context for deciding how much geometry a family needs — the primary lever for controlling RFA file size.
-
Symetri: Top Tips to Reduce Revit Family File Size (2018, techniques valid for 2026) — The most comprehensive single-source treatment of file size reduction techniques, including the 30–45 % project-export technique and its material-stripping caveat.
-
Symetri: To Purge or Not To Purge — Detailed analysis of what Purge Unused can and cannot remove, including the system-family and group limitations. Essential reading before running any purge workflow.
-
BIM Chapters: Revit Model Maintenance Best Practices — Documents the multi-pass purge requirement (continue until zero items remain) and the 50 % file size reduction observed after full maintenance procedures. The 200 MB / weekly maintenance threshold referenced in this article.
-
Triple Zero Blog: Purging Shared Parameters in Revit — The only practical documentation of the hidden shared parameter GUID problem and the RevitLookup workaround. Required reading before any parameter cleanup effort.
-
Revit API Docs: FamilyLoadedIntoDocument Event — Official API reference for the telemetry event used in Pass 3. Documents the non-cancellable nature,
FamilyLoadedIntoDocumentEventArgsstructure, and available-since version. -
The Building Coder: Element Level Events — Jeremy Tammik's authoritative reference for
Application.DocumentChangedand related database events. Useful background for understanding when family load events fire relative to transaction commit. -
archi-lab: Delete Duplicate Families from Revit Model — Practical Dynamo/Python implementation of location-based duplicate detection. The Python coordinate-string comparison technique is the basis for the signature-matching approach in Pass 2c.
-
Autodesk Support: Backward Compatibility of Revit with Earlier Releases — Official confirmation that Revit files cannot be saved to older formats, the fundamental constraint that drives version proliferation in multi-year practices.
Tools that pair with this post
- Revit
Revit central file corruption recovery: when worksharing breaks at 4 p.m. Friday
A central file that throws "Cannot open" at 16:12 on a Friday with 22 modellers about to clock off. The local-to-central extraction, audit-recover-detach sequence, and prevention playbook.
- Revit
Revit linked files: Overlay vs Attach, shared coordinates, host visibility trap
The structural link landed 1.2 km away from the architectural model. A nested link reload broke 47 sheets. The Overlay vs Attach decision and shared-coordinate workflow that holds.
- Revit
Revit performance triage: GPU vs CPU vs RAM bottleneck diagnosis
A new $4,200 RTX workstation that opens a 400 MB model in 8 minutes. The bottleneck is not the GPU. The Task Manager + Journal + Process Explorer workflow that finds the real cause.