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 = null
and 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.
findOne
anddelete
are 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
fileInfo
fields. -
Content API controller factory (
plugin.controllers['content-api']
): it wrapsfind
,findOne
, anddestroy
to enforce per-user access:find
filters with:or{ "$or": [ { "owner.documentId": user.documentId }, { "owner": null } ] }
{ owner: null }
if unauthenticated.findOne
/destroy
verify 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
files
table and_links
relation 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 install
is 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.json
is the full Upload File schema with the addedowner
relation (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 Uploadfile
schema 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 /upload
GET /upload/files
GET /upload/files/:id
DELETE /upload/files/:id
(optional; delete is owner-only at runtime)
Grant Public role only the endpoints you need (typically
GET /upload/files
andGET /upload/files/:id
if you want public visibility of ownerless files).
- Authenticated upload (Bearer JWT header):
- The extension sets
owner
to 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):
owner
staysnull
and the file is visible to everyone.
GET /api/upload/files
returns:- For authenticated users: their files and public files.
- For unauthenticated users: public files only.
GET /api/upload/files/:id
returns the file if it’s public or owned by the current user.DELETE /api/upload/files/:id
is allowed only to the owner (if role permission is granted).
The extension uses Strapi v5 Document Service to attach ownership and
entityService
to query with filters onowner.documentId
orowner = null
.
- The extension avoids setting
owner
throughbody.fileInfo
because 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.documentId
in 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.json
is the full Upload file schema (samecollectionName: "files"
) with the addedowner
attribute. Rebuild (rm -rf .cache .tmp && npm run build
). -
created_by_id
foreign key errors when uploading with JWT users
Don’t passopts.user
into 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.documentId
anduser.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