This error occurs when attempting to upload a file or access an object in Supabase Storage with an invalid file path. Common causes include special characters in the file name, empty path strings, non-ASCII characters that get URL-encoded, or structural issues in the path format.
The "Invalid key" error is thrown by Supabase Storage API when the file path or key you're using doesn't conform to S3 safe character requirements. Supabase Storage validates file paths using a regex pattern that enforces AWS S3 object key naming rules. The validation function checks if your key contains only allowed characters—most special characters like `%`, `#`, `?`, and some encoded sequences are rejected. This is different from an authentication or API key error; it's specifically about the file path you're trying to upload or access. Understanding what constitutes a valid file path is essential for working with Supabase Storage reliably.
Supabase Storage expects relative paths without leading slashes. Check your upload code and remove any leading / character:
// ❌ Wrong - leading slash
const { data, error } = await supabase.storage
.from('bucket')
.upload('/path/to/file.png', file);
// ✅ Correct - no leading slash
const { data, error } = await supabase.storage
.from('bucket')
.upload('path/to/file.png', file);
if (error) {
console.error('Upload error:', error.message);
}Paths should always be relative to the bucket root, not absolute file system paths.
Ensure you're passing a non-empty string as the file path. Empty strings will trigger "Invalid key: " error:
// ❌ Wrong - empty path
const filePath = '';
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file);
// Error: Invalid key:
// ✅ Correct - provide a valid file path
const filePath = 'documents/my-file.pdf';
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file);
if (error) {
console.error('Error:', error.message);
}Add validation to ensure the path is always defined before uploading.
The following characters are not allowed in Supabase Storage paths and must be removed or replaced:
- % (percent signs from URL encoding)
- # (hash/fragment)
- ? (question mark/query)
- Control characters (ASCII 0-31)
- Certain high Unicode characters that get encoded
// ❌ Wrong - contains special characters
const fileName = 'file#1.png'; // Contains #
const fileName = '100%done.pdf'; // Contains %
const fileName = 'what?.docx'; // Contains ?
// ✅ Correct - safe characters only
const fileName = 'file-1.png'; // Replace # with -
const fileName = '100-done.pdf'; // Replace % with -
const fileName = 'what.docx'; // Remove ?
// Safe function to sanitize file names
function sanitizeFileName(fileName: string): string {
return fileName
.replace(/[#%?]/g, '-') // Replace invalid chars with dash
.replace(/\s+/g, '_') // Replace spaces with underscore
.replace(/[^a-zA-Z0-9._\-/]/g, ''); // Remove other unsafe chars
}
const safePath = sanitizeFileName(originalFileName);
const { data, error } = await supabase.storage
.from('bucket')
.upload(safePath, file);Always sanitize user-provided file names before uploading.
File names containing non-ASCII characters (Chinese, Japanese, emoji, accents) get URL-encoded by the browser and can result in invalid key errors. The best approach is to use a UUID or sanitized name:
import { v4 as uuid } from 'uuid';
// ❌ Problematic - non-ASCII characters
const fileName = '文件.pdf'; // Chinese characters
const filePath = `uploads/${fileName}`;
// Gets encoded to %E6%96%87%E4%BB%B6.pdf which may fail
// ✅ Best approach - use UUID
const fileExtension = originalFile.name.split('.').pop();
const safeFileName = `${uuid()}.${fileExtension}`;
const filePath = `uploads/${safeFileName}`;
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file);
if (error) {
console.error('Upload error:', error.message);
}
// Alternative - sanitize and preserve original name in metadata
function sanitizeFileName(fileName: string): string {
return fileName
.replace(/[^a-zA-Z0-9._-]/g, '_') // Replace non-ASCII with underscore
.toLowerCase()
.substring(0, 255); // Limit length
}
const originalName = originalFile.name;
const safeName = sanitizeFileName(originalName);
const filePath = `uploads/${safeName}`;
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file, {
metadata: {
originalFileName: originalName
}
});Using UUIDs is the most reliable approach for user-uploaded files.
Ensure your file paths don't contain double slashes and are properly formatted:
// ❌ Wrong - double slash
const filePath = 'uploads//my-file.pdf'; // Double slash
const filePath = 'uploads/./file.pdf'; // Unnecessary dot
const filePath = 'uploads/../file.pdf'; // Relative path traversal
// ✅ Correct - clean paths
const filePath = 'uploads/my-file.pdf';
const filePath = 'uploads/subfolder/file.pdf';
// Safe path builder function
function buildFilePath(...segments: string[]): string {
return segments
.filter(s => s && s.length > 0) // Remove empty segments
.join('/')
.replace(/\/+/g, '/') // Remove double slashes
.replace(/^\//, ''); // Remove leading slash
}
const filePath = buildFilePath('uploads', userId, fileName);
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file);Always validate the path structure before sending to the API.
S3 object keys have a maximum length of 1024 bytes. Create a validation function:
function validateFilePath(filePath: string): { valid: boolean; error?: string } {
// Check not empty
if (!filePath || filePath.trim().length === 0) {
return { valid: false, error: 'File path cannot be empty' };
}
// Check not too long
if (filePath.length > 1024) {
return { valid: false, error: 'File path exceeds 1024 bytes' };
}
// Check no leading slash
if (filePath.startsWith('/')) {
return { valid: false, error: 'File path cannot start with /' };
}
// Check no double slashes
if (filePath.includes('//')) {
return { valid: false, error: 'File path cannot contain //' };
}
// Check for invalid characters
const invalidChars = ['%', '#', '?'];
for (const char of invalidChars) {
if (filePath.includes(char)) {
return { valid: false, error: `File path cannot contain '${char}' character` };
}
}
return { valid: true };
}
// Usage
const validation = validateFilePath(filePath);
if (!validation.valid) {
console.error('Invalid file path:', validation.error);
return;
}
const { data, error } = await supabase.storage
.from('bucket')
.upload(filePath, file);Validating before attempting upload saves time and provides better error messages.
While "Invalid key" typically refers to file path issues, also verify your Supabase client and bucket name are correct:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// Verify bucket exists and you have access
const { data: buckets, error: listError } = await supabase.storage.listBuckets();
if (listError) {
console.error('Cannot list buckets:', listError.message);
} else {
const bucketNames = buckets.map(b => b.name);
console.log('Available buckets:', bucketNames);
if (!bucketNames.includes('your-bucket')) {
console.error('Bucket "your-bucket" not found');
}
}
// Then attempt upload
const { data, error } = await supabase.storage
.from('your-bucket')
.upload('valid/path/file.png', file);
if (error) {
console.error('Upload error:', error.message);
}Ensure your Supabase URL and key are correctly set from environment variables.
S3 Object Key Naming Rules: Supabase Storage strictly enforces AWS S3 object key naming conventions. Keys can contain 1-1024 bytes and can only use characters from the set: a-z, A-Z, 0-9, !, -, _, ., *, ', (, ), and /. Any other characters, including percent-encoded sequences like %20, will cause validation to fail.
URL Encoding Confusion: A common issue occurs when developers URL-encode file names thinking it will help. For example, a space becomes %20, but Supabase Storage API rejects the % character itself. Always pass clean, unencoded paths to the Supabase SDK—the SDK handles encoding if needed.
Non-ASCII Character Handling: JavaScript strings can contain any Unicode character, but when you pass them to Supabase Storage, they get URL-encoded for HTTP transmission. Supabase then validates the encoded string, which may contain % characters that violate the key rules. This is why using UUIDs or sanitized ASCII-only names is the most reliable approach.
Client Library Versions: Older versions of the Supabase JavaScript client may have different error messages or validation behavior. Ensure you're using a recent version: npm update @supabase/supabase-js.
Error Message Format: The API sometimes returns "Invalid key: " with a trailing space when the path is completely empty. If you see this exact message, check if your path variable is an empty string or null.
Self-Hosted Storage Validation: If running self-hosted Supabase with the storage service, the validation rules may differ slightly between versions. Check your storage service logs for more detailed error information: docker logs supabase-storage.
Recovery: Once you receive an "Invalid key" error, the request has already been rejected before attempting to write to S3. There's no partial upload or cleanup needed—simply fix the path and retry.
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists