Command Palette

Search for a command to run...

Files

Pylo stores files as PyloMedia records, a built-in system entity that behaves like any other entity. To put a file in your data model, connect its PyloMedia record to one of your entities through a media relation, for example contact.avatar or project.attachments.

The upload flow

Uploads take two steps. Your client never talks to the storage backend, and no file bytes pass through your own server:

  1. Request an upload URL. The createUpload mutation returns { id, url }. The id is the id of the PyloMedia record the upload will create. The url is a Pylo upload endpoint containing an embedded, expiring credential (valid for about 1 hour).
  2. Send the bytes. POST the file to that url as multipart/form-data with a single file part. No auth headers needed: the credential in the URL is scoped to this one upload.
createUpload.graphql
1 2 3 4 5 6 7 8 9 mutation { createUpload(input: { entity_relation_path: "Contact.avatar" is_public: false }) { id # the future PyloMedia id url # POST the file here } }

Pylo reads the file's name, mime type, and size from the uploaded bytes, so you do not declare them in step 1. It writes the PyloMedia record only after step 2 finishes, so do not query the record in between. The SDKs wrap both steps in one call, see Uploading & Downloading.

Upload restrictions

entity_relation_path points at the media relation the file is destined for, in the form "Entity.relation". Pylo enforces that relation's allowed mime types (wildcards like image/* work) and file extensions during the upload. Omit input entirely and the upload runs unrestricted and unattached; Pylo validates the file later, when you connect it to a relation. Each upload URL takes one file, up to 2 GB.

Connecting files to entities

Connect an uploaded file like any other relation, using the PyloMedia id in an upsert. Media relations are system-entity relations, so the suffixes are _set (single), _add, and _remove (list). Pylo checks the relation's mime and extension rules again at connect time, so a file uploaded without restrictions still cannot go onto a relation it does not satisfy.

You can upload first and connect later, for example when a user picks a file before the record it belongs to exists. Keep the PyloMedia id around.

Reading files: download URLs

PyloMedia is a regular entity. Query it directly or through the relation on your entity:

download.graphql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 query { contactById(id: "contact-uuid") { data { avatar { data { id file_name file_mime_type file_size_bytes file_extension is_public url # ready-to-use download URL, fresh on every query } } } } }

Pylo computes url at query time. For private files it is a signed, expiring URL (about 5 minutes by default, configurable per relation). Treat it as ephemeral: fetch it when you need it, never store it. The createDownload(id) mutation creates a fresh URL for a PyloMedia id on demand.

Public vs. private files

By default every file is private: downloads require a freshly signed URL, which in turn requires an authenticated GraphQL request. Two ways to make a file publicly downloadable:

  • Per file: pass is_public: true when requesting the upload URL. The file gets a permanent public URL.
  • Per relation: configure the media relation with public downloads. Pylo then serves every file on that relation under a permanent URL, whatever the file's own flag says.

In both cases url returns the permanent public form instead of a signed one. Use public files for assets you embed openly (logos, product images). Keep documents and user-generated content private.

Configuring media relations

A media relation is a normal entity relation whose target is PyloMedia. Per relation you can configure:

  • Allowed mime types, for example image/png or wildcard patterns like image/*
  • Allowed file extensions, for example pdf, docx
  • Public downloads: serve all connected files under permanent public URLs
  • Download URL lifetime: how long signed URLs stay valid for private files