> ## 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.

# 01 - Project scaffold

> Scaffold a new formae plugin project with formae plugin init and tour the generated files.

This section walks through creating a new plugin project using `formae plugin init`.

## Initialize the plugin

Run `formae plugin init` to start the interactive setup:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae plugin init
```

The command walks you through several prompts:

1. **Plugin name**: a short identifier for your plugin (lowercase, letters/numbers/hyphens). We'll use `sftp`.

2. **Namespace**: the prefix for your resource types (uppercase). We'll use `SFTP`, which means our resources will have types like `SFTP::Files::File`.

3. **Description**: a brief summary of what the plugin does. For this tutorial: `SFTP file management plugin for formae`.

4. **Category**: pick the closed category that best describes your plugin, one of `cloud`, `auth`, `config`, `observability`, `cicd`, `network`, `data`, `security`, `containers`, or `other`. It mirrors the hub's catalog. For the SFTP plugin we'll use `data`.

5. **Author**: your name or organization. Enter whatever identifies you as the plugin developer.

6. **Module path**: the Go module path for your plugin (e.g., `github.com/your-org/formae-plugin-sftp`). This is used in `go.mod` and import statements throughout the generated code.

7. **License**: choose `Apache-2.0`, `BSD-3-Clause`, `MIT`, or `MPL-2.0` (the licenses the hub accepts), or `Other` to provide a custom SPDX identifier. The command generates the matching LICENSE file automatically, and a plugin licensed as `Other` builds and runs locally but is not publishable to the hub.

8. **Target directory**: where to create the plugin project. Defaults to `./<plugin-name>`, so we'll get `./sftp`.

After answering the prompts, the command downloads a template and creates a complete project structure.

## Non-interactive mode

For automation, CI pipelines, or LLM-assisted workflows, you can run `formae plugin init` non-interactively by providing all required values as flags:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae plugin init \
  --name sftp \
  --namespace SFTP \
  --description "SFTP file management plugin for formae" \
  --author "Your Name" \
  --module-path github.com/your-org/formae-plugin-sftp \
  --license Apache-2.0 \
  --category data \
  --no-input
```

The `--no-input` flag disables interactive prompts and requires all mandatory flags to be provided. Run `formae plugin init --help` for the complete list of flags.

## Project structure

```
formae-plugin-sftp/
├── formae-plugin.pkl      # Plugin manifest
├── main.go                # Entry point
├── sftp.go                # ResourcePlugin implementation (stub)
├── schema/
│   └── pkl/
│       ├── PklProject     # PKL dependencies
│       └── sftp.pkl       # Resource schemas (example)
├── conformance_test.go    # Conformance test setup
├── scripts/
│   └── ci/
│       └── clean-environment.sh
├── examples/              # Usage examples
├── .github/
│   └── workflows/         # CI configuration
├── go.mod
├── go.sum
├── Makefile
└── README.md
```

## Makefile

The template includes a Makefile with common development tasks:

| Command                 | Description                                                        |
| ----------------------- | ------------------------------------------------------------------ |
| `make build`            | Build the plugin binary to `bin/`                                  |
| `make test`             | Run all tests                                                      |
| `make test-unit`        | Run unit tests only (tests tagged with `//go:build unit`)          |
| `make test-integration` | Run integration tests (tests tagged with `//go:build integration`) |
| `make lint`             | Run golangci-lint                                                  |
| `make install`          | Build and install the plugin locally to `~/.pel/formae/plugins/`   |
| `make conformance-test` | Run conformance tests against a running formae agent               |
| `make clean`            | Remove build artifacts                                             |

The `install` target copies the binary, schema files, and manifest to the local plugin directory where the formae agent can discover it.

## Key files

### formae-plugin.pkl

The manifest contains the information the formae agent needs to know about your plugin:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
name = "sftp"
version = "0.1.0"
namespace = "SFTP"
description = "SFTP file management plugin for formae"
category = "data"
license = "FSL-1.1-ALv2"
minFormaeVersion = "0.84.0"   // auto-managed by `make build`

output {
  renderer = new JsonRenderer {}
}
```

| Field              | Description                                                                                                                                                                  |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | Plugin identifier (lowercase, letters/numbers/hyphens)                                                                                                                       |
| `version`          | Semantic version                                                                                                                                                             |
| `namespace`        | Resource type prefix (uppercase). Appears in types like `SFTP::Files::File`                                                                                                  |
| `category`         | Hub catalog category (closed enum: `cloud`, `auth`, `config`, `observability`, `cicd`, `network`, `data`, `security`, `containers`, `other`)                                 |
| `license`          | SPDX license identifier                                                                                                                                                      |
| `minFormaeVersion` | Minimum formae version required. Managed automatically: `make build` rewrites this field from the SDK's `MinFormaeVersion` constant, so it always tracks your SDK dependency |

### main.go

The entry point starts the SDK and should never be modified:

```go theme={"languages":{"custom":["/languages/pkl.json"]}}
package main

import "github.com/platform-engineering-labs/formae/pkg/plugin/sdk"

func main() {
    sdk.RunWithManifest(&Plugin{}, sdk.RunConfig{})
}
```

`RunWithManifest` takes care of all plugin initialization: reading the manifest, loading schemas, and handling communication with the formae agent. Your job is to implement the `Plugin` struct; the SDK handles everything else.

### sftp.go

The template provides a stub implementation with all required methods:

```go theme={"languages":{"custom":["/languages/pkl.json"]}}
type Plugin struct{}

var _ plugin.ResourcePlugin = &Plugin{}

// Configuration methods
func (p *Plugin) RateLimit() plugin.RateLimitConfig { ... }
func (p *Plugin) DiscoveryFilters() []plugin.MatchFilter { ... }
func (p *Plugin) LabelConfig() plugin.LabelConfig { ... }

// CRUD operations (stubs that return ErrNotImplemented)
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) { ... }
func (p *Plugin) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResult, error) { ... }
func (p *Plugin) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) { ... }
func (p *Plugin) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResult, error) { ... }
func (p *Plugin) Status(ctx context.Context, req *resource.StatusRequest) (*resource.StatusResult, error) { ... }
func (p *Plugin) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResult, error) { ... }
```

### go.mod

The module imports the formae plugin SDK and conformance test packages:

```go theme={"languages":{"custom":["/languages/pkl.json"]}}
module github.com/platform-engineering-labs/formae-plugin-sftp

go 1.25

require (
    github.com/platform-engineering-labs/formae/pkg/plugin v0.1.21
    github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.40
)
```

## Verify the setup

Build the plugin to verify everything compiles:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
cd formae-plugin-sftp
make build
```

You should see output like:

```
Building sftp plugin...
go build -o bin/sftp .
```

The binary is created at `bin/sftp`.

## What's next

The template includes an example resource schema to get you started. In the next section, we'll replace it with our own `File` resource schema that defines the properties for managing files on an SFTP server.

***

Next: [02 - Resource Schema](/plugin-development/tutorial/02-schema) - Define the File resource in PKL
