laurent22/joplin · error · Error

${screenshotType} is not a valid screenshot type. Valid type

Error message

${screenshotType} is not a valid screenshot type. Valid types are: 
${allPossibleScreenshotsType}

What it means

Thrown by validateScreenshots() after extracting the file extension via `screenshot.src.split('.').pop()`. The extension must be in allPossibleScreenshotsType. Only local files reach this check — https:// and http:// src values `continue` early and bypass it.

Source

Thrown at packages/generator-joplin/generators/app/templates/webpack.config.js:111

	if ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');
	// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
	categories.forEach(category => {
		if (!allPossibleCategories.map(category => { return category.name; }).includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are: \n${allPossibleCategories.map(category => { return category.name; })}\n`);
	});
}

function validateScreenshots(screenshots) {
	if (!screenshots) return null;
	for (const screenshot of screenshots) {
		if (!screenshot.src) throw new Error('You must specify a src for each screenshot');

		// Avoid attempting to download and verify URL screenshots.
		if (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {
			continue;
		}

		const screenshotType = screenshot.src.split('.').pop();
		if (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \n${allPossibleScreenshotsType}\n`);

		const screenshotPath = path.resolve(rootDir, screenshot.src);

		// Max file size is 1MB
		const fileMaxSize = 1024;
		const fileSize = fs.statSync(screenshotPath).size / 1024;
		if (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);
	}
}

function readManifest(manifestPath) {
	const content = fs.readFileSync(manifestPath, 'utf8');
	const output = JSON.parse(content);
	if (!output.id) throw new Error(`Manifest plugin ID is not set in ${manifestPath}`);
	validateCategories(output.categories);
	validateScreenshots(output.screenshots);
	return output;
}

View on GitHub (pinned to 2654b33620)

Solutions

  1. Convert the screenshot to a supported type — the valid list is printed in the error message.
  2. Rename the file so its extension is lowercase and matches an entry in allPossibleScreenshotsType.
  3. If the file genuinely has no extension, add the correct one.
  4. Re-run the build.

Example fix

// before
"src": "screenshots/hero.BMP"
// after (convert + rename)
"src": "screenshots/hero.png"
Defensive patterns

Strategy: validation

Validate before calling

const supported = new Set(allPossibleScreenshotsType); // exported list
const bad = (manifest.screenshots || [])
  .filter(s => s.src && !/^https?:\/\//.test(s.src))
  .filter(s => !supported.has(s.src.split('.').pop().toLowerCase()));
if (bad.length) throw new Error(`Unsupported screenshot type for: ${bad.map(s => s.src).join(', ')}`);

Type guard

const isSupportedScreenshotType = (src, supported) => /^https?:\/\//.test(src) || supported.has(src.split('.').pop().toLowerCase());

Try / catch

try { validateScreenshots(manifest.screenshots); }
catch (e) { if (/not a valid screenshot type/.test(e.message)) { /* convert or replace */ } else throw e; }

Prevention

When it happens

Trigger: A local screenshot src has an extension that is not in allPossibleScreenshotsType (e.g. .bmp, .tiff, .webp if unsupported), or src has no dot so split('.').pop() returns the whole filename.

Common situations: Author exports a screenshot in an uncommon format; file has no extension; case mismatch where the list expects lowercase but the file uses uppercase (.PNG).

Related errors


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