Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sxyazi/yazi/llms.txt

Use this file to discover all available pages before exploring further.

Yazi includes a Virtual Filesystem (VFS) that abstracts file operations across different storage backends. This enables seamless remote file management alongside local files using the same interface.

Overview

The VFS provides a unified API for file operations regardless of the underlying storage:
  • Local files - Standard filesystem operations
  • SFTP - SSH-based remote file access
  • Archive files - Navigate inside archives (future)
  • Custom providers - Extensible for new backends

Architecture

The VFS is implemented through a provider system in yazi-vfs/:
pub trait Provider {
    type File;
    type Gate;
    type ReadDir;
    type UrlCow;

    async fn absolute(&self) -> io::Result<Self::UrlCow>;
    async fn canonicalize(&self) -> io::Result<UrlBuf>;
    async fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>;
    async fn create(&self) -> io::Result<RwFile>;
    async fn metadata(&self) -> io::Result<Cha>;
    async fn read_dir(self) -> io::Result<Self::ReadDir>;
    async fn remove_file(&self) -> io::Result<()>;
    async fn rename<P>(&self, to: P) -> io::Result<()>;
    // ... and more
}

URL Scheme

Yazi uses URL-based paths to identify resources:
pub enum Url<'a> {
    Regular(&'a Path),           // Local: /home/user/file.txt
    Search { .. },               // Search: fzf://query
    Archive { .. },              // Archive: file.zip!/internal/path
    Sftp { loc, domain },        // SFTP: sftp://hostname/path
}

Example URLs

  • Local: /home/user/documents/file.txt
  • SFTP: sftp://server.example.com/var/www/
  • Archive: /home/user/archive.zip!/folder/file.txt (future)
  • Search: fzf://search-query

SFTP Support

Yazi provides full SFTP support for remote file management over SSH.

Configuration

Configure SFTP hosts in ~/.config/yazi/yazi.toml:
[[vfs.sftp]]
name = "production"
host = "server.example.com"
port = 22
user = "username"
# Authentication via SSH agent or key file

[[vfs.sftp]]
name = "staging"
host = "192.168.1.100"
port = 2222
user = "deploy"

Connecting

Connect to SFTP servers using the cd command:
# In Yazi, press : to enter command mode
cd sftp://production/var/www
cd sftp://staging/home/deploy
Yazi will:
  1. Authenticate via SSH agent or key file
  2. Establish SFTP connection
  3. Navigate to the specified path
  4. Display remote files like local ones

Connection Management

SFTP connections are pooled and reused for efficiency:
impl<'a> Sftp<'a> {
    pub(super) async fn op(&self) -> io::Result<deadpool::managed::Object<Conn>> {
        Conn { name: self.name, config: self.config }.roll().await
    }
}
Connections are:
  • Persistent - Reused across operations
  • Automatic reconnect - Handle network interruptions
  • Concurrent - Multiple operations use same connection
  • Pooled - Connection pool prevents resource exhaustion

Supported Operations

All standard file operations work over SFTP:

Reading

  • List directories
  • Read file contents
  • Get file metadata
  • Follow symbolic links
  • Calculate directory sizes

Writing

  • Create files and directories
  • Copy files (SFTP → SFTP, local → SFTP, SFTP → local)
  • Move/rename files
  • Delete files and directories
  • Create symbolic links
  • Create hard links (if supported by server)

Bulk Operations

  • Copy multiple files
  • Bulk rename
  • Multi-select operations
  • Progress tracking for large transfers

Implementation

The SFTP provider is implemented in yazi-vfs/src/provider/sftp/sftp.rs:78:
async fn copy<P>(&self, to: P, attrs: yazi_fs::provider::Attrs) -> io::Result<u64> {
    let to = to.as_path().as_unix()?;
    let attrs = super::Attrs(attrs).try_into().unwrap_or_default();

    let op = self.op().await?;
    let from = op.open(self.path, Flags::READ, &Attrs::default()).await?;
    let to = op.open(to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, &attrs).await?;

    let mut reader = BufReader::with_capacity(524288, from);
    let mut writer = BufWriter::with_capacity(524288, to);
    let written = tokio::io::copy(&mut reader, &mut writer).await?;

    writer.flush().await?;
    if !attrs.is_empty() {
        writer.get_ref().fsetstat(&attrs).await.ok();
    }

    writer.shutdown().await.ok();
    Ok(written)
}
Note the 512KB buffers for optimal throughput.

Progress Tracking

