Project ID Rules and Validation
Status: Stable
Valid Project IDs
Project IDs must follow these rules:
✅ Format: [a-z0-9]+
- Only lowercase letters and numbers
- No uppercase letters
- No special characters (hyphens, underscores, etc.)
- At least one character
The restriction is not cosmetic. An environment variable addresses a project by its id, uppercased, inside a name built with double underscores — DESIGN_TESTS__PROJECTS__BFH__URL. A hyphen makes that name invalid as a shell identifier, so DESIGN_TESTS__PROJECTS__KUS-CONTRACTS__URL=... is rejected by the shell before the library ever sees it. Other ways of setting a variable accept the name — env, a Docker -e flag, a Jenkins withEnv block, a Kubernetes env entry — and the override then works, so what a hyphen costs is the one form people actually type.
Underscores are excluded for a different mechanism. The name splits on __ only, so a single underscore creates no segment; each segment is then camel-cased, and DESIGN_TESTS__PROJECTS__MY_PROJECT__URL addresses a project called myProject. An id carrying an underscore is unreachable under its own name.
A consumer migrating an existing suite renames its project ids to satisfy this. Renaming moves baselines: the default path is <baselineDir>/<projectKey>/<title>/<name>-<viewport>.png, so every baseline of a project that sets no snapshotsDirFn moves with the id. A project that sets one gets exactly what that function returns, and renames for free only where the returned path does not name the key.
✅ Examples of Valid IDs
webapp
api
mobile1
project2025
bfh
hkb
alumni
❌ Examples of Invalid IDs
Web-App # Contains uppercase and hyphen
api_v2 # Contains underscore
Mobile! # Contains uppercase and special char
project-2025 # Contains hyphen
all # Reserved keyword
Reserved Keywords
"all" is Reserved
The project ID "all" is reserved for the --all CLI flag and cannot be used as a project ID.
Why? To avoid ambiguity:
# With --all flag: run ALL projects
design-tests run --all
# With --project flag: run specific project
design-tests run --project webapp
# If "all" were allowed as a project ID, this would be confusing:
design-tests run --project all # Run project named "all" or all projects?
Error message:
Project ID cannot be "all" - this is a reserved keyword for the --all flag.
Please rename your project to something else (e.g., "allprojects", "main", etc.)
Validation Timing
Project IDs are validated when the configuration is loaded:
// .designTests.js
module.exports = {
projects: {
// ✓ Valid - lowercase alphanumeric
webapp: { ... },
api: { ... },
// ✗ Invalid - will throw error on load
"Web-App": { ... }, // uppercase + hyphen
"api_v2": { ... }, // underscore
"all": { ... }, // reserved keyword
}
};
When validation happens:
- Config file is loaded
- Zod schema validates structure
- Normalization sets missing keys
- Project ID validation runs ← Checks format and reserved words
- Returns validated config
Error Messages
Invalid Format
❌ Error: Invalid project ID: "Web-App"
Project IDs must contain only lowercase letters and numbers [a-z0-9].
Examples:
✓ Valid: "webapp", "api", "mobile1", "project2025"
✗ Invalid: "Web-App", "api_v2", "Mobile!", "project-2025"
To fix: Rename the project to use only lowercase letters and numbers.
Reserved Keyword
❌ Error: Project ID cannot be "all" - this is a reserved keyword for the --all flag.
Please rename your project to something else (e.g., "allprojects", "main", etc.)
Type Safety (Branded Type)
Project IDs use a branded type ProjectId for compile-time safety:
import { type ProjectId, toProjectId } from '@qs/design-tests/config';
// Validate and create branded type
const projectId: ProjectId = toProjectId('webapp'); // ✓ OK
const invalidId: ProjectId = toProjectId('Web-App'); // ✗ Throws error
// String is not assignable to ProjectId
const unsafeId: ProjectId = 'webapp'; // ✗ Type error
This ensures project IDs are validated before use.
Migration Guide
If you have existing projects with invalid IDs:
Before
module.exports = {
projects: {
"Web-App": { url: "..." },
"api_v2": { url: "..." },
"mobile-app": { url: "..." },
}
};
After
module.exports = {
projects: {
webapp: { url: "..." }, // lowercase, no hyphen
apiv2: { url: "..." }, // no underscore
mobileapp: { url: "..." }, // no hyphen
}
};
Note: This will change baseline directory names. You may need to rename directories:
mv design-tests/baseline-images/Web-App design-tests/baseline-images/webapp
mv design-tests/baseline-images/api_v2 design-tests/baseline-images/apiv2
mv design-tests/baseline-images/mobile-app design-tests/baseline-images/mobileapp
Or just delete and recreate baselines:
rm -rf design-tests/baseline-images/
npm run test # Auto-creates new baselines with correct names
CLI Flag Behavior
--project Flag
Processes only the specified project (as if other projects don't exist):
# Only tests the "webapp" project
design-tests run --project webapp
# Baseline creation works per-project
rm -rf design-tests/baseline-images/webapp/
design-tests run --project webapp # Creates baseline for webapp only
--all Flag
Processes all projects in the configuration:
# Tests all projects defined in config
design-tests run --all
# Baseline creation works per-project
# - Projects with baselines: compare mode
# - Projects without baselines: create mode
design-tests run --all
Mixed Mode Example
// Config has 3 projects
{
projects: {
webapp: { ... },
api: { ... },
mobile: { ... }
}
}
Baseline state:
design-tests/baseline-images/
├── webapp/ # Has baseline (10 images)
└── api/ # Has baseline (5 images)
# mobile/ missing - no baseline yet
Running --all:
design-tests run --all
# Output:
🔍 Project "webapp": Comparing against baseline
🔍 Project "api": Comparing against baseline
📝 Project "mobile": Creating baseline (no existing baseline found)
📊 Test Results: 0 visual differences, 8 new baselines detected
Result:
webapp: compared against existing baselineapi: compared against existing baselinemobile: new baseline created- Report shows mixed results: 0 diffs, 8 new items
See Also
- Getting Started - First-time setup
- Adding Projects - Add new projects
- Baseline Management - Recreate baselines