Snapshots and Forks
Snapshots and forks extend traditional object versioning beyond individual objects.
- Snapshots capture the state of an entire bucket at a specific point in time.
- Forks let you branch a bucket into a new one, reusing objects without copying data.
Together, they provide powerful tools for data recovery, branching workflows, AI agent workflows, and efficient experimentation.
Key Terminology
| Term | Definition |
|---|---|
| Snapshot | An immutable, point-in-time view of all objects in a bucket. |
| Live bucket | The current, writable version of a bucket. |
| Fork | A new bucket created from an existing one, sharing data without duplication. |
| Parent bucket | The source bucket from which a fork is created. |
| Child bucket | The new bucket that inherits objects from its parent at the time of forking. |
Snapshots
A snapshot freezes the entire state of a bucket at creation time.
- Immutable: once created, snapshots cannot be modified.
- Writes always go to the live bucket, not to snapshots.
- Snapshots provide consistent recovery points and reproducibility.
How Snapshots Work
The diagram below shows how snapshots capture bucket state over time:

At the beginning, the bucket contains two objects (Object 1 = foo, Object 2 =
bar).
-
T1 → Snapshot 1 is created.
- Bucket contains Object 1 =
fooand Object 2 =bar. - Both are preserved in Snapshot 1.
- Bucket contains Object 1 =
-
T2 → Object 2 is updated to
quux.- Snapshot 1 remains unchanged.
-
T3 → Snapshot 2 is created.
- It records Object 1 =
fooand Object 2 =quux.
- It records Object 1 =
-
T4 → Object 3 is added with value
baz. -
T5 → Snapshot 3 is created.
- It preserves Object 1 =
foo, Object 2 =quux, and Object 3 =baz.
- It preserves Object 1 =
-
T6 → Object 1 is deleted from the live bucket.
- Past snapshots (1, 2, 3) still retain Object 1.
-
T7 → Snapshot 4 is created.
- It records the live state: Object 2 =
quuxand Object 3 =baz.
- It records the live state: Object 2 =
Key Takeaways
- Snapshots are immutable: once created, they never change.
- Deletions in the live bucket do not affect prior snapshots.
- Each snapshot reflects the exact bucket contents at its creation time.
Use Cases
- Backup & recovery: restore data to a known state.
- Audit & compliance: maintain point-in-time records.
- Experimentation: compare different dataset versions.
- Agent workflows: give AI agents a consistent snapshot to work on without interference from ongoing changes in the live bucket. For example, an agent can reliably process or analyze a dataset without being affected by concurrent writes.
Forks
A fork creates a new bucket from an existing one:
- Shares objects with its parent (no data copy).
- New objects written to the child bucket do not affect the parent(same object modification only reflects in child bucket).
- Enables isolated experimentation with minimal storage overhead.
Forking Example
The diagram below illustrates how forking works across multiple buckets and timelines:

