Skip to main content
Pindeck uses NextCloud as its primary media storage backend. When an image is uploaded or generated, Pindeck first buffers the file in Convex storage, then persists the original, preview, and resized derivatives to NextCloud via WebDAV. Public URLs are derived from a shared folder token rather than per-file share links.

Storage path structure

All media lands under a configurable upload prefix (default: pindeck/media-uploads), organized by date:
pindeck/media-uploads/
└── YYYY/
    └── MM_DD/
        ├── original/   <slug>-<timestamp>-<nonce>.<ext>
        ├── preview/    <slug>-<timestamp>-<nonce>-preview.webp
        ├── low/        <slug>-<timestamp>-<nonce>-w320.webp
        └── high/       <slug>-<timestamp>-<nonce>-w768.webp
                        <slug>-<timestamp>-<nonce>-w1280.webp
VariantLocationFormat
Originaloriginal/As uploaded or generated (JPG, PNG, WebP, etc.)
Previewpreview/WebP, 640×360 crop
Low derivativelow/WebP, 320px width
High derivativeshigh/WebP, 768px and 1280px width
Directories are created with WebDAV MKCOL before each PUT. If a directory already exists, the 405 response is treated as success.

Public delivery options

The shared-folder approach is strongly preferred. It requires only one share to be created in NextCloud and avoids per-file OCS API calls, which are slower and harder to manage at scale.

Option 1: Shared folder (preferred)

Create a single public share of your upload root folder in NextCloud, then set NEXTCLOUD_PUBLIC_SHARE_TOKEN in Convex Project Settings. Pindeck derives public image URLs using the pattern:
https://<your-nextcloud>/public.php/dav/files/<token>/path/to/file.webp
No per-file share creation is needed. All originals, previews, and derivatives are immediately accessible via the shared root.

Option 2: Per-file shares

If NEXTCLOUD_PUBLIC_SHARE_TOKEN is not set, Pindeck creates an individual public share for each file through the NextCloud OCS API (/ocs/v2.php/apps/files_sharing/api/v1/shares). This works but generates a large number of share records over time.

Setup

1

Create a NextCloud app password

In NextCloud, go to Settings → Security → Devices & sessions and generate an app password for Pindeck. Do not use your main account password.
2

Find your WebDAV base URL

Your WebDAV URL follows the pattern:
https://<your-nextcloud>/remote.php/dav/files/<username>
3

Create a public share (recommended)

In NextCloud, share the pindeck/media-uploads folder (or your chosen upload prefix) as a public link. Copy the share token from the link URL — it is the alphanumeric string after /s/.
4

Set Convex environment variables

In Convex Project Settings → Environment Variables, add the following:
NEXTCLOUD_WEBDAV_BASE_URL=https://<your-nextcloud>/remote.php/dav/files/<username>
NEXTCLOUD_WEBDAV_USER=<username>
NEXTCLOUD_WEBDAV_APP_PASSWORD=<app-password>
NEXTCLOUD_UPLOAD_PREFIX=pindeck/media-uploads
NEXTCLOUD_PUBLIC_SHARE_TOKEN=<share-token>
5

Verify connectivity

Run the self-cleaning persistence test from your project root. It uploads a small text file, reads it back, then deletes it — no test files are left behind.
npx convex run mediaStorage:testNextcloudPersistence "{}"
Expect { "ok": true } on success.

Image record status fields

Each image record tracks its NextCloud persistence state:
FieldValuesDescription
nextcloudPersistStatuspending | succeeded | failedWhether the file has been written to NextCloud
nextcloudPersistErrorstringError message when persist failed
derivativeUrls{ small, medium, large }Public URLs for resized derivatives
derivativeStoragePaths{ small, medium, large }WebDAV paths used for cleanup on delete

Backfilling failed uploads

If images are stuck with storageProvider: "convex" and have not been persisted to NextCloud, run the backfill mutation to reschedule them:
bunx convex run images:backfillNextcloudFailedUploads '{"limit":50}'
The mutation targets images where sourceType = "upload", storageProvider = "convex", and storageId is still present. Run it multiple times if you have more than 50 images to backfill.

Environment variables reference

Set all of these in Convex Project Settings → Environment Variables.
VariableRequiredDescription
NEXTCLOUD_WEBDAV_BASE_URLYesFull WebDAV root URL, e.g. https://cloud.example.com/remote.php/dav/files/user
NEXTCLOUD_WEBDAV_USERYesNextCloud username
NEXTCLOUD_WEBDAV_APP_PASSWORDYesNextCloud app password (not your account password)
NEXTCLOUD_UPLOAD_PREFIXNoUpload path prefix; defaults to pindeck/media-uploads
NEXTCLOUD_PUBLIC_SHARE_TOKENRecommendedPublic share token for shared-folder URL derivation
NEXTCLOUD_UPLOAD_SHARE_TOKENOptionalSeparate write-enabled share token for backend uploads
NEXTCLOUD_PUBLIC_SHARE_PATHOptionalOverrides the shared root path; defaults to NEXTCLOUD_UPLOAD_PREFIX
NEXTCLOUD_PUBLIC_BASE_URLOptionalOverrides the NextCloud server base URL for public links (useful when NextCloud is behind a proxy)

Build docs developers (and LLMs) love