# How do you guys manage profile photos / files?

**URL:** <https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858>\
**Category:** Client SDKs\
**Tags:** files\
**Created:** [July 29, 2021, 8:58am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858 "2021-07-29T08:58:42Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![jayson](https://community.parseplatform.org/user_avatar/community.parseplatform.org/jayson/32/679_2.png) [@jayson](https://community.parseplatform.org/u/jayson)\
**Post date:** [July 29, 2021, 8:58am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/1 "2021-07-29T08:58:43Z")

</div>

So I’m working on user profile photos and uploading them to my database.

However, I do wonder how do you guys manage the profile photos? As users change their photos, what do you do with the old ones? Do you have it programmatically deleted before saving a new one?

What happens to “orphaned” photos / Files? I can’t seem to find any documentation on how to manage these files via dashboard.

I do use a hosted backend ([http://sashido.io](http://sashido.io)) if that helps.

---

<div class="post-metadata">

**Author:** ![Manuel](https://community.parseplatform.org/user_avatar/community.parseplatform.org/manuel/32/186_2.png) [@Manuel](https://community.parseplatform.org/u/Manuel)\
**Post date:** [July 29, 2021, 2:23pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/2 "2021-07-29T14:23:02Z")

</div>

Cleaning up files without reference can be challenging, because a file can be referenced by more than one document. If you know that a file will only have one reference, you can delete the file using a Cloud Trigger, otherwise you’d probably need to implement a custom reference counter.

Currently, a developer has to implement their own custom solution, depending on their use case.

There have been attempts to address this, but none of them have resulted in a feature. The concept to address this is more or less clear, but someone would have to pick up the code and continue where others have left off.

See

- ["Clean Up Files" Feature · Issue #1023 · parse-community/parse-server · GitHub](https://github.com/parse-community/parse-server/issues/1023)
- [FR: ACL for Parse.File · Issue #7001 · parse-community/parse-server · GitHub](https://github.com/parse-community/parse-server/issues/7001)

---

<div class="post-metadata">

**Author:** ![uzaysan](https://community.parseplatform.org/user_avatar/community.parseplatform.org/uzaysan/32/59_2.png) [@uzaysan](https://community.parseplatform.org/u/uzaysan)\
**Post date:** [July 30, 2021, 11:54am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/3 "2021-07-30T11:54:51Z")

</div>

I was checking the User object on the afterSave trigger. And if profile\_photo field has changed I delete the file. For example:

```auto
Parse.Cloud.afterSave('_User', ({original, object}) => {
  if (!original) return; //New user
  if (original.get('profile_photo').name() !== object.get('profile_photo').name()) {
    //Profile photo has changed.
    original.get('profile_photo').destroy({useMasterKey: true});
  }
});

```

---

<div class="post-metadata">

**Author:** ![lsmilek1](https://community.parseplatform.org/user_avatar/community.parseplatform.org/lsmilek1/32/629_2.png) [@lsmilek1](https://community.parseplatform.org/u/lsmilek1)\
**Post date:** [July 30, 2021, 3:32pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/4 "2021-07-30T15:32:01Z")

</div>

I my case I use ParseSwift cloud function to save file:

```auto
struct CloudSaveFile: ParseCloud {
    
    //Return type of your Cloud Function
    typealias ReturnType = String
    //These are required for Object
    var functionJobName: String = CloudFunctionType.saveFile.rawValue
        
    init(name: String, data: [UInt8], ownerId: String) {
        self.name = name
        self.data = data
        self.ownerId = ownerId
    }
    
    var name: String?
    var data: [UInt8]?
    var ownerId: String?
    
}

```

I the picture data are encrypted with a key string equal to profile `objectId` so that the images cannot be viewed via public direct link in the storage (there is the profile `objectId` not known). The cloud function creates a reference in the MongoDB and returns to the client the new image url:

```auto
//File does not have final url and name in beforeFileSave, can't be used to save FileObj
//in afterFileSave I cannot remove metadata and the ownerId should be inaccessible 
//Using cloud function with passing ownerId as a parameter
Parse.Cloud.define("saveFile", async (request) => {
    //check if user is authenticated
    const token = { sessionToken: request.user.getSessionToken() };
    if (token == undefined) {
        throw `could not get valid session token`;
    }
    //save file to recieve url an unique name
    const passedName = request.params.name;
    const bytes = request.params.data;
    const file = new Parse.File(passedName, bytes);
    await file.save({sessionToken: token});
    
    //create FileObj entry with ownerID
    const fileObject = new Parse.Object('FileObj');
    fileObject.set('file', file); 
    fileObject.set('fn', file.name().split('_')[0]); //getting only uid part
    fileObject.set('ow', request.params.ownerId);
    await fileObject.save(null, { useMasterKey: true });

    //returning to the clien, what is the new image url
    return file.url();
});

```

It is then easy to work with object in the database, in this case `FileObj`. For example when it gets deleted it clears the related file from the storage:

```auto
//FileObj gets deleted first and triggers delete in storage
Parse.Cloud.beforeDelete("FileObj", async (request) => {
    const file = request.object.get("file");
    //request.log.info(`deleting file: ${file.name()}`);
    if (file == undefined) {
        return;
    } 
    try {        
        await file.destroy({ useMasterKey: true }); //here should be probably return instead await??
    } catch (e) {
        //TODO: mark somewhere for failed indexing attempts?
        throw `error deleting file in storage. File: ${file.name()}, error: ${e}`;
    }
    return;
});

```

Note to the idea behind encrypting all profile images with the given profile `objectId` as key:

- Users that search for other users have to be already logged in to obtain both the other’s `objectId` and file urls
- The storage direct links are not readable for any search engines as they do not know to what profile objectId the file belongs

I hope it helps. Let me know your thoughts as I myself am learning software development from scratch.

Thanks!

---

<div class="post-metadata">

**Author:** ![jayson](https://community.parseplatform.org/user_avatar/community.parseplatform.org/jayson/32/679_2.png) [@jayson](https://community.parseplatform.org/u/jayson)\
**Post date:** [July 31, 2021, 4:20pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/5 "2021-07-31T16:20:16Z")

</div>

I like this. Simple.

I should have done this and stuck to one profile pic for now.

However, I went with pointer to ImageClass which holds a ParseFile.

This way, profile photos act much like a regular photo and will be there until the user decides to delete it.

Much like how Telegram app keeps a history of all your profile photos until you delete them.

---

<div class="post-metadata">

**Author:** ![Tom](https://community.parseplatform.org/user_avatar/community.parseplatform.org/tom/32/2_2.png) [@Tom](https://community.parseplatform.org/u/Tom)\
**Post date:** [August 5, 2021, 10:16pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/6 "2021-08-05T22:16:32Z")

</div>

Besides the already very helpful responses here I thought I would add that there are some useful step-by-step guides from Sashido and Back4App about deleting files:

> **[How to delete a file](https://blog.sashido.io/how-to-properly-delete-a-file-without-leaving-an-orphan/)**
>
> How to proceed If you wish to delete a simple file from the file storage

> **[Delete Files from a React Native App - Back4app Backend](https://www.back4app.com/docs/react-native/parse-sdk/working-with-files/react-native-delete-files)**
>
> In this guide you'll learn how to delete files in Back4App on your React Native App

---

<div class="post-metadata">

**Author:** ![jayson](https://community.parseplatform.org/user_avatar/community.parseplatform.org/jayson/32/679_2.png) [@jayson](https://community.parseplatform.org/u/jayson)\
**Post date:** [August 6, 2021, 9:25am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/7 "2021-08-06T09:25:19Z")

</div>

nice thanks for the link! Missed that Sashido article. that works.

---

<div class="post-metadata">

**Author:** ![Manuel](https://community.parseplatform.org/user_avatar/community.parseplatform.org/manuel/32/186_2.png) [@Manuel](https://community.parseplatform.org/u/Manuel)\
**Post date:** [August 18, 2021, 11:57am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/8 "2021-08-18T11:57:52Z")

</div>

Would anyone want to add the beforeSave / beforeDelete triggers as workaround to delete files to the Parse docs? We are getting this question over and over again, and it surely could help developers to read this in the docs.

---

<div class="post-metadata">

**Author:** ![lsmilek1](https://community.parseplatform.org/user_avatar/community.parseplatform.org/lsmilek1/32/629_2.png) [@lsmilek1](https://community.parseplatform.org/u/lsmilek1)\
**Post date:** [August 20, 2021, 5:28pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/9 "2021-08-20T17:28:24Z")

</div>

Manuel, please have a look at the [commit](https://github.com/parse-community/docs/commit/d555421aa61f3f051621459b30bf44a981da731f), if it is what you had in your mind?

Thank you!

---

<div class="post-metadata">

**Author:** ![dblythy](https://community.parseplatform.org/user_avatar/community.parseplatform.org/dblythy/32/231_2.png) [@dblythy](https://community.parseplatform.org/u/dblythy)\
**Post date:** [August 20, 2021, 6:32pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/10 "2021-08-20T18:32:57Z")

</div>

Although this is a neat solution, it’s assuming that the file is set to `user.profile_photo`. I would also add:

```auto
Parse.Cloud.beforeSaveFile(async ({file, user}) => {
  user.set('profile_photo', file);
  await user.save(null, {useMasterKey: true})
}, {
  requireUser: true
});

```

Just to be sure that the file is set.

---

<div class="post-metadata">

**Author:** ![dblythy](https://community.parseplatform.org/user_avatar/community.parseplatform.org/dblythy/32/231_2.png) [@dblythy](https://community.parseplatform.org/u/dblythy)\
**Post date:** [August 20, 2021, 6:37pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/11 "2021-08-20T18:37:28Z")

</div>

I believe this is already covered pretty well in the [Parse Cloud docs](https://docs.parseplatform.org/cloudcode/guide/#afterdeletefile)

---

<div class="post-metadata">

**Author:** ![dblythy](https://community.parseplatform.org/user_avatar/community.parseplatform.org/dblythy/32/231_2.png) [@dblythy](https://community.parseplatform.org/u/dblythy)\
**Post date:** [August 20, 2021, 6:52pm UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/12 "2021-08-20T18:52:53Z")

</div>

Some thoughts:

- Make sure you have CLPs, ACLs or a beforeSave set on `FileObj` class. Otherwise any user can query any FileObj and send a `destroy` command. This could allow malicious users to delete all your files.
- You can use a [Parse Cloud validator](https://docs.parseplatform.org/cloudcode/guide/#implementing-cloud-function-validation) with `requireUser: true` instead of these lines:

```auto
const token = { sessionToken: request.user.getSessionToken() };
if (token == undefined) {
  throw `could not get valid session token`;
}

```

or:

```auto
if (!request.user) {
  throw 'Please login to call this function';
}

```

- For this next section, it would make more logical sense to use a Parse Cloud File trigger (this is what they were made for 😊) :

```auto
const passedName = request.params.name;
const bytes = request.params.data;
const file = new Parse.File(passedName, bytes);
await file.save({sessionToken: token});

```

Also I would recommend looking into Pointers and ACLs 😊

A simpler way would be:

```auto
Parse.Cloud.afterSaveFile(async ({file, user}) => {
  const fileObject = new Parse.Object('FileObj');
  fileObject.set('file', file);
  fileObject.set('fn', file.name().split('_')[0]);
  fileObject.set('ow', user);
  await fileObject.save(null, { useMasterKey: true );
});

```

```auto
Parse.Cloud.beforeDeleteFile(async ({file, user}) => {
  const query = new Parse.Query('FileObject');
  query.equalTo('fn', file.name().split('_')[0]));
  query.equalTo('ow, user);
  const fileObject = await query.first({ useMasterKey: true });
  if (!fileObject) {
    throw 'You do not have permission to delete this file';
  }
  fileObject.destroy({useMasterKey: true});
});

```

Actually i’ve just noted the reason you didn’t use file triggers was:

```auto
//in afterFileSave I cannot remove metadata and the ownerId should be inaccessible 

```

Is this a Parse Server bug?

---

<div class="post-metadata">

**Author:** ![lsmilek1](https://community.parseplatform.org/user_avatar/community.parseplatform.org/lsmilek1/32/629_2.png) [@lsmilek1](https://community.parseplatform.org/u/lsmilek1)\
**Post date:** [August 21, 2021, 8:03am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/13 "2021-08-21T08:03:20Z")

</div>

Thank you for a great comment!

1. I completely missed that CLPs, ACLs security issue. That is a valid point you made!
2. I was running Parse 4.2 at the time of making this functions as back4app had 4.4 still marked as beta. Now they bumped it to 4.5 as beta so I will give it a try. Just out curiosity, is there any performance difference between Parse Cloud validator and checking `if (!request.user) { }`?
3. The reason why I did not use Parse Cloud File trigger is are rather silly two.

- First, I would need to fetch the file after save to receive a new URL (ParseSwift SDK)
- In my case I have ownerId an `objectId` of a `Profile` object and not `User`. My initial plan was to user custom `objectId` for `Profile` to match the `objectId` with `User`, but ParseSwift SDKL does not support a mixed environment of generated/custom objectId what did prevent me from saving other objects with generated id. As I also realised now, the same method is used for `Group` and `Event` photos, where owner id is not an `User` but any other object.

> [@dblythy](#):
>
> I believe this is already covered pretty well in the [Parse Cloud docs](https://docs.parseplatform.org/cloudcode/guide/#afterdeletefile)

As you mentioned, my example got inspired by the linked documentation, although I obviously did not get everything right. So I am not sure if my [commit](https://github.com/parse-community/docs/commit/d555421aa61f3f051621459b30bf44a981da731f) is good contribution.

---

<div class="post-metadata">

**Author:** ![dblythy](https://community.parseplatform.org/user_avatar/community.parseplatform.org/dblythy/32/231_2.png) [@dblythy](https://community.parseplatform.org/u/dblythy)\
**Post date:** [August 21, 2021, 8:55am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/14 "2021-08-21T08:55:29Z")

</div>

Before moving to production, I would recommend trying to test some weak-points in your server design, such as querying objects when logged out, deleting objects when logged out, deleting other users’ objects, etc.

The Parse Cloud validator does `if (!request.user)` internally. It’s just a convenience tool to simplify your cloud code 😊.

I would do something such as:

```auto
Parse.Cloud.afterSave(Parse.User, async ({ object, master }) => {
    if (master) {
        return;
    }
    if (!object.existed()) {
        const profile = new Parse.Object('Profile');
        const acl = new Parse.ACL(object);
        acl.setPublicReadAccess(true);
        profile.setACL(acl);
        await profile.save(null, {useMasterKey: true});
        object.set('profile', profile);
        await object.save(null, {useMasterKey: true});
    }
});
Parse.Cloud.beforeSave(Parse.User, ({object, master, original = new Parse.Object()}) => {
    if (master) {
        return;
    }
    if (object.get('profile') !== original.get('profile')) {
        // prevents user from unsetting or changing the profile pointer
        object.set('profile', original.get('profile');
    }
});

```

Then on frontend, use the pointer of `current.profile` to update a users’ profile.

And for good measure:

```auto
Parse.Cloud.afterSaveFile(async ({file, user}) => {
  const profile = user.get('profile');
  await profile.fetch(null, {useMasterKey: true});
  const photo = profile.get('photo');
  if (photo) {
     await photo.destroy(null, {useMasterKey: true});
  }
  profile.set('photo', file);
  await profile.save(null, {useMasterKey: true});
});

```

Pointers are your friend!! 😊

---

<div class="post-metadata">

**Author:** ![woutercouvaras](https://community.parseplatform.org/user_avatar/community.parseplatform.org/woutercouvaras/32/760_2.png) [@woutercouvaras](https://community.parseplatform.org/u/woutercouvaras)\
**Post date:** [August 27, 2021, 7:15am UTC](https://community.parseplatform.org/t/how-do-you-guys-manage-profile-photos-files/1858/15 "2021-08-27T07:15:28Z")

</div>

Here’s a slightly different approach. We store all our images on AWS S3. The route we’ve taken is to use their pre-signed post requests, so that we can essentially upload to S3 directly from the browser. To this, you’ll need a simple Cloud function to generate the pre-signed post url:

```auto
export const createS3Upload = async (request: CloudRequest): Promise<CloudResponse> => {
    if (!request.params.fileName) {
        throw new Error(CLOUD_PARAMS_MISSING);
    }

    AWS.config.update({
        accessKeyId: config.AWS.accessKeyId,
        secretAccessKey: config.AWS.secretAccessKey,
        region: config.AWS.region,
    });

    const s3 = new AWS.S3({ apiVersion: "2006-03-01" });

    const uploadPolicy = s3.createPresignedPost({
        Bucket: config.AWS.s3ImageBucket,
        Fields: { key: request.params.fileName },
    });

    return {
        data: uploadPolicy,
    };
};

```

Once you’ve uploaded to S3 (from your browser), you can simply store the path and filename to the db. This way, you don’t have to worry about managing any of the media on the server.
