Extending Upload to have nested folders #1961
Replies: 24 comments 31 replies
-
Yes this would indeed be very nice feature to have. I'm in need of oraganizing the files to folders scoped to collection item. So when a Page is deleted I could just iterate the filenames in folder |
Beta Was this translation helpful? Give feedback.
-
Would also really appreciate is this became a feature, right now I have way too many media files which I'm not able to view in an organised manner. |
Beta Was this translation helpful? Give feedback.
-
Looking for this feature |
Beta Was this translation helpful? Give feedback.
-
Has anybody found a workaround for this feature? Would be nice to have for sure. |
Beta Was this translation helpful? Give feedback.
-
Yeah, Only way to do it to give up and try to save all your files into S3. You can freely define your path as you want. I also have so so many images, saving all of them into ONE media folder is too silly and not logically organised as well. I've spent a whole day to look into it, but failed. |
Beta Was this translation helpful? Give feedback.
-
Any progress on this feature? This should be a priority 1 feature. |
Beta Was this translation helpful? Give feedback.
-
I create a work-around...
if (!fs.existsSync(mediaDir + '/' + catId)) { |
Beta Was this translation helpful? Give feedback.
-
so, I've been searching for a while how can I do it natively, nothing yet? |
Beta Was this translation helpful? Give feedback.
-
It'd be great to have this feature supported natively |
Beta Was this translation helpful? Give feedback.
-
I think this might overlap with: #5664 |
Beta Was this translation helpful? Give feedback.
-
Update on this? |
Beta Was this translation helpful? Give feedback.
-
I switched back to Strapi because of this issue. In my case, I have million of images (thumbnail, medium, large...), it is really slow if putting a large number of images in a single directory. |
Beta Was this translation helpful? Give feedback.
-
I developed 2 applications on Payload but this was the reason that I switched to Strapi. |
Beta Was this translation helpful? Give feedback.
-
@jmikrut, would really appreciate if this was launched soon |
Beta Was this translation helpful? Give feedback.
-
any updates of this ? |
Beta Was this translation helpful? Give feedback.
-
I'm sad this hasn't been addressed yet |
Beta Was this translation helpful? Give feedback.
-
I also tried to find a solution to this problem because I was interested in using this product. However, despite my searches, I have not been able to find an easy way to solve it. After studying the source code, I was disappointed and realized that a quick solution was not possible. However, I can offer at least some kind of solution that is not ideal, but is capable of completing the task. Perhaps in the future I will be able to improve it or even create a plugin. SourcesCreate collections with hooksImportant: the collection is called 'Images' ! export const Images: CollectionConfig = {
slug: 'images',
access: {
read: () => true,
},
fields: [
{
name: 'alt',
type: 'text',
required: true,
},
],
hooks: {
beforeOperation: [
({ req, operation }) => {
if ((operation === 'create' || operation === 'update') && req.file) {
if (req.file) {
const file = req.file as unknown as { md5: string }
const hash = file.md5
req.file.name = `${hash.slice(0, 2)}_${hash.slice(2, 4)}_${req.file.name}`
console.log(req.file)
}
}
},
],
afterChange: [
({ collection, doc }) => {
function move(dir: string, filename: string) {
const oldPath = path.resolve(dir, filename)
const matches = [...filename.matchAll(/^([^_]*)_([^_]*)_(.*)$/gm)][0]
const newFolder = path.resolve(dir, matches[1], matches[2])
const newPath = path.resolve(newFolder, matches[3])
mkdirSync(newFolder, {
recursive: true,
})
renameSync(oldPath, newPath)
}
if (collection.upload.staticDir) {
move(collection.upload.staticDir, doc.filename)
const sizes = doc.sizes as Record<string, { filename: string }>
Object.values(sizes).forEach((item) => {
move(collection.upload.staticDir ?? '', item.filename)
})
}
},
],
afterDelete: [
({ doc, collection }) => {
function del(dir: string, filename: string) {
const matches = [...filename.matchAll(/^([^_]*)_([^_]*)_(.*)$/gm)][0]
const newFolder = path.resolve(dir, matches[1], matches[2])
rmSync(path.resolve(newFolder, matches[3]), { force: true })
}
if (collection.upload.staticDir) {
del(collection.upload.staticDir, doc.filename)
const sizes = doc.sizes as Record<string, { filename: string }>
Object.values(sizes).forEach((item) => {
del(collection.upload.staticDir ?? '', item.filename)
})
}
},
],
},
upload: {
staticDir: uploadImagesFolder,
adminThumbnail: '384',
mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
formatOptions: {
format: 'webp',
options: { quality: 85 },
},
imageSizes: [
...
],
},
} Create custom routecreate file in app/api/{images - collection name}/file/[slug]/route.ts import { uploadImagesFolder } from '@/collections/Images'
import { stat, readFile } from 'fs/promises'
import path from 'path'
function changePath(dir: string, filename: string) {
const matches = [...filename.matchAll(/^([^_]*)_([^_]*)_(.*)$/gm)][0]
const newPath = path.resolve(dir, matches[1], matches[2], matches[3])
return newPath
}
export async function GET(_: Request, { params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug
const filePath = changePath(uploadImagesFolder, slug)
const stats = await stat(filePath)
const headers = new Headers()
headers.set('Content-Type', 'image/webp')
headers.set('Content-Length', stats.size + '')
const data = await readFile(filePath)
return new Response(data, {
headers,
status: 200,
})
} |
Beta Was this translation helpful? Give feedback.
-
Why is there a screenshot of the landing page showing a Folder functionality for the Media if this is no where available ? |
Beta Was this translation helpful? Give feedback.
-
I found a decently simple work around that works for me using the S3 cloud storage plugin with cloudflare R2. I gave the The import type { CollectionConfig } from 'payload';
import slugify from 'slugify';
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticDir: 'media',
mimeTypes: ['image/*'],
},
fields: [
{
name: 'project',
type: 'relationship',
relationTo: 'projects', // Each item is associated with a project
required: true, // Means every media item must be associated with a project
hasMany: false, // It can only belong to 1 project
},
],
hooks: {
beforeOperation: [
async ({ req, operation }) => {
if (operation === 'create' && req.data) {
// get the project ID from the request data
const projectId = req.data.project;
if (!projectId) return;
// find the project by ID
const project = await req.payload.findByID({
collection: 'projects',
id: projectId,
});
if (project && project.Title) {
const projectSlug = slugify(project.Title, { lower: true, strict: true, replacement: '_' });
// set the prefix to the project slug, so the file gets saved in a sub directory inside the media folder
req.data.prefix = `media/${projectSlug}`;
console.log(`Media will be stored in: ${req.data.prefix}`);
}
}
},
],
},
}; import type { CollectionConfig } from 'payload'
export const Projects: CollectionConfig = {
slug: 'projects',
fields: [
{
name: 'Title',
type: 'text',
required: true,
},
{
name: 'mediaItems',
type: 'join', // join is a type of field that allows you to connect two collections
collection: 'media', // joining with the media collection
on: 'project', // the field in the media collection that we are joining on
},
],
admin: {
useAsTitle: 'Title',
},
}; |
Beta Was this translation helpful? Give feedback.
-
Having all uploads stored flat in a single folder it's ok just for toy websites, not for any real production website. I don't understand why they don't care about this feature, maybe they think all production site should host assets on cloud storage. I've keeping an eye on feat/folders about Folder View in List which is also mentioned in this thread, but I don't think it addresses the problem we are facing with uploads. Besides some custom logic like folders named after collection and so on, it should be default as year/month/filename similar to what WP does. I hope someone from the dev team can tell us if and when this issue will be resolved, then we can decide to move on to something that works best for assets or try to fix this by community efforts, like @fidhal-ht suggested by building a plugin |
Beta Was this translation helpful? Give feedback.
-
Closing as duplicated of #4190 |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Does anyone know how to sync the folder path with your S3 Provider of choice? I was quite disappointed when I found out everything was being written to the root path of the S3 bucket. The folder feature would've been perfect if it wasn't for this caveat... Is it possible to read the folder hierarchy via the Payload API? You can probably use an on change hook to read & adjust the S3 paths based on the folder structure within Payload. |
Beta Was this translation helpful? Give feedback.
-
Hi guys, I just want to organize my assets like this in S3
I tried to change the prefix inside beforeOperation hook:
but this will upload the texture in the "texture" folder and in How can I fix this? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I have a media collection which I use to save all media files. I want to organize the files into subfolders. Currently, all files are saved in folder: media
How can I save files from collection-1 to media/collection-1 when using media relationship.
Strapi provides media gallery out of the box in which you can create folders to structure your media gallery such as media/products or media/posts. Maybe we can define the folder inside the relationship such as below.
{ name: 'products', type: 'upload', relationTo: 'media', DESTINATION_FOLDER: '../../media/products' // Example },
Beta Was this translation helpful? Give feedback.
All reactions