Skip to main content

Overview

The AttachmentController handles file uploads and deletion for patient medical records. It supports uploading documents, images, and other files associated with patient records. Controller Location: app/Http/Controllers/AttachmentController.php
Middleware: auth, verified, role:admin|doctor|receptionist

Endpoints

Upload Patient Attachment

method
string
HTTP Method: POST
route
string
Route: /patients/{patient}/attachments
name
string
Route Name: patients.attachments.store
Upload a file and attach it to a patient’s medical record.

Path Parameters

patient
integer
required
Patient ID to attach the file to

Request Body

file
file
required
The file to upload (max size: 10 MB)
label
string
Optional descriptive label for the attachment (max 255 characters)

Response

Redirects back with success message: “Archivo adjunto guardado.”

Example Request

POST /patients/15/attachments
Content-Type: multipart/form-data

file: [binary file data]
label: Lab Results - Blood Test January 2026
// Frontend example with FormData
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('label', 'Lab Results - Blood Test January 2026');

await axios.post(`/patients/${patientId}/attachments`, formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});

Validation Rules

[
    'file' => 'required|file|max:10240', // 10MB max
    'label' => 'nullable|string|max:255',
]

Implementation

public function storePatient(Request $request, Patient $patient, UploadAttachmentAction $action)
{
    $request->validate([
        'file' => 'required|file|max:10240', // 10MB max
        'label' => 'nullable|string|max:255',
    ]);

    $action->execute($patient, $request->file('file'), $request->input('label'));

    return redirect()->back()->with('success', 'Archivo adjunto guardado.');
}

Delete Attachment

method
string
HTTP Method: DELETE
route
string
Route: /attachments/{attachment}
name
string
Route Name: attachments.destroy
Delete an attachment and remove the associated file from storage.

Path Parameters

attachment
integer
required
Attachment ID to delete

Response

Redirects back with success message: “Archivo eliminado.”

Example Request

DELETE /attachments/42
// Frontend example
await axios.delete(`/attachments/${attachmentId}`);

Implementation

public function destroy(Attachment $attachment)
{
    \Illuminate\Support\Facades\Storage::disk('public')->delete($attachment->file_path);
    $attachment->delete();

    return redirect()->back()->with('success', 'Archivo eliminado.');
}
Deleting an attachment permanently removes both the database record and the physical file from storage. This action cannot be undone.

Authorization

Only users with admin, doctor, or receptionist roles can upload and delete attachments:
Route::middleware(['role:admin|doctor|receptionist'])->group(function () {
    Route::post('patients/{patient}/attachments', [AttachmentController::class, 'storePatient']);
    Route::delete('attachments/{attachment}', [AttachmentController::class, 'destroy']);
});

File Size Limits

Maximum file size: 10 MB (10,240 KB)This limit is enforced at the validation level. Larger files will be rejected with a validation error.

Supported File Types

While no explicit MIME type restrictions are enforced in the validation, recommended file types include:

Documents

  • PDF (.pdf)
  • Word (.doc, .docx)
  • Text (.txt)

Images

  • JPEG (.jpg, .jpeg)
  • PNG (.png)
  • GIF (.gif)

Medical Imaging

  • DICOM (.dcm)
  • TIFF (.tif, .tiff)

Spreadsheets

  • Excel (.xls, .xlsx)
  • CSV (.csv)

Action Classes

The controller uses an action class for file upload logic: UploadAttachmentAction (app/Actions/Attachments/UploadAttachmentAction.php) This action handles:
  • File storage to the public disk
  • Extracting file metadata (size, MIME type, name)
  • Creating the Attachment database record

Storage Configuration

Attachments are stored using Laravel’s public disk:
// Typical storage path
storage/app/public/attachments/patients/[filename]
Ensure the storage link is created by running:
php artisan storage:link
This creates a symbolic link from public/storage to storage/app/public.

Use Cases

Uploading Lab Results

// Frontend: Upload lab results PDF
const fileInput = document.getElementById('labResults');
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('label', 'Blood Test - January 2026');

try {
  await axios.post(`/patients/${patient.id}/attachments`, formData);
  alert('Lab results uploaded successfully');
} catch (error) {
  alert('Upload failed: ' + error.response.data.message);
}

Uploading Medical Imaging

// Upload X-ray image
const formData = new FormData();
formData.append('file', xrayFile);
formData.append('label', 'Chest X-Ray - March 15, 2026');

await axios.post(`/patients/${patient.id}/attachments`, formData);

Deleting an Attachment

// Delete attachment with confirmation
if (confirm('Are you sure you want to delete this attachment?')) {
  await axios.delete(`/attachments/${attachment.id}`);
  // Refresh attachment list
  loadAttachments();
}

Displaying Attachments

// Frontend: Display patient attachments
const attachments = patient.attachments;

attachments.forEach(attachment => {
  const link = document.createElement('a');
  link.href = attachment.url;
  link.target = '_blank';
  link.textContent = attachment.file_name;
  
  const label = document.createElement('span');
  label.textContent = attachment.label || 'No label';
  
  // Display file size
  const size = (attachment.file_size / 1024).toFixed(2) + ' KB';
  
  console.log(`${attachment.file_name} (${size}) - ${attachment.label}`);
});

Security Considerations

Access Control: Only authenticated users with appropriate roles can upload attachments. Implement additional checks if needed to ensure users can only access attachments for patients they are authorized to view.
File Validation: The controller validates file size (max 10 MB) but does not restrict file types. Consider adding MIME type validation if specific file types should be blocked for security reasons.

Error Handling

Common Validation Errors

// File too large
{
  "message": "The file must not be greater than 10240 kilobytes.",
  "errors": {
    "file": ["The file must not be greater than 10240 kilobytes."]
  }
}

// Missing file
{
  "message": "The file field is required.",
  "errors": {
    "file": ["The file field is required."]
  }
}

// Label too long
{
  "message": "The label must not be greater than 255 characters.",
  "errors": {
    "label": ["The label must not be greater than 255 characters."]
  }
}

Build docs developers (and LLMs) love