---
name: file-attachments
description: Handling file uploads and attachments in RSX including upload flow, attaching files to models, retrieving attachments, and generating URLs. Use when implementing file uploads, working with File_Attachment_Model, attaching files to records, displaying thumbnails, or handling document downloads.
---
# RSX File Attachments
## Two-Model Architecture
RSX separates physical storage from logical metadata:
| Model | Purpose |
|-------|---------|
| `File_Storage_Model` | Physical files on disk (framework model) |
| `File_Attachment_Model` | Logical uploads with metadata (user model) |
This enables **deduplication** - identical files share physical storage while maintaining separate metadata.
---
## Upload Flow
Files follow a secure two-step process:
1. **Upload** - File uploaded UNATTACHED via `POST /_upload`
2. **Attach** - Controller validates and assigns to model
This session-based validation prevents cross-user file assignment.
---
## Uploading Files
### Frontend Component
Use the built-in file upload component:
```jqhtml
```
### Upload Response
Upload returns a `file_key` that identifies the unattached file:
```javascript
// After upload completes
const file_key = upload_component.get_file_key();
```
---
## Attaching Files to Models
### Single Attachment (Replaces)
For one-to-one relationships (e.g., profile photo):
```php
#[Ajax_Endpoint]
public static function save_photo(Request $request, array $params = []) {
$user = User_Model::find($params['user_id']);
$attachment = File_Attachment_Model::find_by_key($params['photo_key']);
if ($attachment && $attachment->can_user_assign_this_file()) {
$attachment->attach_to($user, 'profile_photo');
}
return ['success' => true];
}
```
`attach_to()` replaces any existing attachment with that slot name.
### Multiple Attachments (Adds)
For one-to-many relationships (e.g., project documents):
```php
$attachment = File_Attachment_Model::find_by_key($params['document_key']);
if ($attachment && $attachment->can_user_assign_this_file()) {
$attachment->add_to($project, 'documents');
}
```
`add_to()` adds to the collection without removing existing files.
### Detaching Files
```php
$attachment->detach();
```
---
## Retrieving Attachments
### Single Attachment
```php
$photo = $user->get_attachment('profile_photo');
if ($photo) {
echo $photo->get_url();
}
```
### Multiple Attachments
```php
$documents = $project->get_attachments('documents');
foreach ($documents as $doc) {
echo $doc->file_name;
}
```
---
## Displaying Files
### Direct URL
```php
$url = $attachment->get_url();
// Returns: /_download/{key}
```
### Download URL (Forces Download)
```php
$url = $attachment->get_download_url();
// Returns: /_download/{key}?download=1
```
### Thumbnail URL
For images with automatic resizing:
```php
// Crop to exact dimensions
$url = $attachment->get_thumbnail_url('cover', 128, 128);
// Fit within dimensions (maintains aspect ratio)
$url = $attachment->get_thumbnail_url('contain', 200, 200);
// Scale to width, auto height
$url = $attachment->get_thumbnail_url('width', 300);
```
Thumbnail types:
- `cover` - Crop to fill exact dimensions
- `contain` - Fit within dimensions
- `width` - Scale to width, maintain aspect ratio
---
## Template Usage
```jqhtml
<% if (this.data.user.profile_photo) { %>
<% } %>
<% for (const doc of this.data.project.documents) { %>
<%= doc.file_name %>
<% } %>
```
---
## File Attachment Model Properties
```php
$attachment->file_name; // Original uploaded filename
$attachment->mime_type; // MIME type (e.g., 'image/jpeg')
$attachment->file_size; // Size in bytes
$attachment->file_key; // Unique identifier
$attachment->created_at; // Upload timestamp
```
---
## Creating Attachments Programmatically
### From Disk
```php
$attachment = File_Attachment_Model::create_from_disk(
'/tmp/import/document.pdf',
[
'site_id' => $site->id,
'filename' => 'imported-document.pdf',
'fileable_category' => 'import'
]
);
```
### From String Content
```php
$csv = "Name,Email\nJohn,john@example.com";
$attachment = File_Attachment_Model::create_from_string(
$csv,
'export.csv',
['site_id' => $site->id, 'fileable_category' => 'export']
);
```
### From URL
```php
$attachment = File_Attachment_Model::create_from_url(
'https://example.com/logo.png',
['site_id' => $site->id, 'fileable_category' => 'logo']
);
```
---
## Security Considerations
### Always Validate Before Attaching
```php
$attachment = File_Attachment_Model::find_by_key($params['file_key']);
// REQUIRED: Check user can assign this file
if (!$attachment || !$attachment->can_user_assign_this_file()) {
return response_error(Ajax::ERROR_UNAUTHORIZED, 'Invalid file');
}
// Now safe to attach
$attachment->attach_to($model, 'slot_name');
```
### File Validation
Check file type before attaching:
```php
if (!in_array($attachment->mime_type, ['image/jpeg', 'image/png', 'image/gif'])) {
return response_form_error('Validation failed', ['photo' => 'Must be an image']);
}
```
---
## Event Hooks for Authorization
Control access with event hooks:
```php
// Require auth for uploads
#[OnEvent('file.upload.authorize', priority: 10)]
public static function require_auth($data) {
if (!Session::is_logged_in()) {
return response()->json(['error' => 'Authentication required'], 403);
}
return true;
}
// Restrict thumbnail access
#[OnEvent('file.thumbnail.authorize', priority: 10)]
public static function check_file_access($data) {
if ($data['attachment']->created_by !== $data['user']?->id) {
return response()->json(['error' => 'Access denied'], 403);
}
return true;
}
// Additional restrictions for downloads
#[OnEvent('file.download.authorize', priority: 10)]
public static function require_premium($data) {
if (!$data['user']?->has_premium()) {
return response()->json(['error' => 'Premium required'], 403);
}
return true;
}
```
---
## System Endpoints
| Endpoint | Purpose |
|----------|---------|
| `POST /_upload` | Upload new file |
| `GET /_download/:key` | Download/view file |
| `GET /_thumbnail/:key/:type/:width/:height` | Get resized image |
| `GET /_icon_by_extension/:extension` | Get file type icon |
## More Information
Details: `php artisan rsx:man file_upload`