Large SFTP transfers show progress:
fn copy_with_progress<P, A>(&self, to: P, attrs: A) 
    -> io::Result<Receiver<io::Result<u64>>> 
{
    let to = UrlBuf::Sftp {
        loc: LocBuf::saturated(to.as_path().to_unix_owned()?, SchemeKind::Sftp),
        domain: self.name.intern(),
    };
    let from = self.url.to_owned();

    Ok(crate::provider::copy_with_progress_impl(from, to, attrs.into()))
}
Progress updates stream via channel for real-time UI updates.

Cross-Provider Operations

The VFS seamlessly handles operations across different providers:

Local ↔ SFTP

pub async fn copy<U, V>(from: U, to: V, attrs: Attrs) -> io::Result<u64> {
    let (from, to) = (from.as_url(), to.as_url());

    match (from.kind().is_local(), to.kind().is_local()) {
        (true, true) => Local::new(from).await?.copy(to.loc(), attrs).await,
        (false, false) if from.scheme().covariant(to.scheme()) => {
            Providers::new(from).await?.copy(to.loc(), attrs).await
        }
        (true, false) | (false, true) | (false, false) => 
            super::copy_impl(from, to, attrs).await,
    }
}
Yazi automatically:
  • Routes to appropriate provider
  • Handles different path formats
  • Preserves attributes when possible
  • Shows unified progress tracking

Capabilities System

Providers declare their capabilities:
pub struct Capabilities {
    pub symlink: bool,
}

fn capabilities(&self) -> Capabilities { 
    Capabilities { symlink: true } 
}
This enables:
  • Feature detection
  • Graceful degradation
  • Provider-specific optimizations
  • Error prevention

Metadata Handling

The VFS normalizes metadata across providers:
pub async fn metadata<U>(url: U) -> io::Result<Cha> {
    Providers::new(url.as_url()).await?.metadata().await
}
Cha (characteristics) includes:
  • File type (regular, directory, symlink)
  • Permissions
  • Size
  • Modified time
  • Link target (for symlinks)

Error Handling

VFS operations return standard I/O errors:
pub enum ErrorKind {
    NotFound,
    PermissionDenied,
    ConnectionRefused,
    ConnectionReset,
    ConnectionAborted,
    NotConnected,
    AlreadyExists,
    Interrupted,
    InvalidInput,
    TimedOut,
    Unsupported,
    // ...
}
Errors are:
  • Consistent - Same error types across providers
  • Actionable - Clear error messages
  • Recoverable - Retry logic for network issues

Directory Reading

Directory listing works uniformly:
pub async fn read_dir<U>(url: U) -> io::Result<ReadDir> {
    Providers::new(url.as_url()).await?.read_dir().await
}

// Usage
let mut it = read_dir(url).await?;
while let Some(entry) = it.next().await? {
    println!("{}", entry.name());
}

Performance Optimizations

The VFS includes several performance optimizations:

Connection Pooling

SFTP connections are pooled to avoid reconnection overhead.

Buffered I/O

Large buffers (512KB) maximize throughput:
let mut reader = BufReader::with_capacity(524288, from);
let mut writer = BufWriter::with_capacity(524288, to);

Async Operations

All I/O is non-blocking and runs on Tokio runtime.

Parallel Transfers

Multiple files transfer simultaneously when possible.

Limitations

Current Limitations

  • Trash not supported - SFTP files are permanently deleted
  • No compression - Files transfer uncompressed
  • SFTP only - Other protocols (FTP, S3, etc.) not yet supported

Future Plans

  • Additional protocols (FTP, WebDAV, S3)
  • Archive mounting (browse ZIP/TAR as directories)
  • Custom search providers
  • Cloud storage integration

Advanced Usage

Case-Insensitive Paths

The VFS handles case-insensitive filesystems:
pub async fn casefold<U>(url: U) -> io::Result<UrlBuf> {
    Providers::new(url.as_url()).await?.casefold().await
}
This resolves ambiguous paths on case-insensitive systems.

Absolute Path Resolution

pub async fn absolute<'a, U>(url: &'a U) -> io::Result<UrlCow<'a>> {
    Providers::new(url.as_url()).await?.absolute().await
}
Converts relative paths to absolute.

Identity Checking

pub async fn identical<U, V>(a: U, b: V) -> io::Result<bool> {
    // Check if two URLs refer to the same file
}
Handles hard links and bind mounts correctly.

See Also

Build docs developers (and LLMs) love