> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formae.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an SFTP plugin

> Tutorial: build a complete formae plugin that manages files on an SFTP server, from scaffold to conformance tests.

This tutorial walks you through building a complete formae plugin that manages files on an SFTP server. By the end, you'll have a production-ready plugin with:

* A `File` resource type with mutable and immutable properties
* Full CRUD operations (Create, Read, Update, Delete)
* Discovery to find existing files on the server
* Integration tests and conformance tests
* CI configuration for GitHub Actions

## Prerequisites

Before starting, ensure you have:

* **Go 1.25 or later**: `go version`
* **PKL CLI**: [Installation guide](https://pkl-lang.org/main/current/pkl-cli/index.html)
* **formae CLI**: installed and accessible in your PATH
* **Docker**: for running the test SFTP server

## Test server setup

We'll use a Docker-based SFTP server for development and testing:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
docker run -p 2222:22 -d --name sftp-test atmoz/sftp testuser:testpass:::upload
```

This creates an SFTP server with:

* **Host:** localhost
* **Port:** 2222
* **Username:** testuser
* **Password:** testpass
* **Writable directory:** /upload

Verify it's running:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
sftp -P 2222 testuser@localhost
# Enter password: testpass
# sftp> ls
# sftp> exit
```

## Tutorial structure

This tutorial uses test-driven development (TDD). For each CRUD operation, we'll:

1. Write a failing test
2. Implement the code to make it pass
3. Refactor if needed

| Section                                                                       | What You'll Learn                            |
| ----------------------------------------------------------------------------- | -------------------------------------------- |
| [01 - Project Scaffold](/plugin-development/tutorial/01-scaffold)             | Initialize the plugin project structure      |
| [02 - Resource Schema](/plugin-development/tutorial/02-schema)                | Define the File resource in PKL              |
| [03 - Target Configuration](/plugin-development/tutorial/03-target)           | Configure SFTP connection settings           |
| [04 - Plugin Configuration](/plugin-development/tutorial/04-configuration)    | Implement RateLimit and LabelConfig          |
| [05 - Create](/plugin-development/tutorial/05-create)                         | Implement file creation (async pattern)      |
| [06 - Read](/plugin-development/tutorial/06-read)                             | Implement file reading (NotFound semantics)  |
| [07 - Update](/plugin-development/tutorial/07-update)                         | Implement file updates                       |
| [08 - Delete](/plugin-development/tutorial/08-delete)                         | Implement file deletion (NotFound semantics) |
| [09 - List](/plugin-development/tutorial/09-list)                             | Implement List for file discovery            |
| [10 - Error Handling](/plugin-development/tutorial/10-error-handling)         | OperationErrorCode patterns                  |
| [11 - Conformance tests](/plugin-development/tutorial/11-conformance)         | Conformance tests against the formae agent   |
| [12 - CI Setup](/plugin-development/tutorial/12-ci-setup)                     | Setting up CI/CD pipelines                   |
| [13 - Local Testing](/plugin-development/tutorial/13-local-testing)           | Testing plugins locally                      |
| [14 - Observability](/plugin-development/tutorial/14-observability)           | Logging and metrics for plugins              |
| [15 - Real-world plugins](/plugin-development/tutorial/15-real-world-plugins) | Examples from production plugins             |

## The asyncsftp library

SFTP operations are inherently synchronous (blocking I/O), but formae's plugin SDK supports an async pattern for long-running operations. We provide an `asyncsftp` library that wraps the standard SFTP client with an async interface:

```go theme={"languages":{"custom":["/languages/pkl.json"]}}
// Start an upload (returns immediately)
opID, err := client.StartUpload(path, content)

// Poll for completion
status, err := client.GetStatus(opID)
if status.State == asyncsftp.StateCompleted {
    // Operation finished
}
```

This pattern is common when integrating with infrastructure APIs. The library handles the complexity internally, so your plugin code just uses the clean async interface.

## Source code

The complete source code for this tutorial is available at:

**[github.com/platform-engineering-labs/formae-plugin-sftp](https://github.com/platform-engineering-labs/formae-plugin-sftp)**

***

Ready? Let's start with [01 - Project Scaffold](/plugin-development/tutorial/01-scaffold).
