Repository navigation
PATCH /editor/project/visibility returns 200 with null and doesn't update when projectId is a slug #4348
Description
p5.js version
No response
What is your operating system?
Mac OS
Web browser and version
Google Chrome 153.0.8010.53 (Official Build) (arm64)
Actual Behavior
PATCH /editor/project/visibility looks up the project by either _id or slug, but then updates it by _id only. When a slug is sent as projectId, the ownership check passes, but the update matches no document. The visibility is not changed, and the server still responds 200 OK with null as the body.
In changeProjectVisibility (server/controllers/project.controller.js):
- The lookup uses
Project.findOne({ $or: [{ _id: projectId }, { slug: projectId }] }), which finds the project by slug. - The ownership check runs on that project and passes.
- The update uses
Project.findByIdAndUpdate(projectId, ...). With a slug, this searches for_id === "<slug>", matches nothing and returnsnull. res.status(200).json(updatedProject)then sendsnullwith status 200.
p5.js-web-editor/server/controllers/project.controller.js
Lines 446 to 487 in eab6aa4
| export async function changeProjectVisibility(req, res) { | |
| try { | |
| const { projectId, visibility: newVisibility } = req.body; | |
| const project = await Project.findOne({ | |
| $or: [{ _id: projectId }, { slug: projectId }] | |
| }); | |
| if (!project) { | |
| return res | |
| .status(404) | |
| .json({ success: false, message: 'No project found.' }); | |
| } | |
| if (!project.user.equals(req.user._id)) { | |
| return res | |
| .status(403) | |
| .json({ success: false, message: 'Unauthorized action.' }); | |
| } | |
| if (newVisibility !== 'Private' && newVisibility !== 'Public') { | |
| return res.status(400).json({ success: false, message: 'Invalid data.' }); | |
| } | |
| const updatedProject = await Project.findByIdAndUpdate( | |
| projectId, | |
| { | |
| visibility: newVisibility | |
| }, | |
| { | |
| new: true, | |
| runValidators: true | |
| } | |
| ) | |
| .populate('user', 'username') | |
| .exec(); | |
| return res.status(200).json(updatedProject); | |
| } catch (error) { | |
| return res.status(500).json(error); | |
| } | |
| } |
The editor UI always sends the _id, so regular users don't hit this. It affects direct calls to the endpoint.
Checked related issues #3864 / #4291 / #3875 and PRs #3920 / #4305 / #3893. #3920 and #4305 change only client-side visibility state, and #3893 only restricts fields in updateProject. None of them change this lookup/update mismatch.
Expected Behavior
The update should apply to the same project that was found and ownership-checked, so sending a slug changes the visibility just like sending the _id. Alternatively, if slugs are not meant to be supported here, the endpoint should reject them with a 4xx error instead of responding 200 with null.
Steps to reproduce
Steps:
Reproduced on the latest develop branch, running locally with Docker.
- Log in, create a new sketch named
visibility test, and save it. Its slug isvisibility_test. You can confirm it in Mongo:
db.projects.find({ name: 'visibility test' }, { slug: 1, visibility: 1 })
In my case the visibility wasPrivate. - Toggle the sketch's visibility once from the toolbar, and copy the
PATCH /editor/project/visibilityrequest from DevTools (Network tab → Copy as cURL). - Resend it with the body
{"projectId":"visibility_test","visibility":"Public"}.- Response:
200 OKwith the bodynull - In Mongo, the visibility is still
Private(unchanged)
- Response:
- Resend it with the body
{"projectId":"<the sketch _id>","visibility":"Public"}.- Response:
200 OKwith the full project JSON - In Mongo, the visibility is now
Public
- Response:
The only difference between steps 3 and 4 is sending the slug instead of the _id.
Possible fix
Update the project that was already found and checked, instead of looking it up again by the raw projectId:
const updatedProject = await Project.findByIdAndUpdate(
project._id,
{ visibility: newVisibility },
{ new: true, runValidators: true }
)I'd be happy to open a PR for this if the approach looks good.
Activity
ANUBHAVSINGH30 commentedon Oct 8, 2026
Hi @Prbhtsgh, I'd like to work on this issue.
I've gone through the reproduction and the lookup/update mismatch makes sense: the project is correctly resolved by either
_idorslug, but the update uses the rawprojectId, causing a slug-based request to return200withnullwithout updating anything.The proposed approach of updating using the already-resolved
project._idlooks straightforward and avoids doing a second lookup.If that's okay, I'd be happy to open a PR for this.
Prbhtsgh commentedon Oct 8, 2026
Hi @ANUBHAVSINGH30 , thanks for taking a look and for confirming the analysis!
As mentioned in the issue, I'd like to work on this one myself. I've already prepared the fix along with a unit test (passes with the fix, fails without it) locally, and I'm waiting for maintainer approval before opening a PR, per the contributing guidelines.
I'll leave the assignment to the maintainers
ANUBHAVSINGH30 commentedon Oct 8, 2026
No worries, thanks for clarifying! Good luck with the PR.