laurent22/joplin · error · Error

Format "${format}" can only be exported to a file

Error message

Format "${format}" can only be exported to a file

What it means

Thrown by exportFolders when the chosen format is 'jex' (a single-file archive) but the target path resolves to an existing directory. JEX export must write one bundle file; writing into a directory is structurally unsupported, so the command rejects it before invoking InteropService.export.

Source

Thrown at packages/app-desktop/commands/exportFolders.ts:26

};

export const runtime = (): CommandRuntime => {
	return {
		// "targetPath" should be a file for JEX export (because the format can
		// contain multiple folders) or a directory otherwise.
		execute: async (_context: CommandContext, folderIds: string[], format: ExportModuleOutputFormat, targetPath: string) => {
			const exportOptions: ExportOptions = {
				sourceFolderIds: folderIds,
				path: targetPath,
				format: format,
				target: FileSystemItem.Directory,
			};

			const targetMustBeFile = format === 'jex';
			const targetIsDir = await shim.fsDriver().isDirectory(targetPath);

			if (targetMustBeFile && targetIsDir) {
				throw new Error(`Format "${format}" can only be exported to a file`);
			}

			if (format === 'jex' || !targetIsDir) {
				exportOptions.target = FileSystemItem.File;
			}

			return InteropService.instance().export(exportOptions);
		},
	};
};

View on GitHub (pinned to 2654b33620)

Solutions

  1. Provide a file path (ending in .jex) as targetPath instead of a directory.
  2. In the UI, ensure the save dialog uses a 'save file' picker (not 'open directory') when format is jex.
  3. Switch format to a per-note format (e.g. md, html) if exporting into a directory is desired.
  4. Validate targetPath with isDirectory() upstream and prompt for a filename.

Example fix

// before
if (targetMustBeFile && targetIsDir) {
  throw new Error(`Format "${format}" can only be exported to a file`);
}

// after — name the specific format and what was given
if (targetMustBeFile && targetIsDir) {
  throw new Error(`Format "${format}" requires a file path, but "${targetPath}" is a directory.`);
}
Defensive patterns

Strategy: validation

Validate before calling

const targetMustBeFile = format === 'jex';
const targetIsDir = await shim.fsDriver().isDirectory(targetPath);
if (targetMustBeFile && targetIsDir) {
  // prompt user for a .jex file path instead
  return;
}
await CommandService.instance().execute('exportFolders', folderIds, format, targetPath);

Type guard

function isValidExportTarget(format: string, isDir: boolean): boolean {
  if (format === 'jex' && isDir) return false;
  return true;
}

Try / catch

try {
  await exportRuntime.execute(ctx, folderIds, format, targetPath);
} catch (e) {
  if (e.message.includes('can only be exported to a file')) {
    // re-prompt for a file path
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the exportFolders command with format='jex' and a targetPath that shim.fsDriver().isDirectory() reports as a directory. The targetMustBeFile flag is derived from format==='jex'.

Common situations: User selects an existing folder in the save dialog for a JEX export; the default path points at a directory; a script passes a directory path when expecting a .jex file path.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/77c7b978ee0a8544. Report an issue: GitHub.