> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Files

> Upload and manage files with Modelence Cloud storage

Modelence provides built-in file storage through Modelence Cloud. Files are organized by visibility — either `public` (accessible via a permanent URL) or `private` (accessible only via a time-limited signed URL).

<Warning>
  **File operations are server-only.** `getUploadUrl`, `getFileUrl`, `downloadFile`, and `deleteFile` are imported from `modelence/server` and can only be called from your server-side code (queries, mutations, cron jobs). They are **not** callable directly from the browser.

  This is deliberate. Each operation issues a signed URL or performs a delete against Modelence Cloud using your app's credentials, for whatever `filePath` it is given. The framework has no way to know which user owns which file, so exposing these to the client would let any caller act on any path. **You** decide who can touch which file by wrapping these calls in your own queries/mutations and enforcing ownership there — see [Exposing file operations to the client](#exposing-file-operations-to-the-client).
</Warning>

## Uploading a File

`getUploadUrl` returns a presigned URL and form fields. POST these together as `FormData` to upload the file directly to storage:

```ts theme={null}
import { getUploadUrl } from 'modelence/server';
import fs from 'fs/promises';

const { url, fields, filePath } = await getUploadUrl({
  filePath: 'photo.png',
  contentType: 'image/png',
  visibility: 'public',
});

const data = await fs.readFile('./photo.png');
const formData = new FormData();
for (const [key, value] of Object.entries(fields)) {
  formData.append(key, value);
}
formData.append('file', new Blob([data], { type: 'image/png' }));

await fetch(url, { method: 'POST', body: formData });

console.log(filePath); // e.g. 'public/photo.png'
```

The `visibility` field controls access:

* `"public"` — the file is accessible via a permanent URL
* `"private"` — the file requires a signed URL to access

## Getting a File URL

Returns a URL for displaying or linking to a file. For public files this is a permanent URL; for private files it is a time-limited presigned URL.

```ts theme={null}
import { getFileUrl } from 'modelence/server';

const { url } = await getFileUrl('public/photo.png');
console.log(url); // Permanent public URL

const { url: privateUrl } = await getFileUrl('private/report.pdf');
console.log(privateUrl); // Time-limited presigned URL
```

## Downloading a File

Returns a presigned download URL for a private file.

```ts theme={null}
import { downloadFile } from 'modelence/server';

const { downloadUrl } = await downloadFile('private/report.pdf');
// Redirect the user to downloadUrl or fetch it server-side
```

## Deleting a File

```ts theme={null}
import { deleteFile } from 'modelence/server';

await deleteFile('public/photo.png');
await deleteFile('private/report.pdf');
```

## File Paths

File paths always include the visibility prefix: `public/<path>` or `private/<path>`. The upload functions return the full `filePath` (including prefix) in their result, which you can store and pass to the other functions.

```ts theme={null}
import { getUploadUrl } from 'modelence/server';

const { filePath } = await getUploadUrl({
  filePath: 'avatars/user-123.png',
  contentType: 'image/png',
  visibility: 'private',
});
// filePath === 'private/avatars/user-123.png'

const { url } = await getFileUrl(filePath);
```

## Exposing file operations to the client

Because the file functions are server-only, the browser reaches them through **your own** queries and mutations. This is where you enforce authorization: never accept a raw client-supplied `filePath` and forward it blindly — derive the path from your own data and verify the current user is allowed to act on it.

The pattern below stores file ownership in a Store and checks it on every operation. Uploading returns the presigned URL to the client, which performs the actual upload directly to storage.

```ts theme={null}
import { Module, Store, schema, getUploadUrl, getFileUrl, deleteFile } from 'modelence/server';
import { ObjectId } from 'mongodb';

const dbDocuments = new Store('documents', {
  schema: {
    userId: schema.userId(),
    filePath: schema.string(),
    createdAt: schema.date(),
  },
});

export default new Module('documents', {
  stores: [dbDocuments],
  queries: {
    // Hand out a time-limited URL only for a document the caller owns.
    async getUrl({ documentId }, { user }) {
      if (!user) {
        throw new Error('Authentication required');
      }
      const doc = await dbDocuments.requireOne({
        _id: new ObjectId(documentId as string),
        userId: new ObjectId(user.id),
      });
      return getFileUrl(doc.filePath);
    },
  },
  mutations: {
    // Issue an upload URL for a path scoped to the caller, then record ownership.
    async requestUpload({ contentType }, { user }) {
      if (!user) {
        throw new Error('Authentication required');
      }
      const filePath = `private/users/${user.id}/${new ObjectId().toString()}`;
      const upload = await getUploadUrl({
        filePath,
        contentType: contentType as string,
        visibility: 'private',
      });
      await dbDocuments.insertOne({
        userId: new ObjectId(user.id),
        filePath,
        createdAt: new Date(),
      });
      return upload; // { url, fields, filePath } — the client POSTs the file to `url`
    },

    // Only the owner can delete.
    async remove({ documentId }, { user }) {
      if (!user) {
        throw new Error('Authentication required');
      }
      const doc = await dbDocuments.requireOne({
        _id: new ObjectId(documentId as string),
        userId: new ObjectId(user.id),
      });
      await deleteFile(doc.filePath);
      await dbDocuments.deleteOne({ _id: doc._id });
    },
  },
});
```

The client then calls your guarded methods — e.g. `callMethod('documents.requestUpload', { contentType })` — instead of the file functions directly. Public assets that are safe for anyone to read can be served straight from their permanent `public/...` URL without a query.
