58

I have a PDF file stored in app/storage/, and I want authenticated users to be able to view this file. I know that I can make them download it using

return Response::download($path, $filename, $headers);

but I was wondering if there is a way to make them view the file directly in the browser, for example when they are using Google Chrome with the built-in PDF viewer. Any help will be appreciated!

Luis Aceituno
  • 859
  • 1
  • 6
  • 13

11 Answers11

130

Update for 2017

As of Laravel 5.2 documented under Other response types you can now use the file helper to display a file in the user's browser.

return response()->file($pathToFile);

return response()->file($pathToFile, $headers);

Source/thanks to below answer

Outdated answer from 2014

You just need to send the contents of the file to the browser and tell it the content type rather than tell the browser to download it.

$filename = 'test.pdf';
$path = storage_path($filename);

return Response::make(file_get_contents($path), 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'inline; filename="'.$filename.'"'
]);

If you use Response::download it automatically sets the Content-Disposition to attachment which causes the browser to download it. See this question for the differences between Content-Disposition inline and attachment.

Edit: As per the request in the comments, I should point out that you'd need to use Response at the beginning of your file in order to use the Facade.

use Response;

Or the fully qualified namespace if Response isn't aliased to Illuminate's Response Facade.

Ben Swinburne
  • 25,669
  • 10
  • 69
  • 108
  • your method is not working for `.txt` file. It downloads the file. – Faisal Ahsan Jan 31 '16 at 12:18
  • Presumably you changed the content type to text/plain ? – Ben Swinburne Jan 31 '16 at 12:27
  • 1
    Just to help, remember you can use the `storage_path()` helper without the concatenation: `$path = storage_path('app/file.txt')`. See https://laravel.com/docs/5.2/helpers#method-storage-path – AARTT Mar 16 '16 at 12:36
  • @BenSwinburne which `Response` facade is this supposed to be; `Illuminate\Http\Response`? I added my answer below for alternatives and because it works in 5.1 / 5.2 due to the new `response()->file()` helper, but it seems in Laravel 5.1+ there is no `make` method anymore … – AARTT Mar 16 '16 at 22:20
  • 1
    `Response => Illuminate\Support\Facades\Response::class` which gets an instance of `Illuminate\Contracts\Routing\ResponseFactory` which is bound in the container in `Illuminate\Routing\RoutingServiceProvider::registerResponseFactory()` to `Illuminate\Routing\ResponseFactory` which does indeed have a `make` method... I just tried `return Response::make('test')` in L5.2 and it works as expected. You'd need to have `use Response` at the top of your file. The response helper simply does `app(ResponseFactory::class)->make(...)`. – Ben Swinburne Mar 16 '16 at 22:41
  • Shame on me. Stupid. – AARTT Mar 17 '16 at 10:34
  • How so? Perhaps elaborate on what caused your issue in order to help others who read these comments. – Ben Swinburne Mar 17 '16 at 10:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106574/discussion-between-aartt-and-ben-swinburne). – AARTT Mar 17 '16 at 10:35
  • Your Content-Disposition is wrong. It should be `'Content-Disposition' => 'inline; filename="'.$filename.'"'` and that's assuming the filename does not contain quotes. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 – Tobia Apr 22 '16 at 08:33
  • I try to avoid using Aliases because when you have a big/complex application, either they get accidentally replaced and can cause errors; or you lose the flexibility of Dependency Inversion – Ash Apr 22 '16 at 10:01
  • @ash That's just how Laravel is set up by default so it's a working example. The response class can also be newed up, instantiated by the container using type hinting on the controller method or made directly in the controller using the container. – Ben Swinburne Apr 22 '16 at 10:09
  • @BenSwinburne yes, but my comment is not saying you are wrong, only offering additional advice to anyone who lands here regarding the use of Aliases. Your comment regarding Di doesn't change my point on Aliases - i'm only stating they add an additional layer of complexity which should be avoided. Aliases can be set in Service Providers and so to assume they are only defined inside `config/app.php` is misleading - so I'm noting to people who come here that's bad practice – Ash Apr 22 '16 at 10:14
  • Maybe its worth to note the `response()->file(...)` solution for Laravel 5.2 from MegaCookie (https://stackoverflow.com/a/37896507/2311074) for those who have a non-working mouse-wheel. – Adam Nov 28 '17 at 22:57
  • return response()->make( – Yevgeniy Afanasyev Jan 21 '19 at 02:15
  • 1
    If you want to display a file from specific disc, then you may get the path without helper like this: `$file = Storage::disk('business_documents')->path('/' . $filename);` – Adam Nov 14 '20 at 11:32
  • The solution of 2017 doesnt work for private cloud storages right? I use gcs, but I think the same is for s3, I had to get the file and create the response – SpaceDogCS Apr 27 '21 at 20:03
  • @SpaceDogCS I suppose you have two options. 1. like you say, download the contents and create a response in laravel. 2. When you upload the files to GCS/S3 set the same headers on the file used in the example - i can't speak for GCS but it works for s3. At the time of upload, the headers get set then when you access the s3/cloudfront url the headers are set and it shows the file. In the case of your laravel response, you could return a redirect response to the cloud storage url. You also _might_ be able to set extra headers on the redirect response but i doubt that – Ben Swinburne Apr 27 '21 at 20:31
  • If using the `Storage` facade to get the file from S3 or other storage, you can use `response` instead of `download`. This will show the file in the browser. `return Storage::disk('s3')->response($file->path, $file->name);` – eriktobben Jul 24 '21 at 13:46
21

Since Laravel 5.2 you can use File Responses
Basically you can call it like this:

return response()->file($pathToFile);

and it will display files as PDF and images inline in the browser.

MegaCookie
  • 5,017
  • 3
  • 21
  • 25
17

In Laravel 5.5 you can just pass "inline" as the disposition parameter of the download function:

return response()->download('/path/to/file.pdf', 'example.pdf', [], 'inline');
Adam Albright
  • 5,917
  • 1
  • 21
  • 14
7

As of laravel 5.5 if the file is stored on a remote storage

return Storage::response($path_to_file);

or if it's locally stored you can also use

return response()->file($path_to_file);

I would recommend using the Storage facade.

Mihai Crăiță
  • 3,328
  • 3
  • 25
  • 37
6

Ben Swinburne's answer is absolutely correct - he deserves the points! For me though the answer left be dangling a bit in Laravel 5.1 which made me research — and in 5.2 (which inspired this answer) there's a a new way to do it quickly.

Note: This answer contains hints to support UTF-8 filenames, but it is recommended to take cross platform support into consideration !

In Laravel 5.2 you can now do this:

$pathToFile = '/documents/filename.pdf'; // or txt etc.

// when the file name (display name) is decided by the name in storage,
// remember to make sure your server can store your file name characters in the first place (!)
// then encode to respect RFC 6266 on output through content-disposition
$fileNameFromStorage = rawurlencode(basename($pathToFile));

// otherwise, if the file in storage has a hashed file name (recommended)
// and the display name comes from your DB and will tend to be UTF-8
// encode to respect RFC 6266 on output through content-disposition
$fileNameFromDatabase = rawurlencode('пожалуйста.pdf');

// Storage facade path is relative to the root directory
// Defined as "storage/app" in your configuration by default
// Remember to import Illuminate\Support\Facades\Storage
return response()->file(storage_path($pathToFile), [
    'Content-Disposition' => str_replace('%name', $fileNameFromDatabase, "inline; filename=\"%name\"; filename*=utf-8''%name"),
    'Content-Type'        => Storage::getMimeType($pathToFile), // e.g. 'application/pdf', 'text/plain' etc.
]);

And in Laravel 5.1 you can add above method response()->file() as a fallback through a Service Provider with a Response Macro in the boot method (make sure to register it using its namespace in config/app.php if you make it a class). Boot method content:

// Be aware that I excluded the Storage::exists() and / or try{}catch(){}
$factory->macro('file', function ($pathToFile, array $userHeaders = []) use ($factory) {

    // Storage facade path is relative to the root directory
    // Defined as "storage/app" in your configuration by default
    // Remember to import Illuminate\Support\Facades\Storage
    $storagePath         = str_ireplace('app/', '', $pathToFile); // 'app/' may change if different in your configuration
    $fileContents        = Storage::get($storagePath);
    $fileMimeType        = Storage::getMimeType($storagePath); // e.g. 'application/pdf', 'text/plain' etc.
    $fileNameFromStorage = basename($pathToFile); // strips the path and returns filename with extension

    $headers = array_merge([
        'Content-Disposition' => str_replace('%name', $fileNameFromStorage, "inline; filename=\"%name\"; filename*=utf-8''%name"),
        'Content-Length'      => strlen($fileContents), // mb_strlen() in some cases?
        'Content-Type'        => $fileMimeType,
    ], $userHeaders);

    return $factory->make($fileContents, 200, $headers);
});

Some of you don't like Laravel Facades or Helper Methods but that choice is yours. This should give you pointers if Ben Swinburne's answer doesn't work for you.

Opinionated note: You shouldn't store files in a DB. Nonetheless, this answer will only work if you remove the Storage facade parts, taking in the contents instead of the path as the first parameter as with the @BenSwinburne answer.

Community
  • 1
  • 1
AARTT
  • 503
  • 5
  • 8
  • Your Content-Disposition is wrong too. It should be `'Content-Disposition' => 'inline; filename="'.$filename.'"'` and that's assuming the filename does not contain quotes. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 – Tobia Apr 22 '16 at 08:35
  • @Tobia thank you for the input ! The quotes matter if the filename contains spaces, reserved or non `ASCII` / `ISO-8859-1` chars. There is a `filename*` alternative which will require encoding as specified in [RFC 5987](https://tools.ietf.org/html/rfc5987). [RFC 6266 Sec-5 has more examples](https://tools.ietf.org/html/rfc6266#section-5). However, respect ing [cross platform file name limitations](https://en.wikipedia.org/wiki/Filename)(the older more restricted ones), it will not be an issue for most platforms. I will keep your suggestion in mind for a PHP solution and update my answer ! – AARTT Apr 23 '16 at 10:39
6

I am using Laravel 5.4 and response()->file('path/to/file.ext') to open e.g. a pdf in inline-mode in browsers. This works quite well, but when a user wants to save the file, the save-dialog suggests the last part of the url as filename.

I already tried adding a headers-array like mentioned in the Laravel-docs, but this doesn't seem to override the header set by the file()-method:

return response()->file('path/to/file.ext', [
  'Content-Disposition' => 'inline; filename="'. $fileNameFromDb .'"'
]);
kreilinger
  • 111
  • 1
  • 6
  • I'm facing the same issue and it's quite frustrating to be honest. Have you found any solution to the problem? :) – D. Petrov May 05 '19 at 21:03
  • I found out that it actually does override the header content, but `'Content-Disposition ` is simply a risky rule that is not applied by all browsers. I am afraid there is no way to change the tab name. – Adam Nov 14 '20 at 11:55
4

Retrieve File name first then in Blade file use anchor(a) tag like below shown. This would works for image view also.

<a href="{{ asset('storage/admission-document-uploads/' . $filename)}}" target="_blank"> view Pdf </a>;
Synapsido
  • 140
  • 3
  • 12
Shreekanth
  • 829
  • 1
  • 9
  • 12
3

Laravel 5.6.*

$name = 'file.jpg';

store on image or pdf

$file->storeAs('public/', $name );

download image or pdf

return response()->download($name);

view image or pdf

return response()->file($name);
Zoe
  • 27,060
  • 21
  • 118
  • 148
2

Ben Swinburne answer was so helpful.

The code below is for those who have their PDF file in database like me.

$pdf = DB::table('exportfiles')->select('pdf')->where('user_id', $user_id)->first();

return Response::make(base64_decode( $pdf->pdf), 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'inline; filename="'.$filename.'"',
]);

Where $pdf->pdf is the file column in database.

Diamond
  • 608
  • 7
  • 23
  • Your Content-Disposition is also wrong. It should be `'Content-Disposition' => 'inline; filename="'.$filename.'"'` and that's assuming the filename does not contain quotes. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 – Tobia Apr 22 '16 at 08:34
  • 1
    Anyone who stores their file contents in a database should go back to school/university and learn that's a terrible practice. – Ash Apr 22 '16 at 10:02
  • 3
    @ash I tend to agree, for practical reasons. (Managing a small DB and a file directory is easier than managing a huge DB that includes the file contents; think rsync, backup, etc.) But you shouldn't make blanket statements like that. Storing files in the database does have practical uses in many scenarios. – Tobia Apr 23 '16 at 18:47
  • @Tobia said it all, when you have a small DB then it is practical. It is not all the time you build large scale software, as for me I build some to automate my work. Thanks for the correction. – Diamond Apr 26 '16 at 06:48
0

Retrieving Files
$contents = Storage::get('file.jpg');

Downloading Files
return Storage::download('file.jpg');

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

Miraj Khandaker
  • 772
  • 10
  • 11
0

You can try this, it will open a pdf or other asset as a tab in your browser:

<a href="{{ URL::asset('/path/to/your/file.pdf') }}">Link to you doc</a>

Using the anchor tag means it behaves link any other link.

Robert Yeomans
  • 192
  • 1
  • 4