Strapi refuses to start:
debug: ⛔️ Server wasn't able to start properly.
error: Cannot read properties of undefined (reading 'attributes')
TypeError: Cannot read properties of undefined (reading 'attributes')
at Object.getNonVisibleAttributes (.../@strapi/utils/lib/content-types.js)
at getNestedFields (.../@strapi/admin/server/services/content-type.js)
...The most upvoted advice on this error, historically, has been "recreate the project". You do not need to.
What it means
Strapi is walking your content types to build the admin panel's field configuration. For each attribute it needs to resolve the target schema, either a component or the content type on the other end of a relation, and read its attributes.
One of those lookups returned undefined, and the code then tried to read .attributes off nothing.
Translation: a content type references something that does not exist. Almost always a component; occasionally a relation, a dynamic zone entry, or a custom field whose plugin is not installed.
Find the broken reference
Every component reference vs. every component file
This one-liner catches the large majority of cases:
grep -rho '"component": "[^"]*"' src/api src/components \
| sed 's/.*: "//; s/"//' | sort -u \
| while read c; do
f="src/components/${c/./\/}.json"
[ -f "$f" ] || echo "MISSING: $c (expected $f)"
doneA component named shared.seo must exist at src/components/shared/seo.json. Anything printed is a reference with no file behind it.
Dynamic zones
Dynamic zones list their allowed components in a components array, which the grep above does not cover:
"blocks": {
"type": "dynamiczone",
"components": ["layout.hero", "layout.cta", "shared.quote"]
}Check those too:
grep -rA10 '"type": "dynamiczone"' src/api src/components \
| grep -o '"[a-z0-9-]*\.[a-z0-9-]*"' | tr -d '"' | sort -u \
| while read c; do
f="src/components/${c/./\/}.json"
[ -f "$f" ] || echo "MISSING (dynamiczone): $c"
doneRelations pointing at deleted content types
grep -rho '"target": "[^"]*"' src/api src/components \
| sed 's/.*: "//; s/"//' | sort -uEach api::foo.foo target needs src/api/foo/content-types/foo/schema.json to exist. Targets starting plugin:: refer to plugins, so plugin::users-permissions.user is fine as long as that plugin is installed.
Nested components
A component can reference another component. If layout.hero includes shared.button and shared/button.json was deleted, the failure surfaces when the parent is loaded, so the error will point you at hero rather than at the actual missing file. The first grep catches this because it searches src/components as well as src/api.
Custom fields from uninstalled plugins
grep -rn '"customField"' src/api src/componentsA "customField": "plugin::color-picker.color" on a content type whose plugin is no longer in package.json produces the same class of failure. Either reinstall the plugin or remove the attribute.
The migration case
This error is common right after a v3 → v4 or v4 → v5 migration, and the reason is structural: components moved location and naming conventions changed between major versions. Codemods handle most of it and reliably miss edge cases. As the original forum thread found, the root cause there was components that were never moved to their v4 location.
After running any migration tool:
- Run the checks above before doing anything else.
- Diff
src/components/against the old project and confirm every file arrived. - Open a few
schema.jsonfiles by hand and confirm the shape looks like the current version's.
For a v4 → v5 move specifically, use the upgrade tool and read the migration guide, because several attribute-level changes are documented there and are easy to miss.
Fixing it
Once you know which reference is broken, you have two options.
Restore the missing file
If it was deleted by accident, get it back:
git log --diff-filter=D --name-only -- src/components/
git checkout <commit>^ -- src/components/shared/seo.jsonThis is the better outcome, because the database table for that component still exists and the content in it is preserved.
Remove the reference
If the component is genuinely gone, edit the content type's schema.json and delete the attribute:
{
"kind": "collectionType",
"collectionName": "articles",
"info": { "singularName": "article", "pluralName": "articles", "displayName": "Article" },
"options": { "draftAndPublish": true },
"attributes": {
"title": { "type": "string" },
"body": { "type": "richtext" }
// "seo": { "type": "component", "component": "shared.seo" } ← removed
}
}Understand what this costs. On the next startup, Strapi's schema sync compares your content types with the database and drops tables, columns, and indexes it previously managed that no longer match. Removing the attribute means the component's data is deleted, without a prompt, in development and production alike.
If the data matters:
- Take a database backup first.
- Or set
forceMigration: falseinconfig/databasesettingsto skip drop operations, retrieve what you need, then decide. Note the caveat in the docs: the new schema is still recorded as the reference, so an object whose deletion was skipped stops being tracked and will not be dropped later if you set the flag back.
Getting a name out of the error
The stack trace does not tell you which content type failed. A temporary log in register will:
export default {
register({ strapi }) {
for (const [uid, ct] of Object.entries(strapi.contentTypes)) {
for (const [name, attr] of Object.entries<any>(ct.attributes ?? {})) {
if (attr.type === 'component' && !strapi.components[attr.component]) {
strapi.log.error(`Broken component reference: ${uid}.${name} → ${attr.component}`);
}
if (attr.type === 'dynamiczone') {
for (const c of attr.components ?? []) {
if (!strapi.components[c]) {
strapi.log.error(`Broken dynamiczone reference: ${uid}.${name} → ${c}`);
}
}
}
}
}
},
bootstrap() {},
};This runs early enough to print before the admin service crashes, so you get names instead of frames. Remove it once you have the answer.
Recovering from a boot loop
You cannot use the admin panel when Strapi will not start, so:
git statusandgit diff: the change is usually right there.git stashto test whether uncommitted work caused it.- Move suspect files out of the tree rather than deleting them:
mv src/components/shared/seo.json /tmp/. - Clear stale build output:
rm -rf dist .cache build .strapi && npm run build.
Preventing it
- Never delete a component file by hand. Use the Content-Type Builder, which removes references at the same time.
- Commit
src/components/andsrc/api/. These are code, and a schema diff in review catches broken references before they reach anyone else. - Boot Strapi in CI against a throwaway database. Every error in this post fails at startup, so a job that starts the server catches all of them at pull-request time.
- After any migration, run the grep checks above before you start debugging anything else.
Related
ERR_INVALID_ARG_TYPE: The "path" argument must be of type string is the sibling of this error: same underlying category, different failure point in the loader.




