Advanced
Custom Path Resolver
Organize your uploaded files with custom path patterns
Custom Path Resolver
The path resolver determines where files are stored in your S3 bucket. By default, BucketKit uses a simple pattern, but you can customize it to match your needs.
Default Behavior
Without customization, files are stored as:
uploads/{uniqueId}-{sanitizedFileName}
# Example: uploads/lxq8mz4k-profile.jpg
# With userId:
uploads/{userId}/{uniqueId}-{sanitizedFileName}
# Example: uploads/user123/lxq8mz4k-profile.jpgCustom Path Resolver
import { createBucketKit, type PathResolverParams } from '@nilovon/bucketkit-core';
function customPathResolver(params: PathResolverParams): string {
const { fileName, userId, contentType, size } = params;
// Your custom logic here
return `custom/path/${fileName}`;
}
const bucketKit = createBucketKit({
provider: 'aws-s3',
region: 'us-east-1',
bucket: 'my-uploads',
defaultUploadPolicy: {
maxSize: 10 * 1024 * 1024,
pathResolver: customPathResolver,
},
});PathResolverParams
interface PathResolverParams {
fileName: string; // Original filename
userId?: string; // User ID (if provided)
contentType: string; // MIME type
size: number; // File size in bytes
}Examples
Organize by Date
function dateBasedResolver({ fileName, userId }: PathResolverParams): string {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const uniqueId = Date.now().toString(36);
if (userId) {
return `users/${userId}/${year}/${month}/${day}/${uniqueId}-${fileName}`;
}
return `public/${year}/${month}/${day}/${uniqueId}-${fileName}`;
}
// Result: users/abc123/2024/03/15/lxq8m-photo.jpgOrganize by Content Type
function typeBasedResolver({ fileName, contentType }: PathResolverParams): string {
const uniqueId = Date.now().toString(36);
if (contentType.startsWith('image/')) {
return `images/${uniqueId}-${fileName}`;
}
if (contentType.startsWith('video/')) {
return `videos/${uniqueId}-${fileName}`;
}
if (contentType === 'application/pdf') {
return `documents/pdf/${uniqueId}-${fileName}`;
}
return `files/${uniqueId}-${fileName}`;
}
// Result: images/lxq8m-photo.jpg
// documents/pdf/lxq8m-report.pdfTenant-Based (Multi-tenant Apps)
function tenantBasedResolver({ fileName, userId }: PathResolverParams): string {
// Assuming userId format: "tenant_user" (e.g., "acme_john")
const [tenant, user] = userId?.split('_') || ['public', 'anonymous'];
const uniqueId = Date.now().toString(36);
return `tenants/${tenant}/users/${user}/${uniqueId}-${fileName}`;
}
// Result: tenants/acme/users/john/lxq8m-photo.jpgHash-Based (Even Distribution)
import crypto from 'crypto';
function hashBasedResolver({ fileName }: PathResolverParams): string {
const hash = crypto.createHash('md5').update(fileName + Date.now()).digest('hex');
const prefix = hash.substring(0, 2); // First 2 chars for distribution
return `files/${prefix}/${hash}-${fileName}`;
}
// Result: files/a7/a7b3c4d5e6f7...-photo.jpgPrivate vs Public
function accessBasedResolver(
params: PathResolverParams,
isPrivate: boolean
): string {
const { fileName, userId } = params;
const uniqueId = Date.now().toString(36);
if (isPrivate && userId) {
return `private/${userId}/${uniqueId}-${fileName}`;
}
return `public/${uniqueId}-${fileName}`;
}
// Use with policyOverrides
const result = await bucketKit.createPresignedUpload({
fileName: 'secret.pdf',
contentType: 'application/pdf',
size: 1024,
userId: 'user123',
policyOverrides: {
pathResolver: (params) => accessBasedResolver(params, true),
},
});Best Practices
- Always include a unique identifier to prevent overwrites
- Sanitize filenames - remove special characters
- Keep paths reasonably short - S3 has a 1024 character limit
- Use forward slashes - S3 uses
/as delimiter - Avoid special characters - stick to alphanumeric,
-,_,/
Using with Per-Request Overrides
// Default resolver for most uploads
const bucketKit = createBucketKit({
provider: 'aws-s3',
region: 'us-east-1',
bucket: 'my-uploads',
defaultUploadPolicy: {
pathResolver: defaultResolver,
},
});
// Override for specific upload
const result = await bucketKit.createPresignedUpload({
fileName: 'avatar.jpg',
contentType: 'image/jpeg',
size: 50000,
policyOverrides: {
pathResolver: ({ userId }) => `avatars/${userId}/current.jpg`,
},
});