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

# Almacenamiento de archivos

> Explicamos cómo leer y escribir archivos, subirlos e integrar almacenamiento en la nube usando la integración con Flysystem de Laravel.

## ¿Qué es el almacenamiento de archivos?

Laravel ofrece una potente capa de abstracción del sistema de archivos basada en el paquete PHP [Flysystem](https://github.com/thephpleague/flysystem).
Puedes manipular con la misma API distintos backends de almacenamiento (disco local, SFTP, Amazon S3…), por lo que **no necesitas cambiar el código** al pasar de uno a otro según el entorno.

<Info>
  Al cambiar de driver, el código no cambia. Puedes usar el disco local en desarrollo y S3 en producción con configuración muy simple.
</Info>

## Configuración

La configuración del sistema de archivos se centraliza en `config/filesystems.php`.
Ahí defines los "discos". Un disco es la combinación de un driver y una ubicación de almacenamiento.

Los drivers principales son:

| Driver   | Uso                                               |
| -------- | ------------------------------------------------- |
| `local`  | Sistema de archivos local del servidor            |
| `public` | Disco local para archivos accesibles desde la web |
| `s3`     | Amazon S3 (o servicios compatibles con S3)        |
| `sftp`   | Servidor SFTP                                     |
| `ftp`    | Servidor FTP                                      |

### Driver local

Con el driver `local`, las operaciones se realizan tomando como base la ruta `root` de la configuración `filesystems`.
Por defecto, `root` es `storage/app/private`.

```php theme={null}
use Illuminate\Support\Facades\Storage;

Storage::disk('local')->put('example.txt', 'Contents');
// Se escribe en storage/app/private/example.txt
```

### Cambiar el disco por defecto

Cambia el disco por defecto con la variable `FILESYSTEM_DISK`.

```ini theme={null}
FILESYSTEM_DISK=s3
```

## Disco public y enlaces simbólicos

El disco `public` sirve para almacenar archivos accesibles desde la web.
Por defecto se guardan en el directorio `storage/app/public`.

Para que sean accesibles desde el servidor web, crea un enlace simbólico de `public/storage` a `storage/app/public`.

<Steps>
  <Step title="Crear el enlace simbólico">
    ```shell theme={null}
    php artisan storage:link
    ```
  </Step>

  <Step title="Obtener la URL del archivo">
    Tras crear el enlace, puedes generar la URL con el helper `asset`.

    ```php theme={null}
    echo asset('storage/file.txt');
    ```
  </Step>
</Steps>

<Tip>
  Si necesitas enlaces simbólicos adicionales, configúralos en el array `links` de `config/filesystems.php`.

  ```php theme={null}
  'links' => [
      public_path('storage') => storage_path('app/public'),
      public_path('images') => storage_path('app/images'),
  ],
  ```
</Tip>

Para eliminar el enlace simbólico usa `storage:unlink`.

```shell theme={null}
php artisan storage:unlink
```

## Operaciones básicas con la fachada Storage

### Leer archivos

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Obtener el contenido como string
$contents = Storage::get('file.jpg');

// Obtener y decodificar un JSON
$data = Storage::json('orders.json');

// Comprobar si existe
if (Storage::exists('file.jpg')) {
    // El archivo existe
}

// Comprobar que no existe
if (Storage::missing('file.jpg')) {
    // El archivo no existe
}
```

### Escribir archivos

```php theme={null}
// Escribir en un archivo
Storage::put('file.jpg', $contents);

// Escribir un recurso como stream
Storage::put('file.jpg', $resource);

// Anteponer o añadir texto
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');

// Copiar y mover
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
```

<Info>
  Si `put` falla devuelve `false` por defecto.
  Con `'throw' => true` en la configuración del disco puedes hacer que lance excepciones.
</Info>

### Eliminar archivos

```php theme={null}
// Un solo archivo
Storage::delete('file.jpg');

// Varios archivos
Storage::delete(['file.jpg', 'file2.jpg']);

// En un disco concreto
Storage::disk('s3')->delete('path/file.jpg');
```

### Respuesta de descarga

```php theme={null}
// Devuelve una respuesta que provoca la descarga en el navegador
return Storage::download('file.jpg');

// Con nombre y cabeceras
return Storage::download('file.jpg', 'my-file.jpg', $headers);
```

## Generación de URLs

### URL normal

```php theme={null}
use Illuminate\Support\Facades\Storage;

$url = Storage::url('file.jpg');
```

Con el driver `local` devuelve una URL relativa como `/storage/file.jpg`.
Con `s3` devuelve la URL remota completa.

### URLs temporales

Para generar URLs con caducidad usa `temporaryUrl`.
Disponible en los drivers `local` y `s3`.

```php theme={null}
use Illuminate\Support\Facades\Storage;

$url = Storage::temporaryUrl(
    'file.jpg',
    now()->plus(minutes: 5)
);
```

En S3 puedes indicar parámetros adicionales.

```php theme={null}
$url = Storage::temporaryUrl(
    'file.jpg',
    now()->plus(minutes: 5),
    [
        'ResponseContentType' => 'application/octet-stream',
        'ResponseContentDisposition' => 'attachment; filename=file.jpg',
    ]
);
```

<Tip>
  Si necesitas una URL temporal para subir, usa `temporaryUploadUrl`.
  Es útil en configuraciones serverless en las que el cliente sube directamente a S3.

  ```php theme={null}
  ['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
      'file.jpg', now()->plus(minutes: 5)
  );
  ```
</Tip>

## Subida de archivos

Patrón habitual para guardar un archivo enviado por un formulario.

### store (genera nombre automáticamente)

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserAvatarController extends Controller
{
    public function update(Request $request): string
    {
        // El nombre se genera automáticamente a partir del MIME
        $path = $request->file('avatar')->store('avatars');

        return $path;
    }
}
```

### storeAs (nombre explícito)

```php theme={null}
$path = $request->file('avatar')->storeAs(
    'avatars',
    $request->user()->id
);
```

### Subir a un disco concreto

```php theme={null}
// Guarda en el disco s3
$path = $request->file('avatar')->store(
    'avatars/' . $request->user()->id,
    's3'
);
```

### Subir con la fachada Storage

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Nombre automático
$path = Storage::putFile('avatars', $request->file('avatar'));

// Nombre explícito
$path = Storage::putFileAs(
    'avatars',
    $request->file('avatar'),
    $request->user()->id
);
```

<Warning>
  `getClientOriginalName()` y `getClientOriginalExtension()` no son seguros porque el usuario puede manipularlos.
  Para el nombre usa `hashName()` y para la extensión `extension()`.

  ```php theme={null}
  $file = $request->file('avatar');

  $name = $file->hashName();       // Nombre aleatorio único
  $extension = $file->extension(); // Extensión deducida del MIME
  ```
</Warning>

## Visibilidad de los archivos

En Flysystem, la privacidad se gestiona con `visibility`.

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Indica la visibilidad al escribir
Storage::put('file.jpg', $contents, 'public');

// Consultar y cambiar la visibilidad
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');
```

Para guardar un archivo subido como público, usa `storePublicly`.

```php theme={null}
$path = $request->file('avatar')->storePublicly('avatars', 's3');
```

## Uso de varios discos

Cambia de disco con `disk`.

```php theme={null}
// En el disco por defecto
Storage::put('avatars/1', $content);

// En s3
Storage::disk('s3')->put('avatars/1', $content);

// Crear un disco on-demand
$disk = Storage::build([
    'driver' => 'local',
    'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
```

## Configuración del almacenamiento en la nube (S3)

### Instalación del paquete

```shell theme={null}
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
```

### Variables de entorno

Configura las credenciales de S3 en `.env`.

```ini theme={null}
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_USE_PATH_STYLE_ENDPOINT=false
```

<Info>
  Servicios compatibles con S3 como DigitalOcean Spaces, Cloudflare R2 o Vultr Object Storage también funcionan con el driver `s3`.
  Indica su endpoint en la opción `endpoint`.

  ```php theme={null}
  'endpoint' => env('AWS_ENDPOINT', 'https://your-endpoint.example.com'),
  ```
</Info>

## Metadatos del archivo

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Tamaño (bytes)
$size = Storage::size('file.jpg');

// Última modificación (timestamp UNIX)
$time = Storage::lastModified('file.jpg');

// Tipo MIME
$mime = Storage::mimeType('file.jpg');

// Ruta absoluta (driver local)
$path = Storage::path('file.jpg');
```

## Operaciones con directorios

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Archivos del directorio
$files = Storage::files($directory);

// Archivos incluyendo subdirectorios
$files = Storage::allFiles($directory);

// Subdirectorios
$directories = Storage::directories($directory);

// Crear directorio
Storage::makeDirectory($directory);

// Eliminar el directorio y su contenido
Storage::deleteDirectory($directory);
```

## Pruebas

Con `Storage::fake()` puedes probar operaciones de archivo sin tocar el disco real.

```php theme={null}
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

test('se puede subir un avatar', function () {
    Storage::fake('avatars');

    $file = UploadedFile::fake()->image('avatar.jpg');

    $this->post('/user/avatar', ['avatar' => $file]);

    // Comprueba que se ha guardado
    Storage::disk('avatars')->assertExists('avatar.jpg');

    // Comprueba que otro archivo no existe
    Storage::disk('avatars')->assertMissing('other.jpg');
});
```

<Info>
  Para usar `UploadedFile::fake()->image()` necesitas la [extensión GD](https://www.php.net/manual/es/book.image.php) de PHP.
</Info>

## Caso práctico: subida de una imagen de perfil

Ejemplo de un controlador que combina validación, guardado y registro de la ruta en la BD.

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;

class ProfileController extends Controller
{
    public function updateAvatar(Request $request): RedirectResponse
    {
        $request->validate([
            'avatar' => ['required', 'image', 'max:2048'],
        ]);

        $user = $request->user();

        // Elimina el avatar existente
        if ($user->avatar_path) {
            Storage::disk('public')->delete($user->avatar_path);
        }

        // Guarda el nuevo avatar
        $path = $request->file('avatar')->store('avatars', 'public');

        // Guarda la ruta en la BD
        $user->update(['avatar_path' => $path]);

        return back()->with('status', 'avatar-updated');
    }
}
```

En la plantilla usa `Storage::url()` para obtener la URL.

```blade theme={null}
<img src="{{ Storage::disk('public')->url($user->avatar_path) }}" alt="Avatar">
```


## Related topics

- [Laravel AI SDK](/es/ai-sdk.md)
- [MongoDB](/es/mongodb.md)
- [Actualización de Laravel 8 a 9](/es/blog/upgrade-8-to-9.md)
- [Autenticación OAuth 2.0 - Google Sheets API for Laravel](/es/packages/laravel-google-sheets/oauth.md)
- [Búsqueda](/es/search.md)
