Library / SDK
jlaffaye/ftp avatar
jlaffaye/ftp

jlaffaye/ftp: a Go package for basic FTP transfers

FTP client package for Go

1,404 stars384 forksGoISC

At a glance

What is it?
jlaffaye/ftp is a small Go client package for connecting to FTP servers, logging in, uploading files, and downloading files. It fits applications that already have a Go codebase, but the repository documents only anonymous login and basic transfer workflows.
Who is it for?
Adopt jlaffaye/ftp when a Go application needs a small FTP client for ordinary login and transfer operations without building an FTP protocol stack. Do not choose it solely for FTPS, directory automation, resume behavior, or production credential handling, because the repository documentation does not establish those capabilities.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 62 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why jlaffaye/ftp exists

jlaffaye/ftp solves a narrow but concrete problem for Go developers: connecting a Go program to an FTP server and moving files through a package API. The repository identifies itself as "A FTP client package for Go," and its example focuses on opening a connection, submitting credentials, sending a file, and reading one back. That makes it more relevant than a command-line transfer utility when FTP access must be embedded in an existing service, scheduled job, import process, or administrative tool.

The documented audience is therefore Go developers who control both sides of the application architecture. A developer can add the module without adopting a separate executable or fitting an external command into the surrounding control flow. The README does not describe a graphical interface, a server component, a deployment daemon, or a general-purpose file browser. Its scope is the client side of FTP communication.

The basic examples are intentionally small. They use `ftp.Dial`, `Login`, `Stor`, `Retr`, and `Quit`, with ordinary Go values such as `time.Duration`, `bytes.Buffer`, and `io.Reader`-style values implied by the signatures used in the README. This is favorable for readers who already know Go and the `io` package. A reader seeking a cross-platform desktop application should look elsewhere because no such delivery format is documented.

The connection and transfer path exposed by jlaffaye/ftp

The documented flow begins with a dial operation that includes an address and a five-second timeout option:

go
c, err := ftp.Dial("ftp.example.org:21", ftp.DialWithTimeout(5*time.Second))
if err != nil {
    log.Fatal(err)
}

The address follows the familiar host-and-port form used by the README example. The option is applied when the client connection is established. The immediate result is a client value and an error, so callers must inspect connection failure before proceeding.

After dialing, the program submits a username and password through `Login`. The README uses the conventional anonymous pair, which is suitable for an FTP server configured to permit anonymous access. It does not describe certificate setup, secure negotiation, credential storage, or permission-scoped accounts. Those omissions matter because the visible example proves client-side credential submission, not safe handling of production secrets.

Once authenticated, the README presents uploads through `Stor` and downloads through `Retr`. `Stor` receives a remote filename and an in-memory buffer. `Retr` returns a readable file value that the caller closes after consuming its contents. Based on these examples, the package maps a local Go byte source to an FTP file store and maps a remote FTP file to a readable local-side stream. The examples do not specify temporary-file handling, transfer cancellation, resume support, or the exact way partial transfer errors reach the caller.

The repository layout also contains separate Go files for connection logic, parsing, response scanning, status handling, and file walking. Based on those names, protocol parsing, response scanning, status interpretation, and recursive or directory traversal are separate concerns in the source. However, the README does not provide the public API for directory listings or walkers, so their exact behavior cannot be confirmed from the documented examples.

Installing and using jlaffaye/ftp for the first transfer

The README gives one installation command. It adds the module to a Go project so the package can be imported under `github.com/jlaffaye/ftp`.

bash
go get -u github.com/jlaffaye/ftp

The repository's `go.mod` declares `go 1.20`, which establishes the module's Go version. The README then points readers to `https://pkg.go.dev/github.com/jlaffaye/ftp` for documentation. After installation, the first minimal connection can follow the README example. This program attempts to connect to port `21`, waits up to five seconds for the dial operation, and checks the returned error:

go
c, err := ftp.Dial("ftp.example.org:21", ftp.DialWithTimeout(5*time.Second))
if err != nil {
    log.Fatal(err)
}

A real server address must replace the documented placeholder. The next step is authentication, again using the exact method and values shown by the project:

go
err = c.Login("anonymous", "anonymous")
if err != nil {
    log.Fatal(err)
}

The example uses anonymous credentials, so it can complete only when the selected FTP service accepts them. After the connection is no longer needed, call `Quit` and handle its error:

go
if err := c.Quit(); err != nil {
    log.Fatal(err)
}

The README describes this as closing out the FTP connection, although it does not document what happens if `Quit` fails. Applications should not infer any retry or recovery policy from this example.

Uploading and reading files with in-memory data

For a first upload, the README uses `bytes.NewBufferString` to construct local content and passes it to `Stor` under the remote name `test-file.txt`. The method returns an error, and the example terminates the program if that error is non-nil.

go
data := bytes.NewBufferString("Hello World")
err = c.Stor("test-file.txt", data)
if err != nil {
    panic(err)
}

This example demonstrates a small in-memory transfer rather than a file-path helper. It does not establish whether callers can pass `os.File`, an upload reader, or some other reader type without copying the entire source into memory. The source signature is not included in the cleaned README, so that point should be checked in the package documentation.

The matching download example requests `test-file.txt` with `Retr`, checks the error immediately, and closes the returned reader with `defer r.Close()`. Reading then consumes the response into a byte slice:

