@liiift-studio/sanity-delete-unused-assets
v2.0.1
Published
Asset cleanup utility for Sanity Studio — remove unused assets, analyze storage, and detect duplicates
Readme
Sanity Delete Unused Assets
An asset cleanup utility component for Sanity Studio that identifies and permanently removes unused assets (images and files no longer referenced by any document) to reclaim storage. It also reports total storage usage and finds duplicate filenames. Built with @sanity/ui.
⚠️ This tool deletes data permanently. Deleting a Sanity asset is irreversible from the Studio. Read the Safety & irreversibility section before running it against a real dataset.
How it works
The component runs a single GROQ query for every image/file asset, counts how many documents reference each one, and treats any asset with zero references as unused. With dryRun it only reports; otherwise it deletes the unused assets in a transaction.
- Asset inventory — scans all
sanity.imageAssetandsanity.fileAssetdocuments in your dataset. - Reference analysis — for each asset, counts referencing documents with
count(*[references(^._id)]). - Usage detection — any asset with
refs == 0is flagged as unused. - Filter application — applies your
assetTypes,olderThan,excludePatterns, andmaxAssetsoptions. - Deletion — when not in
dryRun, deletes the flagged assets in a batched transaction and reports what was removed.
Features
- 🔍 Asset scanning — detects unused image and file assets by reference count
- 📊 Storage analysis — reports file sizes and total/per-type storage usage
- 🧬 Duplicate detection — groups assets that share the same original filename
- 🎯 Filtering — narrow the scan by asset type, age, exclude patterns, and a max count
- 🛡️ Safety controls —
dryRunpreview,excludePatterns, and batch processing - 📱 Sanity UI — built with
@sanity/ui, fits naturally inside a Studio tool or pane
Installation
npm install @liiift-studio/sanity-delete-unused-assetsQuick Start
The package's default export is a React component. Render it inside a Studio tool/pane and hand it a Sanity client:
import React from 'react'
import { DeleteUnusedAssets } from '@liiift-studio/sanity-delete-unused-assets'
import { useClient } from 'sanity'
const AssetCleanup = () => {
const client = useClient({ apiVersion: '2023-01-01' })
return (
<DeleteUnusedAssets
client={client}
dryRun // preview first — strongly recommended
onComplete={(results) => {
console.log(`Cleaned up ${results.deleted} assets, freed ${results.savedSpace} bytes`)
}}
/>
)
}The component is also available as the package's default export, so
import DeleteUnusedAssets from '@liiift-studio/sanity-delete-unused-assets'works too.
This package ships a component, not a Studio plugin — there is no auto-registering
tool. Mount the component yourself, for example as a custom tool in sanity.config.ts:
import { defineConfig, useClient } from 'sanity'
import { DeleteUnusedAssets } from '@liiift-studio/sanity-delete-unused-assets'
const AssetCleanupTool = () => {
const client = useClient({ apiVersion: '2023-01-01' })
return <DeleteUnusedAssets client={client} dryRun />
}
export default defineConfig({
// ...
tools: (prev) => [
...prev,
{ name: 'asset-cleanup', title: 'Asset Cleanup', component: AssetCleanupTool },
],
})With filters
<DeleteUnusedAssets
client={client}
assetTypes={['image']} // only image assets
olderThan={new Date('2024-01-01')} // only assets created before this date
excludePatterns={['hero-', 'logo-']} // skip filenames containing these
maxAssets={50} // never act on more than 50 at once
dryRun // preview the result without deleting
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| client | SanityClient | required | Sanity client instance (must have delete permissions to actually remove assets) |
| assetTypes | ('image' \| 'file')[] | ['image', 'file'] | Which asset kinds to scan |
| olderThan | Date | undefined | Only consider assets created before this date |
| excludePatterns | string[] | [] | Filename substrings to exclude from deletion |
| maxAssets | number | undefined | Cap on how many assets a single run will act on |
| batchSize | number | 10 | Number of assets processed per batch |
| dryRun | boolean | false | Preview mode — reports what would be deleted without deleting |
| onComplete | (results: { deleted: number; savedSpace: number; errors: string[] }) => void | undefined | Called when a run finishes |
| onError | (error: string) => void | undefined | Called on a top-level failure |
Safety & irreversibility
Deleting a Sanity asset is permanent. There is no Studio-level undo. Treat every non-dryRun run as destructive and follow these practices:
- Always run with
dryRunfirst and review the reported list before deleting for real. - Export a dataset backup before a real deletion:
sanity dataset export <dataset> backup.tar.gz. references()only sees published references. An asset referenced only by a draft document (or via a path/custom resolver thatreferences()does not traverse) can be counted as unused. Such assets may be deleted even though a draft still points at them.- Freshly uploaded but not-yet-saved assets have no references and will be flagged as unused — finish your edits before cleaning up.
- Scope your client. The
clientyou pass controls which dataset is affected and whether deletion is even permitted. A read-only token cannot delete (Sanity returns Insufficient permissions — pass a write token, e.g. via--with-user-tokenwhen scripting). - Use
excludePatternsandmaxAssetsto limit blast radius on large or unfamiliar datasets.
Dry run
<DeleteUnusedAssets client={client} dryRun />Preview what would be deleted, validate filters and exclude patterns, and sanity-check storage savings — all without touching your data.
Exclude patterns
excludePatterns={[
'hero-', // skip hero images
'logo-', // skip logos
'backup', // skip anything with "backup" in the filename
]}Batch processing
Large cleanups are processed in batches (batchSize, default 10) to avoid transaction timeouts on big asset collections.
Storage analysis
The component surfaces storage metrics alongside cleanup:
- Total storage used by image and file assets, with per-type breakdown
- Asset counts (total, images, files)
- Per-asset size and reference count
- Duplicate filename groups for spotting accidental re-uploads
Performance tips
- Use
batchSizeto control how aggressively a run processes. - Apply
assetTypes/olderThan/maxAssetsto reduce scan scope on large datasets. - Run large cleanups during off-peak hours.
Requirements
This package declares the following peer dependencies:
| Peer | Supported range |
|------|-----------------|
| sanity | ^3 \|\| ^4 \|\| ^5 |
| @sanity/ui | ^1 \|\| ^2 \|\| ^3 |
| @sanity/icons | ^2 \|\| ^3 |
| react | ^18 \|\| ^19 |
Regenerating the diagram
The data-flow diagram is generated from a committed Mermaid source (assets/data-flow.mmd):
npm run capture # renders assets/data-flow.svg via @mermaid-js/mermaid-cliLicense
MIT License. Licensed under the terms declared in package.json ("license": "MIT").
Contributing
Contributions are welcome. Please open an issue or pull request on the repository to help improve this utility.
