You're reading the open-source Community docs. Plakar also offers Control Plane, the enterprise version. It's a virtual appliance with a web-based interface for centralized backup management across your infrastructure. View Control Plane docs →

Creating a custom integration

This guide shows how to create a custom Plakar integration in Go, build it, package it, and install it using the plakar CLI.

An integration is a package of one or more connectors. An importer connector reads data from a source during backup, an exporter connector writes it to a destination during restore, and a storage connector hosts a repository.

Why write a custom integration?

Plakar ships with integrations for common sources and storage backends. When you need to back up something that isn’t supported out of the box, such as an internal database or a custom data source, you can write your own integration in Go and install it like any other package.

What you will build

An integration with a single importer connector that backs up one hardcoded file. Once you understand the pattern, you can extend it to walk directories, read from APIs, or consume any other data source.

The guide follows the layout of the example integration, which also implements an exporter and a storage backend.

Prerequisites

  • Go 1.25 or later
  • plakar installed and available in your $PATH

Set up the project

Create a new Go module for your integration:

mkdir plakar-example
cd plakar-example
go mod init github.com/yourorg/plakar-example

Install the two required dependencies:

go get github.com/PlakarKorp/go-kloset-sdk
go get github.com/PlakarKorp/kloset

Create the project structure:

.
├── connector.go        # Connector implementation
├── cmd/
│   └── importer/
│       └── main.go     # Importer entry point
├── manifest.yaml       # Manifest describing the integration
├── Makefile            # Build and packaging targets
├── go.mod
└── go.sum

Implement the connector

Create connector.go:

package connector

import (
	"context"
	"io"
	"os"
	"path/filepath"

	"github.com/PlakarKorp/kloset/connectors"
	"github.com/PlakarKorp/kloset/location"
	"github.com/PlakarKorp/kloset/connectors/importer"
	"github.com/PlakarKorp/kloset/objects"
)

const FILE = "/home/user/Documents/notes.md"

type testConnector struct{}

func NewImporter(ctx context.Context, opts *connectors.Options, proto string, config map[string]string) (importer.Importer, error) {
	return &testConnector{}, nil
}

func (f *testConnector) Root() string                  { return filepath.Dir(FILE) }
func (f *testConnector) Origin() string                { return "localhost" }
func (f *testConnector) Type() string                  { return "example" }
func (f *testConnector) Flags() location.Flags         { return location.FLAG_LOCALFS }
func (f *testConnector) Ping(_ context.Context) error  { return nil }
func (f *testConnector) Close(_ context.Context) error { return nil }

func (f *testConnector) Import(ctx context.Context, records chan<- *connectors.Record, results <-chan *connectors.Result) error {
	defer close(records)

	info, err := os.Stat(FILE)
	if err != nil {
		records <- connectors.NewError(FILE, err)
		return nil
	}

	fi := objects.FileInfo{
		Lname:    filepath.Base(FILE),
		Lsize:    info.Size(),
		Lmode:    info.Mode(),
		LmodTime: info.ModTime(),
	}

	records <- connectors.NewRecord(FILE, "", fi, nil, func() (io.ReadCloser, error) {
		return os.Open(FILE)
	})

	return nil
}

FLAG_LOCALFS tells Plakar the location is a local filesystem path. A file that cannot be read is reported with connectors.NewError() rather than failing the whole backup.

Writing to the console

Never write to os.Stdout. Plakar communicates with the connector over gRPC through stdin and stdout, so any writes there corrupt the stream. Use os.Stderr for debug output instead.

Create the entry point

Create cmd/importer/main.go:

package main

import (
	"context"
	"os"

	sdk "github.com/PlakarKorp/go-kloset-sdk"
	connector "github.com/yourorg/plakar-example"
)

func main() {
	ctx := sdk.InterruptableContext(context.Background())
	sdk.EntrypointImporter(ctx, os.Args, connector.NewImporter)
}

InterruptableContext cancels the context when the process receives an interrupt, so the connector stops cleanly.

Write the manifest

Create manifest.yaml:

name: example
description: A minimal integration that backs up a single file.
homepage: https://github.com/yourorg/plakar-example
license: ISC
connectors:
  - type: importer
    executable: ./example-importer
    protocols: [example]
    location_flags: [localfs]

The manifest describes the integration and each connector it contains. The executable value must match the binary produced in the build step. The location_flags list must match the flags returned by the connector’s Flags() method.

See the manifest reference for the optional fields.

Build the integration

Create a Makefile:

GO      = go
PLAKAR  = plakar
VERSION = v1.0.0

all: build

build:
	${GO} build -o example-importer ./cmd/importer

package: build
	rm -f example_${VERSION}_*.ptar
	${PLAKAR} pkg create ./manifest.yaml ${VERSION}

uninstall:
	-${PLAKAR} pkg rm example

install: package
	${PLAKAR} pkg add -allow-unsigned ./example_${VERSION}_*.ptar

reinstall: uninstall install

clean:
	rm -f example-importer example_${VERSION}*.ptar

.PHONY: all build package uninstall install reinstall clean

Package and install

Package and install the integration in one step:

make install

This builds the binary, runs plakar pkg create to produce example_v1.0.0_<os>_<arch>.ptar, and installs it with plakar pkg add. A local build is unsigned, so the install target passes -allow-unsigned.

To update an existing installation:

make reinstall

This removes the previously installed package with plakar pkg rm before installing the new one.

Verify the installation:

plakar pkg show

You should see example listed.

Use the integration

Back up using your new importer:

plakar at /var/backups backup example://

Because this importer uses a hardcoded file path, the location after example:// is ignored. The importer always reads from /home/user/Documents/notes.md.

Next steps

Walking a directory. Instead of a hardcoded path, parse the location from the config map (strings.TrimPrefix(config["location"], proto+"://")) and use filepath.WalkDir to send a record for each file.

Remote sources. For connectors that talk to an API, use 0 as the flags value instead of location.FLAG_LOCALFS, and parse credentials and endpoints from the config map passed to your constructor.

Streaming imports. If your source cannot be replayed, such as a pipe, add location.FLAG_STREAM to your flags. Plakar then calls Import only once.

Adding exporter or storage connectors. Implement the Exporter or Store interface, add an entry point under cmd/, and add a connector entry to manifest.yaml.

See the SDK reference for the full interface definitions and the example integration for a working importer, exporter, and storage backend.