go
r, err := c.Retr("test-file.txt")
if err != nil {
    panic(err)
}
defer r.Close()

buf, err := ioutil.ReadAll(r)
println(string(buf))

The README does not check the error returned by `ioutil.ReadAll`, and `println(string(buf))` is demonstration code rather than a production logging pattern. It also does not limit the response size or show how to stream a large download directly to disk. That makes the example easy to understand, but incomplete for deciding how the package handles large files. Review the API documentation before choosing a buffering or streaming pattern.

Timeouts, errors, and limits in the documented API

The clearest operational control in the README is `ftp.DialWithTimeout(5*time.Second)`, applied to connection establishment. That prevents the initial dial example from relying on an unspecified default timeout. The README does not document separate transfer timeouts, keepalive intervals, idle timeouts, maximum file sizes, retry counts, or configurable buffers, so none should be assumed.

Error handling is explicit at each visible step. `Dial` returns an error, `Login` returns an error, `Quit` returns an error, and `Stor` returns an error. `Retr` also returns an error before its reader is used. This is a good base for caller-controlled failure handling, but the snippets do not show whether errors distinguish network failures, authentication rejection, missing files, permission errors, or malformed server responses.

The README also uses `panic` in the upload and download examples. That is adequate for illustrating API calls, but it gives the surrounding program no recovery path. Production code would need to decide whether a transfer failure should stop a job, trigger a retry, record a partial result, or continue with another file. The project documentation does not establish those higher-level policies.

The package should therefore be judged as a protocol client, not as a complete transfer scheduler. There is no documented concurrency model, no example of running several transfers on one client, and no guidance about whether separate connections are required. Those are concrete adoption checks rather than hypothetical concerns.

FTP versus direct transport with Go's standard library

A real alternative is to use a protocol directly supported by Go's standard library, such as HTTP or HTTPS through `net/http`, when the remote system offers that protocol. The difference is architectural: `jlaffaye/ftp` packages the older FTP exchange behind methods such as `Dial`, `Login`, `Stor`, and `Retr`, while `net/http` is a standard library designed around HTTP requests and responses. HTTP deployments commonly integrate with existing request middleware and URL-oriented configuration, but the project repository does not describe an HTTP endpoint for any FTP migration path.

Another alternative is implementing FTP protocol handling directly with Go networking primitives. That avoids adding this package's FTP abstraction, but it places connection lifecycle, command parsing, response handling, data transfer, and server compatibility in the application itself. The repository layout suggests that jlaffaye/ftp already separates several of those concerns across `ftp.go`, `parse.go`, `scanner.go`, `status.go`, and `walker.go`. It would still be a poor choice if its documented API omits a required behavior, because replacing a missing package feature can become as expensive as implementing the missing portion yourself.

For servers that provide only FTP, switching to HTTP may not be possible. In that situation, the comparison is between an existing FTP package and application-owned protocol code. The README documents enough basic operations to begin, but not enough to determine coverage for enterprise servers, passive and active data modes, or server-specific authentication policies.

Release state, Go requirements, and the ISC license

The repository is not archived, and the last push was on 2026-08-22. Recent releases include `v0.2.4` and `v0.2.3` on 2026-08-22, followed earlier by `v0.2.2` on 2026-07-29. Those dated releases provide evidence of recent project output, but they do not by themselves define compatibility guarantees or establish how quickly issues receive responses.

The module declares `go 1.20`, so adopting developers should account for that minimum language and toolchain requirement. The README uses `github.com/stretchr/testify v1.12.1` in `go.mod`, while `go.yaml.in/yaml/v3 v3.0.5` is marked as an indirect dependency. These files do not explain whether either dependency participates in runtime FTP behavior or only in tests and transitive module resolution.

The project uses the ISC license. That designation is relevant when reviewing redistribution and notice obligations, but this review does not interpret the license or provide legal advice. The actual obligations should be read in the repository's `LICENSE` file and checked against the application's packaging and distribution model. The maintenance cost also includes deciding when to move beyond the current release and reviewing changes against the server behavior the application depends on.

Editorial conclusion

Adopt jlaffaye/ftp when a Go application needs a small FTP client for ordinary login and transfer operations without building an FTP protocol stack. Do not choose it solely for FTPS, directory automation, resume behavior, or production credential handling, because the repository documentation does not establish those capabilities. Before adoption, inspect the package documentation for the exact connection, TLS, listing, timeout, and error-handling contracts needed by the target server, and review the ISC license in `LICENSE` for your distribution requirements.

Frequently asked questions

How do I install jlaffaye/ftp in a Go project?

The README gives `go get -u github.com/jlaffaye/ftp`. The repository's `go.mod` declares Go version 1.20.

How do I connect and log in to an FTP server with jlaffaye/ftp?

The documented flow uses `ftp.Dial` with an address and `ftp.DialWithTimeout`, then calls `Login` with a username and password. The example uses `anonymous` for both values.

How do I upload and download a file with jlaffaye/ftp?

The README passes an in-memory buffer to `Stor` for an upload. It uses `Retr` to obtain a reader for a remote file, closes that reader, and reads its contents into a byte slice.

Official sources

  1. Issues
  2. jlaffaye/ftp on GitHub
  3. License: ISC
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jlaffaye-ftp.svg)](https://hysenlabs.com/projects/jlaffaye-ftp)

Was this useful?