{"record":{"id":"9db6a739e8fca29e","repo":"mrdoob/three.js","slug":"three-propertybinding-can-not-parse-propertyname","errorCode":null,"errorMessage":"THREE.PropertyBinding: can not parse propertyName from trackName: ${trackName}","messagePattern":"THREE\\.PropertyBinding: can not parse propertyName from trackName: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/animation/PropertyBinding.js","lineNumber":249,"sourceCode":"\n\t\t\tconst objectName = results.nodeName.substring( lastDot + 1 );\n\n\t\t\t// Object names must be checked against an allowlist. Otherwise, there\n\t\t\t// is no way to parse 'foo.bar.baz': 'baz' must be a property, but\n\t\t\t// 'bar' could be the objectName, or part of a nodeName (which can\n\t\t\t// include '.' characters).\n\t\t\tif ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) {\n\n\t\t\t\tresults.nodeName = results.nodeName.substring( 0, lastDot );\n\t\t\t\tresults.objectName = objectName;\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( results.propertyName === null || results.propertyName.length === 0 ) {\n\n\t\t\tthrow new Error( 'THREE.PropertyBinding: can not parse propertyName from trackName: ' + trackName );\n\n\t\t}\n\n\t\treturn results;\n\n\t}\n\n\t/**\n\t * Searches for a node in the hierarchy of the given root object by the given\n\t * node name.\n\t *\n\t * @static\n\t * @param {Object} root - The root object.\n\t * @param {string|number} nodeName - The name of the node.\n\t * @return {?Object} The found node. Returns `null` if no object was found.\n\t */\n\tstatic findNode( root, nodeName ) {\n","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/mrdoob/three.js/blob/da05705fa31f02710c19772a2aa70c7454807f0c/src/animation/PropertyBinding.js#L231-L267","documentation":"Thrown by PropertyBinding.parseTrackName() as a secondary guard: the regex matched, but the propertyName capture group (match[5]) came back null or empty. Because the property regex segment (\\.(WC+)) is the only mandatory part of _trackRe, reaching this branch typically means the supported-object-name reshuffling at line 230-245 consumed the trailing token, leaving no property — i.e. the track name's only dot-delimited tail was an allowlisted object name ('material'/'materials'/'bones'/'map') with no property after it.","triggerScenarios":"A track name ending in '.material', '.materials', '.bones', or '.map' with no following property (e.g. 'mesh.material'), so the reshaping logic treats the token as objectName and leaves propertyName empty. Edge-case track names that the regex partially matches without capturing the property group.","commonSituations":"Animating 'material' as if it were a property rather than animating a property OF material (e.g. 'mesh.material' instead of 'mesh.material.opacity'). Building bindings from data where the property suffix was stripped.","solutions":["Append a concrete property after the object name: 'mesh.material.opacity', 'node.bones[0].position', 'sprite.map.repeat'.","If you intended to target the object itself (not a property), reconsider — PropertyBinding animates properties/accessors, not whole objects.","Validate that the track name ends in a property token after any allowlisted object name."],"exampleFix":"// before — allowlisted object name but no property after it\nnew NumberKeyframeTrack( 'mesh.material', times, values );\n// -> 'can not parse propertyName from trackName: mesh.material'\n\n// after — target a property of the material\nnew NumberKeyframeTrack( 'mesh.material.opacity', times, values );","handlingStrategy":"validation","validationCode":"const ALLOWED_OBJECTS = new Set( [ 'material', 'materials', 'bones', 'map' ] );\n// A name ending in an allowlisted object name with no property tail is the common trigger.\nfunction endsWithBareObject( name ) {\n  const tail = name.split( '.' ).pop();\n  return ALLOWED_OBJECTS.has( tail );\n}\nfunction validateTrackNameHasProperty( name ) {\n  if ( typeof name !== 'string' || endsWithBareObject( name ) ) {\n    throw new Error( `Track name needs a property after the object: ${name}` );\n  }\n}","typeGuard":"const ALLOWED_OBJECTS = new Set( [ 'material', 'materials', 'bones', 'map' ] );\nfunction trackNameHasPropertyTail( name ) {\n  if ( typeof name !== 'string' ) return false;\n  const parts = name.split( '.' );\n  if ( parts.length < 2 ) return false;\n  const tail = parts[ parts.length - 1 ];\n  return ! ALLOWED_OBJECTS.has( tail ) && tail.length > 0;\n}","tryCatchPattern":"try {\n  THREE.PropertyBinding.parseTrackName( name );\n} catch ( err ) {\n  if ( /can not parse propertyName/.test( err.message ) ) {\n    console.error( 'Track name ends in an object name with no property:', name );\n    return null;\n  }\n  throw err;\n}","preventionTips":["Animate a property OF an object, not the object itself: 'mesh.material.opacity' not 'mesh.material'.","Ensure the last dot-delimited token is a real property, not one of material/materials/bones/map.","Validate the property tail is non-empty before binding."],"tags":["animation","property-binding","parsing","track-name","validation"],"backgroundTag":null,"analyzedSha":"da05705fa31f02710c19772a2aa70c7454807f0c","analyzedAt":"2026-08-12T22:51:37.160Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}