A lightweight server-side extension for Strapi’s Upload plugin that enforces file ownership via a relation field owner pointing to plugin::users-permissions.user.
- When an authenticated user uploads files via REST (
POST /api/upload), the extension automatically attaches the uploader as the fileowner. - When unauthenticated upload is allowed, uploaded files have
owner = nulland are treated as public in the Content API. - The Content API is restricted so that:
- Authenticated users can see their own files and public files.
- Unauthenticated users can see only public files.
findOneanddeleteare allowed only to the owner (or public forfindOne).
This repository targets Strapi v5.23.0 and newer in the v5 line.
This extension customizes the Upload plugin in two places:
-
Service wrapper (
plugin.services.upload): after the Upload service persists files, it updates each created file using the Document Service to set:{ "owner": { "connect": ["<currentUser.documentId>"] } }This avoids the Content API’s request body sanitation which ignores unknown
fileInfofields. -
Content API controller factory (
plugin.controllers['content-api']): it wrapsfind,findOne, anddestroyto enforce per-user access:findfilters with:or{ "$or": [ { "owner.documentId": user.documentId }, { "owner": null } ] }{ owner: null }if unauthenticated.findOne/destroyverify that the loaded file either hasowner = null(public) orowner.documentId = currentUser.documentId.
- Strapi:
v5.23.0(tested) - Database: any supported by Strapi v5 (uses the Upload plugin’s
filestable and_linksrelation tables). - Users: relation is to Users & Permissions users (
plugin::users-permissions.user). Admin users are not used for ownership checks.
This is a project-level extension. No
npm installis required.
- In your Strapi project, create the following directory structure and copy the provided files:
src/
└─ extensions/
└─ upload/
├─ content-types/
│ └─ file/
│ └─ schema.json # full original Upload file schema + added "owner" relation
└─ strapi-server.js # service + controller factory wrappers
- Ensure the
schema.jsonis the full Upload File schema with the addedownerrelation (camelCase). Example attribute to add:
"owner": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user"
}Do not provide a truncated schema. If you omit original fields (e.g.
url,hash,mime, etc.), Strapi will treat it as a new content type and break media relations. Always start from the original Uploadfileschema and appendowner.
- Rebuild and run Strapi:
rm -rf .cache .tmp
npm run build
npm run dev-
In Settings → Users & Permissions → Roles grant the Authenticated role permissions for:
POST /uploadGET /upload/filesGET /upload/files/:idDELETE /upload/files/:id(optional; delete is owner-only at runtime)
Grant Public role only the endpoints you need (typically
GET /upload/filesandGET /upload/files/:idif you want public visibility of ownerless files).
- Authenticated upload (Bearer JWT header):
- The extension sets
ownerto the current user. - The file becomes visible to the owner and to no one else via the protected endpoints.
- The extension sets
- Public upload (if allowed via role):
ownerstaysnulland the file is visible to everyone.
GET /api/upload/filesreturns:- For authenticated users: their files and public files.
- For unauthenticated users: public files only.
GET /api/upload/files/:idreturns the file if it’s public or owned by the current user.DELETE /api/upload/files/:idis allowed only to the owner (if role permission is granted).
The extension uses Strapi v5 Document Service to attach ownership and
entityServiceto query with filters onowner.documentIdorowner = null.
- The extension avoids setting
ownerthroughbody.fileInfobecause the Upload Content API sanitizes request bodies and rejects unknown keys. Ownership is set after creation usingdocuments('plugin::upload.file').update({ documentId, data: { owner: { connect: [...] }}}). - The relation field name must be
owner(camelCase). Filters useowner.documentIdin Strapi v5. - Admin-side audit fields (
createdBy,updatedBy) are not used for ownership; they reference Admin users, not Users & Permissions users.
-
ValidationError: Invalid key owner
Ensure your extensionschema.jsonis the full Upload file schema (samecollectionName: "files") with the addedownerattribute. Rebuild (rm -rf .cache .tmp && npm run build). -
created_by_idforeign key errors when uploading with JWT users
Don’t passopts.userinto the Upload service override; admin audit fields point to Admin users and will FK-fail for U&P users. -
Owner not set after upload
Confirm the service wrapper runs and that you calldocuments('plugin::upload.file').update(...)withfile.documentIdanduser.documentId.
MIT — use at your own risk.
- Strapi: https://strapi.io/
- Upload plugin docs: https://docs.strapi.io/dev-docs/plugins/upload
- Users & Permissions: https://docs.strapi.io/dev-docs/plugins/users-permissions
strapi strapi-v5 strapi-5.23.0 upload upload-plugin content-api ownership file-ownership media authorization access-control users-permissions document-service entity-service extensions security