# Object tags

Object tags are a small set of key/value labels attached to an object. Use them to mark lifecycle state
(`status=final`), ownership (`team=finance`), or anything you want to filter or report on later. Tags are a
replace-on-write set: writing tags replaces the whole set, it does not merge into the existing one.

The tag set is independent of user metadata. Metadata is fixed at write time and travels with the object body. Tags can
change after the object exists without rewriting the object.

::: warning Setting tags replaces the entire set. To add one tag, read the current set, modify it, then write it back; a
bare set call drops every existing tag. :::

## Limits and format

Tags follow the S3 tagging rules:

- Up to 10 tags per object.
- A key is up to 128 characters; a value is up to 256 characters.
- Keys and values are UTF-8 strings.
- Keys are unique within the set (a repeated key keeps the last value).

The server validates the set on write and rejects an oversized or malformed set. The native path applies the same limits
as the S3 path.

## Get, Set, Delete

The native client uses `getObjectTags`, `setObjectTags`, and `deleteObjectTags`. `setObjectTags` replaces the whole set
and returns the stored set so you can confirm what landed. The Node native client takes and returns a plain object; the
Go and Java native clients use a list of `Tag` records (`{key, value}`).

:::: native

::: code-group

```ts [Node]
await native.setObjectTags("reports", "q1/summary.txt", {
  team: "finance",
  status: "final",
});

const tags = await native.getObjectTags("reports", "q1/summary.txt");
console.log(tags); // { team: 'finance', status: 'final' }

await native.deleteObjectTags("reports", "q1/summary.txt");
```

```go [Go]
_, err := native.SetObjectTags(ctx, "reports", "q1/summary.txt", []lockwellnative.Tag{
    {Key: "team", Value: "finance"},
    {Key: "status", Value: "final"},
})

tags, err := native.GetObjectTags(ctx, "reports", "q1/summary.txt")
for _, t := range tags {
    fmt.Println(t.Key, t.Value)
}

err = native.DeleteObjectTags(ctx, "reports", "q1/summary.txt")
```

```java [Java]
import com.lockwell.sdk.nativeapi.NativeTypes.Tag;
import java.util.List;

nativeClient.setObjectTags("reports", "q1/summary.txt",
    List.of(new Tag("team", "finance"), new Tag("status", "final")));

List<Tag> tags = nativeClient.getObjectTags("reports", "q1/summary.txt");

nativeClient.deleteObjectTags("reports", "q1/summary.txt");
```

:::

::::

:::: s3

The S3 client exposes the same three operations under the S3 names: `PutObjectTagging` replaces the set,
`GetObjectTagging` reads it, and `DeleteObjectTagging` clears it.

::: code-group

```ts [Node]
await s3.putObjectTagging("reports", "q1/summary.txt", {
  team: "finance",
  status: "final",
});

const tags = await s3.getObjectTagging("reports", "q1/summary.txt");
console.log(tags.team, tags.status); // "finance" "final"

await s3.deleteObjectTagging("reports", "q1/summary.txt");
```

```go [Go]
err := s3.PutObjectTagging(ctx, "reports", "q1/summary.txt", map[string]string{
    "team":   "finance",
    "status": "final",
})

tags, err := s3.GetObjectTagging(ctx, "reports", "q1/summary.txt")
fmt.Println(tags["team"], tags["status"]) // "finance" "final"

err = s3.DeleteObjectTagging(ctx, "reports", "q1/summary.txt")
```

```java [Java]
import java.util.Map;

s3.putObjectTagging("reports", "q1/summary.txt",
    Map.of("team", "finance", "status", "final"));

Map<String, String> tags = s3.getObjectTagging("reports", "q1/summary.txt");
System.out.println(tags.get("team") + " " + tags.get("status"));

s3.deleteObjectTagging("reports", "q1/summary.txt");
```

:::

::::

Both clients always replace the whole set. To add one tag to an existing set, read the set, modify it, and write it
back.

::: code-group

```ts [Node]
const current = await native.getObjectTags("reports", "q1/summary.txt");
await native.setObjectTags("reports", "q1/summary.txt", { ...current, reviewed: "yes" });
```

:::

## Tagging a specific version

In a versioned bucket, tags belong to a version. The Node native client takes an optional `versionId` on its tagging
methods; without it, the current version is tagged. The Go and Java native tagging methods operate on the current
version only, so to read or replace the tags of a specific version from those languages, use the S3 client's
`WithVersionID` (shown in the S3 block below).

:::: native

::: code-group

```ts [Node]
// Read the tags of a specific version:
const tags = await native.getObjectTags("reports", "q1/summary.txt", { versionId });

// Replace the tags on a specific version:
await native.setObjectTags("reports", "q1/summary.txt", { status: "archived" }, { versionId });
```

```go [Go]
// The native tagging methods operate on the current version; reach for a versioned
// tag set with the S3 client's WithVersionID, shown in the S3 block below.
tags, err := native.GetObjectTags(ctx, "reports", "q1/summary.txt")
```

```java [Java]
List<Tag> tags = nativeClient.getObjectTags("reports", "q1/summary.txt");
```

:::

::::

:::: s3

The Go S3 client's tagging methods accept `WithVersionID` to target a non-current version:

::: code-group

```go [Go]
// Read the tags of a specific version:
tags, err := s3.GetObjectTagging(ctx, "reports", "q1/summary.txt",
    lockwellsdk.WithVersionID(versionID))

// Replace the tags on a specific version:
err = s3.PutObjectTagging(ctx, "reports", "q1/summary.txt",
    map[string]string{"status": "archived"},
    lockwellsdk.WithVersionID(versionID))
```

:::

::::

The Node native client (shown above) is the cleanest way to tag a specific older version from a non-Go language, since
the Node and Java S3 tagging methods operate on the current version only. See [versioning](/guide/versioning) for how
versions and version ids work.

## Related

- [Versioning](/guide/versioning) for tagging a specific version.
- [Data operations](/guide/data-operations) for user metadata, the write-time alternative to tags.
- [S3 operations reference](/reference/s3-operations) for the full operation matrix.
