Command Palette

Search for a command to run...

Uploading & Downloading

The SDKs wrap the whole upload flow in one call: client.files.upload on the server, usePyloUpload in the browser. In the browser the bytes stream straight to Pylo, so you need no extra API route and no file bytes cross your server. The hook reports byte-accurate progress and error state, and it can abort a batch.

Uploading

upload.ts
1 2 3 4 5 6 7 8 9 10 11 12 import { pylo } from './lib/pylo'; import { readFile } from 'node:fs/promises'; const buffer = await readFile('./avatar.png'); const media = await pylo.files.upload(buffer, { fileName: 'avatar.png', mimeType: 'image/png', entityRelationPath: 'contact.avatar', }); // media: { id, fileName, mimeType, size }

Accepted sources on the server are File, Blob, ArrayBuffer, and typed arrays (including Node Buffer). fileName is required unless the source is a File. Passing entityRelationPath enforces the relation's mime and extension rules at upload time; omit it for unrestricted, unattached uploads. isPublic: true creates a permanent public download URL.

Attaching to a record

attachTo connects the uploaded file in the same call. Use mode: 'add' for list relations. On the client it also invalidates the entity's queries, so open views refetch.

attach.ts
1 2 3 4 5 6 7 await pylo.files.upload(buffer, { fileName: 'avatar.png', entityRelationPath: 'contact.avatar', attachTo: { id: contactId }, }); // list relations: attachTo: { id: projectId, mode: 'add' }

Upload now, attach later

If you do not know the target record yet, upload without attachTo. The result always contains the PyloMedia id; connect it later with a normal upsert:

deferred-attach.ts
1 2 3 4 5 6 7 8 9 10 const media = await pylo.files.upload(buffer, { fileName: 'report.pdf', entityRelationPath: 'project.documents', }); // later, once the record exists: await pylo.project.upsert({ id: projectId, documents_add: [{ id: media.id }], });

Downloading

Private download URLs expire (about 5 minutes by default), so fetch them right before use and never store them. Public files return their permanent URL from the same calls.

download.ts
1 2 const url = await pylo.files.getDownloadUrl(mediaId); const response = await fetch(url);

usePyloFileUrl caches the URL and refetches it before the expiry. You can also select url on the relation in a regular query; each fetch returns fresh URLs.

Upload URLs

createUploadUrl returns the upload URL without sending bytes, for example to hand it to another process or a language without an SDK. It expires after about 1 hour. POST the file to url as multipart/form-data with a single file part.

upload-url.ts
1 2 3 4 const uploadUrl = await pylo.files.createUploadUrl({ entityRelationPath: 'project.documents', }); // uploadUrl: { id, url }

usePyloUpload reference

usePyloUpload(options?) returns:

  • startUpload(files, overrides?): uploads one file or an array, resolves with the created PyloMedia rows ({ id, fileName, mimeType, size } each)
  • isUploading: whether a batch is in flight
  • progress: 0-100, aggregated by bytes across the batch
  • uploadedFiles: results of the last successful batch
  • error: the failure of the last batch, or null
  • abort(): cancels the in-flight batch
  • reset(): clears progress, error, and results

Options (all optional; you can also pass entityRelationPath, isPublic, and attachTo per call, as the second argument of startUpload):

  • entityRelationPath: the media relation the files are destined for, for example 'contact.avatar' (autocompletes from your schema)
  • isPublic: create permanent public download URLs (requires entityRelationPath)
  • attachTo: { id, mode? }, connects each uploaded file to the record; mode is 'set' (default, single relations) or 'add' (list relations)
  • onProgress, onSuccess, onError: callbacks
  • headers: extra headers for the GraphQL requests

Progress reporting

In the browser, progress is byte-accurate. In Node, onProgress fires only at the start and the end of the transfer, because the runtime's fetch emits no upload progress events.