Skip to main content

Nextcloud storage

Pindeck uses Nextcloud as a persistent media storage layer. Images uploaded to Convex are automatically persisted to Nextcloud via WebDAV.

How it works

  1. An image is uploaded to Convex file storage first (fast, temporary)
  2. A background action uploads the file to Nextcloud via WebDAV
  3. Derivatives (preview, resized versions) are generated and stored
  4. The image record is updated with Nextcloud URLs and persistence status

Upload path format

pindeck/media-uploads/YYYY/MM_DD/original/<filename>
pindeck/media-uploads/YYYY/MM_DD/preview/<filename>-preview.<ext>
pindeck/media-uploads/YYYY/MM_DD/low/<filename>-w320.webp
pindeck/media-uploads/YYYY/MM_DD/high/<filename>-w768.webp
pindeck/media-uploads/YYYY/MM_DD/high/<filename>-w1280.webp
Directories are created explicitly via WebDAV MKCOL before PUT.

Configuration

Convex dashboard variables

VariableDescription
NEXTCLOUD_WEBDAV_BASE_URLWebDAV endpoint URL
NEXTCLOUD_WEBDAV_USERNextcloud username
NEXTCLOUD_WEBDAV_APP_PASSWORDApp-specific password (not your login password)
NEXTCLOUD_UPLOAD_PREFIXPath prefix (default: pindeck/media-uploads)

Public sharing

Pindeck supports two ways to serve Nextcloud files as browser-safe URLs:

Preferred: Shared folder model

Share the upload root folder once in Nextcloud and set:
VariableRequiredDescription
NEXTCLOUD_PUBLIC_SHARE_TOKENYesToken from the shared folder
NEXTCLOUD_UPLOAD_SHARE_TOKENNoSeparate write-enabled token for backend uploads
NEXTCLOUD_PUBLIC_SHARE_PATHNoDefaults to NEXTCLOUD_UPLOAD_PREFIX
NEXTCLOUD_PUBLIC_BASE_URLNoDefaults to Nextcloud server base URL
Public URLs are derived from the shared root token using:
public.php/dav/files/<token>/...

Alternate: Per-file public shares

Create individual public shares through the Nextcloud OCS API for each file.

Persistence tracking

Each image tracks its storage state:
FieldValuesDescription
storageProviderconvex, nextcloudCurrent storage location
nextcloudPersistStatuspending, succeeded, failedPersistence state
nextcloudPersistErrorstringError message on failure
derivativeUrlsobject{ small, medium, large } URLs
derivativeStoragePathsobject{ small, medium, large } paths

Backfill failed uploads

Reschedule persistence for uploads stuck in Convex storage:
bunx convex run images:backfillNextcloudFailedUploads '{"limit":50}'
This targets images where:
  • sourceType = "upload"
  • storageProvider = "convex"
  • storageId is still present

Admin endpoints

Two admin HTTP endpoints manage Nextcloud storage:
EndpointMethodDescription
/admin/backfillNextcloudPOSTRe-upload files from Convex to Nextcloud
/admin/quarantineBrokenNextcloudPOSTQuarantine files with broken Nextcloud references
These admin endpoints are not part of the public API and are not documented in the API reference.