WordPress plugins and themes often need to read configuration files, write logs, create cache files, or copy assets to the uploads directory. Using PHP’s native file_get_contents(), file_put_contents(), and fopen() functions works in most environments, but it bypasses WordPress’s abstraction layer and can fail silently on hosts that use FTP-based file systems, restrictive open_basedir settings, or SSH-only write access. The correct approach is the WP_Filesystem API — WordPress’s built-in abstraction that transparently handles direct, FTP, SSH2, and other file system transports depending on the hosting environment. Using it ensures your code works on shared hosting, VPS, managed WordPress hosts, and WP Engine alike. The API is accessed via the global $wp_filesystem object which must be initialised with WP_Filesystem() before use. This article covers the full initialisation pattern, the most common read/write operations, and how to handle environments that require credentials.
Problem: Your plugin writes files using native PHP functions. On some hosting environments with FTP or SSH file system modes the writes fail silently, or the written files are owned by the wrong user.
Solution: Use the WP_Filesystem API. Initialise it with WP_Filesystem(), check the transport method, and use $wp_filesystem methods for all file operations.
Full initialisation and basic read/write example:
<?php
function my_plugin_write_file( $file_path, $content ) {
global $wp_filesystem;
// Initialise the filesystem — returns false if credentials are needed
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$creds = request_filesystem_credentials( site_url() );
if ( ! WP_Filesystem( $creds ) ) {
return new WP_Error( 'filesystem_error', 'Could not initialise WP_Filesystem.' );
}
// Write the file
if ( ! $wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ) ) {
return new WP_Error( 'write_error', 'Could not write file: ' . $file_path );
}
return true;
}
function my_plugin_read_file( $file_path ) {
global $wp_filesystem;
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
WP_Filesystem();
if ( ! $wp_filesystem->exists( $file_path ) ) {
return false;
}
return $wp_filesystem->get_contents( $file_path );
}
Common $wp_filesystem methods and their PHP equivalents:
<?php
// Check if a file or directory exists
$wp_filesystem->exists( $path ); // file_exists()
// Read a file into a string
$wp_filesystem->get_contents( $path ); // file_get_contents()
// Write a string to a file (creates if not exists, overwrites if exists)
$wp_filesystem->put_contents( $path, $content, FS_CHMOD_FILE ); // file_put_contents()
// Create a directory
$wp_filesystem->mkdir( $path, FS_CHMOD_DIR ); // mkdir()
// Delete a file
$wp_filesystem->delete( $path ); // unlink()
// Copy a file
$wp_filesystem->copy( $source, $dest ); // copy()
// Check the file system transport currently in use
echo $wp_filesystem->method; // 'direct', 'ftpext', 'ftpsockets', 'ssh2'
NOTE: On most standard hosting setups WP_Filesystem() uses the direct transport, which behaves identically to native PHP file functions. You only notice the difference on hosts that require FTP credentials for file writes. If your plugin only ever runs on direct-transport environments (e.g. a managed WordPress host), using $wp_filesystem still makes sense as a forward-compatible habit, and it costs nothing in performance.