{"record":{"id":"008354ef004fa885","repo":"apache/cordova-android","slug":"required-attribute-src-not-specified-in-source","errorCode":null,"errorMessage":"Required attribute \"src\" not specified in <source-file> element from plugin: ${plugin.id}","messagePattern":"Required attribute \"src\" not specified in <source-file> element from plugin: (.+?)","errorType":"validation","errorClass":"CordovaError","httpStatus":null,"severity":"error","filePath":"lib/pluginHandlers.js","lineNumber":29,"sourceCode":"\n    Unless required by applicable law or agreed to in writing,\n    software distributed under the License is distributed on an\n    \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n    KIND, either express or implied.  See the License for the\n    specific language governing permissions and limitations\n    under the License.\n*/\n\nconst fs = require('node:fs');\nconst path = require('node:path');\nconst isPathInside = require('is-path-inside');\nconst events = require('cordova-common').events;\nconst CordovaError = require('cordova-common').CordovaError;\n\nconst handlers = {\n    'source-file': {\n        install: function (obj, plugin, project, options) {\n            if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id));\n            if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id));\n\n            const dest = getInstallDestination(obj);\n\n            if (options && options.force) {\n                copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));\n            } else {\n                copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));\n            }\n        },\n        uninstall: function (obj, plugin, project, options) {\n            const dest = getInstallDestination(obj);\n\n            // TODO: Add Koltin extension to uninstall, since they are handled like Java files\n            if (obj.src.endsWith('java')) {\n                deleteJava(project.projectDir, dest);\n            } else {\n                // Just remove the file, not the whole parent directory","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/apache/cordova-android/blob/7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3/lib/pluginHandlers.js#L11-L47","documentation":"The 'source-file' installer in pluginHandlers validates each <source-file> element of a plugin.xml before copying anything: it requires a src attribute (the file inside the plugin) and, next, a target-dir attribute. A missing/empty src throws immediately with the plugin id in the message. Raised during `cordova plugin add`.","triggerScenarios":"`cordova plugin add <plugin>` (or pluginManager install) where the plugin's plugin.xml contains <source-file> without src, e.g. `<source-file target-dir=\"src/com/example\"/>` or src=\"\".","commonSituations":"Hand-written or local plugins missing attributes; plugin.xml refactored and the src attribute dropped; a plugin element copy-pasted from a different platform's section.","solutions":["Add src to the element, pointing at a file inside the plugin package: <source-file src=\"src/android/Foo.java\" ...>.","Verify the referenced file actually exists in the plugin directory.","Also set target-dir (the very next validation) to the destination under the Android project, e.g. target-dir=\"src/com/example\".","If the plugin is third-party, pin a fixed release or patch it locally (cordova plugin add ./path --searchpath)."],"exampleFix":"<!-- before -->\n<source-file target-dir=\"src/com/example/foo\" />\n\n<!-- after -->\n<source-file src=\"src/android/Foo.java\" target-dir=\"src/com/example/foo\" />","handlingStrategy":"validation","validationCode":"const et = require('elementtree');\nconst pluginXml = et.parse(fs.readFileSync('plugin.xml', 'utf8').toString());\nfor (const el of pluginXml.getroot().iter('source-file')) {\n  if (!el.attrib.src) {\n    throw new Error(`<source-file> in ${plugin.id} is missing src=\"...\"`);\n  }\n}","typeGuard":"const isValidSourceFileElement = (el) =>\n  Boolean(el && el.tag === 'source-file' && el.attrib && el.attrib.src && el.attrib['target-dir']);","tryCatchPattern":"try {\n  await api.addPlugin(pluginInfo);\n} catch (e) {\n  if (/Required attribute \".*\" not specified in <source-file>/.test(e.message)) {\n    // fix the named attribute in the named plugin's plugin.xml, then retry plugin add\n  }\n  throw e;\n}","preventionTips":["Validate plugin.xml with a linter/schema in the plugin's own CI before publishing.","Test every plugin release with a scratch `cordova plugin add` + build.","Require both src and target-dir on all source-file elements by convention in code review."],"tags":["plugin-xml","source-file","plugin-install","validation"],"backgroundTag":"plugin-manifest-missing-attribute","analyzedSha":"7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3","analyzedAt":"2026-08-22T04:57:58.868Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}