- T1 → Bucket A starts with two objects (Object 1 =
foo, Object 2 =bar). - T2 → Snapshot A1 is created.
- T3 → Object 1 is deleted in Bucket A (but remains in Snapshot A1).
- T4 → Snapshot A2 is created.
- T5 → Bucket B is forked from Bucket A. Initially, Bucket B contains
Object 2 (
bar). - T6 → a new Object 3 (
baz) is added to Bucket B. - T7 → Snapshot B1 is created containing two objects: Object 2 (
barinherited from Bucket A) and Object 3 (baz). - T8 → Object 3 in Bucket B is updated to
foo. - T9 → Bucket C is forked from Bucket A with Snapshot A1. It starts
with objects (Object 1 =
foo, Object 2 =bar). - T10 → Object 1 in Bucket C is updated to
baz.- Bucket C now has Object 1 (
bazits own copy) and Object 2 (barinherited from Bucket A)
- Bucket C now has Object 1 (
- T11 → Bucket D is forked from Bucket B with Snapshot B1. It starts
with objects - Object 2 =
bar(inherited from bucket 'A') and Object 3 =baz(inherited from bucket B). - T12 → Object 2 in Bucket D is updated to
quux. - T13 → Object 1 in Bucket D is added with value
baz.- Bucket D now has three objects: Object 1 =
baz(its own data), Object 2 =quux(its own copy), and Object 3 =baz(inherited from bucket B).
- Bucket D now has three objects: Object 1 =
Key Takeaways
- Forks always inherit the current state of their parent bucket at the time of forking.
- Subsequent changes in either the parent or child bucket are isolated.
- Snapshots (e.g., A1, A2, B1) preserve historical states, while forks (Buckets B, C, D) enable branching evolution.
Use Cases
- Branching datasets: run experiments on a fork without affecting production.
- Multi-tenant scenarios: give each team a forked bucket.
- Efficient testing: avoid costly full copies of large buckets.
- Agent workflows: fork a bucket for each agent so they can work in parallel on isolated copies. This enables agents to test strategies, train models, or generate outputs without interfering with each other or with the parent dataset.
Snapshots vs. Forks
The table below summarizes the key differences between snapshots and forks at a glance.
| Feature | Snapshots | Forks |
|---|---|---|
| Mutability | Immutable | Mutable |
| Scope | Point-in-time state of a bucket | Independent bucket derived from another |
| Storage | Minimal (metadata + references) | Minimal (shared objects, new objects stored separately) |
| Best for | Recovery, auditing, reproducibility | Experimentation, branching workflows |
Using Snapshots and Forks
Snapshots and forks are opt-in. To enable them on a bucket, you must set a special header at creation time.
Enabling Snapshots and Forks
To enable snapshots and forks, set the X-Tigris-Enable-Snapshot: true header
when creating the bucket.
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func createBucketWithSnapshotEnabled(ctx context.Context, client *s3.Client, bucketName string) error {
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucketName)}, func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Enable-Snapshot", "true"))
})
return err
}
Example using the Tigris SDK for JavaScript:
import { createBucket } from "@tigrisdata/storage";
const result = await createBucket("bucket-with-snapshots", {
enableSnapshot: true,
});
if (result.error) {
console.error("error creating bucket with snapshots enabled", result.error);
} else {
console.log("bucket created with snapshots enabled");
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
TigrisSnapshotEnabled,
create_snapshot_bucket,
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# Create snapshot enabled bucket using context manager pattern
with TigrisSnapshotEnabled(s3_client):
s3_client.create_bucket(Bucket='my-snapshot-bucket')
# Or, create snapshot enabled bucket using helper function
create_snapshot_bucket(s3_client, 'my-snapshot-bucket')
Creating a Snapshot
Snapshots are created with the same CreateBucket API call, with an additional
header: X-Tigris-Snapshot: true.
Snapshot name can optionally be specified as part of that header (with semicolon
separator), e.g., X-Tigris-Snapshot: true; name=test snapshot name.
The version of the snapshot created is returned in the
X-Tigris-Snapshot-Version header of the response.
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func createBucketSnapshot(ctx context.Context, client *s3.Client, bucketName string, snapshotName string) (string, error) {
resp, err := client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucketName)}, func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Snapshot", fmt.Sprintf("true; name=%s", snapshotName))
})
if err != nil {
return "", err
}
rawResp := middleware.GetRawResponse(resp.ResultMetadata).(*http.Response)
return rawResp.Header.Get("X-Tigris-Snapshot-Version"), nil
}
Example using the Tigris SDK for JavaScript:
import { createBucketSnapshot } from "@tigrisdata/storage";
const result = await createBucketSnapshot("bucket-with-snapshots", {
name: "test snapshot", // optional name for the snapshot
});
if (result.error) {
console.error("error creating bucket snapshot", result.error);
} else {
console.log("bucket snapshot created");
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
create_snapshot_bucket,
create_snapshot,
get_snapshot_version,
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# Create snapshot-enabled bucket
create_snapshot_bucket(s3_client, 'bucket-with-snapshots')
# Create snapshots
result = create_snapshot(s3_client, 'bucket-with-snapshots', snapshot_name='test-snapshot')
version = get_snapshot_version(result)
Listing Snapshots
Snapshots are listed with the same ListBuckets API call, with an additional
header: X-Tigris-Snapshot: <BUCKET_NAME>.
To list snapshots for a bucket, make a list buckets request and set the
X-Tigris-Snapshot header to the bucket name, e.g.,
X-Tigris-Snapshot: test-bucket.
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func listSnapshotsForBucket(ctx context.Context, client *s3.Client, bucketName string) (*s3.ListBucketsOutput, error) {
return client.ListBuckets(ctx, &s3.ListBucketsInput{}, func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Snapshot", bucketName))
})
}
Example using the Tigris SDK for JavaScript:
import { listBucketSnapshots } from "@tigrisdata/storage";
const listSnapshots = await listBucketSnapshots("bucket-with-snapshots"); // bucket name
if (listSnapshots.error) {
console.error("error listing snapshots", listSnapshots.error);
} else {
console.log("snapshots:", listSnapshots.data);
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
TigrisSnapshot,
create_snapshot_bucket,
list_snapshots,
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# Create snapshot-enabled bucket
create_snapshot_bucket(s3_client, 'bucket-with-snapshots')
# List snapshots for a bucket using context manager pattern
with TigrisSnapshot(s3_client, 'bucket-with-snapshots'):
snapshots = s3_client.list_buckets()
# Or, list snapshots via the helper function
snapshots = list_snapshots(s3_client, 'bucket-with-snapshots')
Example List Snapshots Response
The response will contain the list of snapshots for the bucket including the name and creation date of the snapshot. The response format is S3-compatible. Name tag here showing snapshot version with the name if added during snapshot creation time.
<ListAllMyBucketsResult>
<Buckets>
<Bucket>
<Name>1751631910196672425; name=my first snapshot</Name>
<CreationDate>2025-07-04T12:25:10.19667705Z</CreationDate>
</Bucket>
<Bucket>
<Name>1751631910169675092</Name>
<CreationDate>2025-07-04T12:25:10.16968055Z</CreationDate>
</Bucket>
<Bucket>
<Name>1751631910140685342; name=another snapshot</Name>
<CreationDate>2025-07-04T12:25:10.141025675Z</CreationDate>
</Bucket>
</Buckets>
<ContinuationToken>1751631910140685342</ContinuationToken>
</ListAllMyBucketsResult>
Creating a Forked Bucket
Forks are also created via the CreateBucket API, specifying a parent bucket (and optionally a snapshot).
To create a bucket which is a fork of another bucket, specify the
X-Tigris-Fork-Source-Bucket: <BUCKET_NAME> header where <BUCKET_NAME> is the
name of the bucket you want to use as the fork parent.
Add the X-Tigris-Fork-Source-Bucket-Snapshot: <SNAPSHOT_VERSION> header (e.g.
X-Tigris-Fork-Source-Bucket-Snapshot: 1751631910140685342) if you want to use
a specific snapshot of the parent bucket for the fork. If this header is not
set, then a new snapshot of the parent will be created at the current time and
used for creating the fork bucket.
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func createBucketFork(ctx context.Context, client *s3.Client, bucketName string) error {
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucketName)}, func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Fork-Source-Bucket", "source-bucket"))
})
return err
}
Example using the Tigris SDK for JavaScript:
import { createBucket } from "@tigrisdata/storage";
const result = await createBucket("forked-bucket", {
sourceBucketName: "source-bucket", // source bucket name
sourceBucketSnapshot: "snapshot_version", // optional snapshot version e.g. 1759343574493973169
});
if (result.error) {
console.error("error creating bucket fork", result.error);
} else {
console.log("bucket fork created");
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
TigrisFork,
create_fork
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# Fork from current state
with TigrisFork(s3_client, 'source-bucket'):
s3_client.create_bucket(Bucket='forked-bucket')
# Fork from specific snapshot
with TigrisFork(s3_client, 'source-bucket', snapshot_version='1759343574493973169'):
s3_client.create_bucket(Bucket='forked-from-snapshot')
# Or, use a helper function to create forks
create_fork(s3_client, 'new-bucket', 'source-bucket', snapshot_version='1759343574493973169')
Retrieving Snapshot and Fork Info for a Bucket
Information related to snapshots and forks for an existing bucket can be retrieved by making a HeadBucket request. The response of the request will include custom Tigris headers with the details:
- The
X-Tigris-Enable-Snapshotheader will always be present and will be set to "true" for snapshot-enabled buckets. X-Tigris-Fork-Source-BucketandX-Tigris-Fork-Source-Bucket-Snapshotwill only be set for fork buckets and will contain the fork parent bucket name and snapshot versions respectively.X-Tigris-Is-Fork-Parentwill only be present for fork parent buckets and will be set to "true".
Note: These are not standard AWS S3 headers, and in order to retrieve them, the raw HTTP response will need to be used. Here is an example for checking whether snapshot is enabled for a bucket:
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func hasSnapshotEnabled(ctx context.Context, client *s3.Client, bucketName string) (bool, error) {
resp, err := client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: aws.String(bucketName)})
if err != nil {
return false, err
}
rawResp := middleware.GetRawResponse(resp.ResultMetadata).(*http.Response)
return rawResp.Header.Get("X-Tigris-Enable-Snapshot") == "true", nil
}
Example using the AWS SDK for JavaScript v3:
import { S3Client, HeadBucketCommand } from "@aws-sdk/client-s3";
async function hasSnapshotEnabled(s3Client, bucketName) {
const response = await s3Client.send(
new HeadBucketCommand({ Bucket: bucketName }),
);
const headers = response.$metadata?.httpHeaders || {};
return headers["X-Tigris-Enable-Snapshot"] === "true";
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
create_snapshot_bucket,
create_snapshot,
create_fork,
get_snapshot_version,
has_snapshot_enabled,
get_bucket_info,
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# Check if a bucket has snapshots enabled
bucket_name = 'my-bucket'
create_snapshot_bucket(s3_client, bucket_name)
if has_snapshot_enabled(s3_client, bucket_name):
print(f"✓ Snapshots are enabled for {bucket_name}")
else:
print(f"✗ Snapshots are not enabled for {bucket_name}")
# Example: Check fork lineage
source_bucket = 'production-data'
create_snapshot_bucket(s3_client, source_bucket)
# Create a snapshot
snapshot_result = create_snapshot(s3_client, source_bucket, snapshot_name='v1')
snapshot_version = get_snapshot_version(snapshot_result)
# Create a fork
forked_bucket = 'test-data'
create_fork(s3_client, forked_bucket, source_bucket, snapshot_version=snapshot_version)
# Inspect the fork
fork_info = get_bucket_info(s3_client, forked_bucket)
print(f"Forked from: {fork_info['fork_source_bucket']}")
print(f"Snapshot version: {fork_info['fork_source_snapshot']}")
Listing and Retrieving Objects from a Snapshot
Objects can be listed in a snapshot with the same ListObjectsV2 API call, with
an additional header: X-Tigris-Snapshot-Version: SNAPSHOT_VERSION. Similarly,
specific versions of an object in a particular snapshot can be retrieved with
the same GetObject API call and header. The same approach also applies to
HeadObject requests.
Below is an example of retrieving an object from a snapshot:
- Go
- JavaScript
- Python
Example using the Go SDK github.com/aws/aws-sdk-go-v2/service/s3:
func getObjectFromSnapshot(ctx context.Context, client *s3.Client, bucket string, object string, snapshotVersion string) (*s3.GetObjectOutput, error) {
return client.GetObject(ctx, &s3.GetObjectInput{Bucket: aws.String(bucket), Key: aws.String(object)}, func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Snapshot-Version", snapshotVersion))
})
}
Example using the AWS SDK for JavaScript v3:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
async function getObjectFromSnapshot(client, bucket, key, snapshotVersion) {
return client.send(
new GetObjectCommand({
Bucket: bucket,
Key: key,
$httpOptions: {
headers: { "X-Tigris-Snapshot-Version": snapshotVersion },
},
}),
);
}
Example using the Python SDK boto3:
pip install tigris-boto3-ext
import boto3
from tigris_boto3_ext import (
TigrisSnapshot,
create_snapshot_bucket,
create_snapshot,
get_object_from_snapshot,
get_snapshot_version,
head_object_from_snapshot,
list_objects_from_snapshot,
)
# Initialize boto3 S3 client
s3_client = boto3.client(
's3',
endpoint_url='https://t3.storage.dev', # Tigris endpoint
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
)
# First, ensure bucket has snapshots enabled
create_snapshot_bucket(s3_client, 'my-bucket')
s3_client.put_object(Bucket='my-bucket', Key='file.txt', Body=b'data')
response = create_snapshot(
s3_client,
'my-bucket',
snapshot_name='daily-backup-2024-01-01'
)
snapshot_version = get_snapshot_version(response)
print(f"Snapshot version: {snapshot_version}")
# Read objects from a specific snapshot using context manager pattern
with TigrisSnapshot(s3_client, 'my-bucket', snapshot_version=snapshot_version):
obj = s3_client.get_object(Bucket='my-bucket', Key='file.txt')
objects = s3_client.list_objects_v2(Bucket='my-bucket')
# Or, use a helper function to access snapshot data
obj = get_object_from_snapshot(s3_client, 'my-bucket', 'file.txt', snapshot_version)
objects = list_objects_from_snapshot(s3_client, 'my-bucket', snapshot_version)
metadata = head_object_from_snapshot(s3_client, 'my-bucket', 'file.txt', snapshot_version)
Check out the examples in the tigris-boto3-ext repo for more details on how to use the snapshot and forking feature with the Python boto3 SDK.
Using a Forked Bucket
Forked buckets behave the same as regular buckets, and you can use the usual tooling (AWS CLI or SDK) to work with them.
The only restriction is that the parent bucket cannot be deleted while forked buckets depend on it.
Authorization
Snapshot and fork operations on existing buckets are limited to users who are
bucket owners, organization admins, or have ReadOnly (or Editor) access to
the buckets. This includes all operations mentioned above, such as creating a
snapshot, creating a fork from a specific parent bucket, or listing objects in a
snapshot.
Limitations and Upcoming Functionality
Current Limitations
Currently, bucket snapshot and fork functionality have the following limitations:
- Once snapshot support is enabled for a bucket, it cannot be disabled.
- Object expiration (TTL) cannot be enabled for buckets with snapshot support.
- Transitioning objects to different storage tiers cannot be configured for snapshot-enabled buckets.
- Only the Standard storage tier is supported for snapshot-enabled buckets.
- Snapshot support cannot be enabled for existing buckets.
Upcoming Features
- Listing all versions of an object.
- Snapshot and fork management in the Tigris Dashboard.
- Deletion of old snapshots.
- Restoring a bucket to a specific snapshot.
Billing
You only pay for the additional object versions created over time. Snapshots and forks themselves do not incur extra charges, making them a cost-efficient way to manage bucket history, power agent workflows, and experiment safely.
FAQ
Can I fork a fork?
Yes. You can fork a bucket that is itself a fork. Each fork becomes an independent bucket that can be further forked or snapshotted.
What happens if I delete the parent bucket?
The parent bucket cannot be deleted while forked buckets depend on it. You must delete the forks first.
Do snapshots affect performance?
No. Snapshots are metadata-only constructs and do not slow down read or write operations to the live bucket.
Can I restore a bucket to a previous snapshot?
Not yet, but this feature is on the roadmap.