laurent22/joplin · error · IOExceptionFast
Invalid file name: Could not extract filename from uri…
Error message
Invalid file name: Could not extract filename from uri string provided
What it means
react-native-saf-x throws this when createFileInDirectory is asked to create a file whose name contains a colon (':'). SAF document display names must not contain ':' characters, so the library rejects the name up front rather than producing a document the provider will mishandle. It is defensive input validation on the fileName argument.
Solutions
- Strip or reject ':' characters from fileName before calling createFile, e.g. fileName.replace(/:/g, '_')
- Pass only the base file name (e.g. 'report.pdf'), never a path or URI string, as fileName
- If deriving the name from a URI, decode and take the last segment after ':' (DocumentsContract convention) instead of embedding separators
- On Windows paths, use path.basename() or equivalent to get the bare file name first
Example fix
// before await Safx.createFile(treeUri, 'C:\\Users\\me\\report.pdf', 'application/pdf'); // after const safeName = 'C:\\Users\\me\\report.pdf'.split(/[\\/:]/).pop() ?? 'report.pdf'; await Safx.createFile(treeUri, safeName, 'application/pdf');
Defensive patterns
Strategy: validation
Validate before calling
function assertSafeSafFileName(name: string) {
if (!name || name.includes(':')) {
throw new Error(`Invalid SAF file name: ${JSON.stringify(name)}`);
}
}
// call before: assertSafeSafFileName(fileName); await Safx.createFile(...) Type guard
const isSafeFileName = (name: unknown): name is string =>
typeof name === 'string' && name.length > 0 && !name.includes(':') && !name.includes('/'); Prevention
- Never pass full paths or URIs as fileName; always pass the bare display name
- Sanitize user-supplied names by replacing [\\/:*?"<>|] before any SAF call
- When deriving a name from a URI, use the last segment after ':' (DocumentsContract convention), not the raw string
When it happens
Trigger: Calling createFile (or any path reaching createFileInDirectory via fileUri/destinationUri) with a fileName containing ':', e.g. 'C:\\file.txt' (a Windows path passed as a name), 'foo:bar.txt', or a name sliced from a full content:// URI.
Common situations: Passing a full Windows path instead of just the file name; concatenating a scheme prefix into the name; extracting a name from a URI incorrectly so a ':' leaks in; user-supplied names not sanitized on Android where ':' is legal in some filesystems but not SAF.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- The created file name was not as expected and the existing…
- The created file name was not as expected: input name was
- a file or directory already exist at
- could not create file at
- Ambiguous notebook " ". Please use notebook id instead -…
AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17).
Data as JSON: /api/errors/603bba573aaa0388.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/EfficientDocumentHelper.java:430
throw new IOExceptionFast("could not create file at: " + unknownStr);
}
return uri;
}
Uri parentDirOfFile = getDocumentUri(unknownStr, true, false);
String fileName = UriHelper.getFileName(unknownStr);
return createFileInDirectory(parentDirOfFile, fileName, mimeType, replaceIfDestinationExists);
}
private Uri createFileInDirectory(
final Uri parentDirOfFile,
final String fileName,
final String mimeType,
final boolean replaceIfDestinationExists
) throws IOException {
if (fileName.indexOf(':') != -1) {
throw new IOExceptionFast(
"Invalid file name: Could not extract filename from uri string provided");
}
String creationMimeType = "*/*";
// Some providers append the extension associated with the MIME type. Pass
// the MIME type only when the requested name already has an extension;
// extensionless Joplin resource IDs must remain extensionless.
String correctFileName = fileName;
if (mimeType != null && !mimeType.equals("")) {
int indexOfDot = fileName.lastIndexOf('.');
// len - 1 because there should be an extension that has at least 1 letter
if (indexOfDot > 0 && indexOfDot < fileName.length() - 1) {
correctFileName = fileName.substring(0, indexOfDot);
creationMimeType = mimeType;
}
}View on GitHub (pinned to 981a03c5c9)