Skip to content

Instantly share code, notes, and snippets.

@grncdr
Last active March 3, 2023 05:57
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save grncdr/afc97da1869f5fb3aef1 to your computer and use it in GitHub Desktop.
Save grncdr/afc97da1869f5fb3aef1 to your computer and use it in GitHub Desktop.
Delete unused assets

Delete unused assets

This is an example script for deleting assets that aren't linked in your content model. It does this by walking through all assets and checking for any links back to them.

WARNING: This script does not take into account assets that are only linked inside of Text fields. If you primarily embed images directly using the markdown editor, this will very likely delete assets you depend on.

You must fill in your own CMA access token & space ID at the top before running

Usage

git clone git@gist.github.com:/afc97da1869f5fb3aef1.git cleanup-assets
cd cleanup-assets
# Edit index.js to set up your credentials
npm install && npm run cleanup-assets
#!/usr/bin/env babel-node --stage=0
import fs from 'fs';
import contentful from 'contentful-management';
import xtend from 'xtend';
const client = contentful.createClient({
// Get one by logging in at https://www.contentful.com/developers/documentation/content-management-api/
accessToken: 'CMA ACCESS TOKEN'
});
const spaceId = 'YOUR SPACE ID';
async function main () {
const space = await client.getSpace(spaceId);
const assetLinkFields = await findAssetLinkFields(space);
console.log(assetLinkFields);
await walkAssets(space, maybeDeleteAsset.bind(null, space, assetLinkFields))
}
async function findAssetLinkFields (space) {
const contentTypes = await space.getContentTypes({});
return contentTypes.reduce((assetLinkFields, contentType) => {
assetLinkFields[contentType.sys.id] = contentType.fields.filter(fieldIsAssetLink).map((field) => field.id)
return assetLinkFields;
}, {});
}
function fieldIsAssetLink (field) {
return (field.type === 'Link' && field.linkType === 'Asset') ||
(field.type === 'Array' && fieldIsAssetLink(field.items))
}
async function walkAssets (space, fn) {
const order = '-sys.createdAt'
const limit = 1000;
async function next (skip) {
const items = await space.getAssets({ skip, limit, order })
await Promise.all(items.map(fn))
if (items.length === limit) {
return next(skip + limit)
}
}
return next(0);
}
async function maybeDeleteAsset (space, assetLinkFields, asset) {
const id = asset.sys.id
var linkCount = 0;
for (var contentTypeId in assetLinkFields) {
let fieldIds = assetLinkFields[contentTypeId];
for (var i = 0, len = fieldIds.length; i < len; i++) {
let fieldId = fieldIds[i];
const entries = await space.getEntries({
content_type: contentTypeId,
[`fields.${fieldId}.sys.id`]: asset.sys.id
})
linkCount += entries.length;
}
}
if (!linkCount) {
// No links to this asset from the selected field, safe to delete
if (asset.sys.publishedVersion) {
asset = await space.unpublishAsset(asset);
}
await space.deleteAsset(asset);
}
}
main().catch((err) => {
console.error(err.stack);
process.exit(1);
});
{
"name": "cleanup-assets",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"cleanup-assets": "babel-node --stage=0 index.js"
},
"author": "Stephen Sugden",
"license": "MIT",
"dependencies": {
"babel": "^5.5.8",
"contentful-management": "^0.1.1",
"xtend": "^4.0.0"
}
}
@samuel99
Copy link

samuel99 commented Mar 3, 2023

@samuel99 Thank you for this update and to all the others here. This has helped tremendously!

Np :) However I should add, that you probably have to run this script multiple times to delete all unlinked assets.

And I think this is the reason:
tmp_524abf68-9f2f-4061-a92d-da464a3a985c

(red squares are unlinked assets that are to be deleted, and green squares are assets that should be kept)

So there's room for improvement if someone wants to give it a go :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment