I am writing a custom module in which data is written to a file and the file is stored in a private directory folder.

In order to do so I have created a private directory and assigned required permissions to it(can confirm it is writable, tested it by creating a new content type from UI and added a file upload filed and able to save uploaded file to that directory). But my problem is I can't save the file to that directory programmatically.

I get an error stating :

The data could not be saved because the destination /var/www/html/sites/sitename/files/private/file.json is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.

My private directory is located in files directory as a subdirectory and named as private, I have configured it in settings.php and can view it in https://example.com/admin/config/media/file-system of course cleared the cache after configuring the private directory in settings.php.

I can save file into public directory using the following code:

    $output = Json::encode($data);

    $directory = file_default_scheme() . '://userslist';

    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

    $fileLocation = $directory.'/'.JSON_FILENAME;

    $file = file_save_data($output, $fileLocation, FILE_EXISTS_REPLACE);

But I can't save in subfolder in public directory using the following code :

    $directory = \Drupal::service('file_system')->realpath("public://userslist");

Same is the case with private directory :

    $directory = \Drupal::service('file_system')->realpath("private://");

I can even save data using the following code :

    $directory = file_default_scheme() . '://private';

as the private directory is present as a subdirectory in public directory hence assigned manually public directory path above.

But can't save using the realpath which obtains the private or public file directory path dynamically.

Can anyone help me out to solve the issue?

I tried the functionality in two different system environments one is Linux and other is windows and I get the same error as above.


Edit 1 : I hade made some trail and errors and got the following results but still cant find a correct way to save the file to private directory.

    // My private file configuration in settings.php 
    $settings['file_private_path'] = 'sites/sitename/files/private';

    $directory = file_default_scheme() . '://userslist';
    dpm($directory); // public://userslist
    // File is writable to the directory and can be accessed through browser.

    $directory = PrivateStream::basePath(). '://userslist'; ;
    dpm($directory); // sites/sitename/files/private://userslist

    $directory = PrivateStream::basePath(). '/userslist'; ;
    dpm($directory); // sites/sitename/files/private/userslist

    $directory = drupal_realpath('public://userslist');
    dpm($directory);  // /var/www/html/sites/sitename/files/userslist

    $directory = \Drupal::service('stream_wrapper_manager')->getViaUri('private://userslist')->getUri();
    dpm($directory); // private://userslist

    // File is writable to the directory and can't be accessed through browser.
    // normally a file which is present in private directory and accessed through browser will have the following url pattern 

    // https://example.com/sitename/default/files/private/userslist/file.json
    // but it has the following pattern.
    // https://example.com/sitename/system/files/userslist/file.json

    $directory = \Drupal::service('file_system')->realpath(file_default_scheme().'://userslist');
    dpm($directory); // /var/www/html/sites/sitename/files/userslist

    $directory = \Drupal::service('file_system')->realpath("private://userslist") ;
    dpm($directory); // /var/www/html/sites/sitename/files/private/userslist

    $directory = \Drupal::service('file_system')->realpath("public://userslist");
    dpm($directory); // /var/www/html/sites/sitename/files/userslist

    $directory = file_default_scheme() . '://private/userslist';
    dpm($directory); //public://private/userslist

3 Answers 3


Your code is almost there, you don't need realpath really. Constructing the $directory variable as a stream wrapper URI (i.e. private://SOME_PATH) is the standard way.

You get that error from file_valid_uri() - which is called by file_save_data() - and expects a Stream Wrapper $destination parameter (no absolute/relative paths).

In general, I would suggest you keep away from absolute/relative file paths and use stream wrappers URI syntax, the functions you are using support and/or expect them.

The following code will allow you to use the private filesystem:

use Drupal\Core\StreamWrapper\PrivateStream;
$file_save_path_stream_directory =  'private://userslist';
file_prepare_directory($file_save_path_stream_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

$fileLocation = $file_save_path_stream_directory . '/' . JSON_FILENAME;

$file = file_save_data($output, $fileLocation, FILE_EXISTS_REPLACE);    

Good luck!

P.S.: Do read this Change Record (https://www.drupal.org/node/2392959), holds important relative information.

  • Thanks for the comment but still I get the same error after using your code . If I print $destination variable I get the correct path but still the error persists. Any idea why its happening ? Nov 6, 2018 at 11:48
  • You're welcome. Just make sure you don't use realpath() . Is the code in your question still up to date? Nov 6, 2018 at 12:03
  • Yes my code is up tp date other your suggested code. Nov 6, 2018 at 13:06
  • What does the printing of $destination look like? Nov 6, 2018 at 13:11
  • I added dpm's of all my trail and errors in my question edit. Can you please have alook into it ? @Stefanos Petrakis Nov 8, 2018 at 7:37

While this is an old question I would like to point out that this is now deprecated. We should use \Drupal::service('file_system') now instead.

As file_prepare_directory now states:

function file_prepare_directory(&$directory, $options = FileSystemInterface::MODIFY_PERMISSIONS) {
  @trigger_error('file_prepare_directory() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::prepareDirectory(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
  return \Drupal::service('file_system')->prepareDirectory($directory, $options);

Please see https://www.drupal.org/node/3006851 for more information.


I agree with @Stefanos Petrakis you don't need realpath because,file_valid_uri()will do the notice, but $directory = PrivateStream::basePath() . '://userslist'; will gives you the path of private file it's not a valid uri either.

Try the following.

  $directory = \Drupal::service('stream_wrapper_manager')->getViaUri('private://userslist')->getUri();
  file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  $fileLocation = $directory . '/' . JSON_FILENAME;
  $file = file_save_data($output, $fileLocation, FILE_EXISTS_REPLACE);
  • Thank you for the answer, it creates the file in the private directory but while accessing it through brower it shows different path and shows access denined error while viewing it. It is showing the following URL :example.com/sitename/system/files/file.json but it should actually show : example.com/sitename//files/private/file.json Nov 6, 2018 at 13:09
  • I used $directory = \Drupal::service('stream_wrapper_manager')->getViaUri('private://')->getUri(); Nov 6, 2018 at 13:10
  • it works for me it create a file inside files/private/userlist with the following config $settings['file_private_path'] = 'sites/default/files/private';
    – berramou
    Nov 6, 2018 at 14:25
  • you can not access to private files through browser .
    – berramou
    Nov 6, 2018 at 14:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.