> This page location: Backend > Object Storage > Guides > Objects
> Full Neon documentation index: https://neon.com/docs/llms.txt

> Summary: Work with objects in Neon Storage using the Files SDK, any S3-compatible SDK, or the AWS CLI. Supports single-part and multipart uploads, range requests, batch deletes, and presigned URLs for browser-side access.

# Objects

Upload, download, list, and delete files

**Coming Soon: Private Preview**

This feature is in private preview: it's not ready for production use, and it may be briefly unavailable as we deploy updates. To get access, [sign up here](https://neon.com/blog/were-building-backends#access).

Objects in Neon Storage are files stored inside a bucket. Every object has a key (its path within the bucket), a body, a content type, and optional metadata. Objects branch with your database. Each branch inherits the parent's objects at the moment of forking without copying any data.

The examples below show both the [Files SDK](https://files-sdk.dev) and the AWS S3 client. See [Get started](https://neon.com/docs/storage/get-started) to configure either client, or [Authentication](https://neon.com/docs/storage/authentication) if you need to create a credential.

## Upload

**Files SDK**

```typescript
import { files } from './client';

await files.upload('images/photo.jpg', fileBuffer, {
  contentType: 'image/jpeg',
  metadata: { 'uploaded-by': 'user-123' },
});
```

**neonctl**

```bash
neonctl bucket object put my-bucket/images/photo.jpg --file ./photo.jpg
neonctl bucket object put my-bucket/images/photo.jpg --file ./photo.jpg --content-type image/jpeg
```

**S3 Client**

```typescript
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { client } from './client';

await client.send(new PutObjectCommand({
  Bucket: 'my-bucket',
  Key: 'images/photo.jpg',
  Body: fileBuffer,
  ContentType: 'image/jpeg',
  Metadata: {
    'uploaded-by': 'user-123',
  },
}));
```

**Python**

```python
client.put_object(
    Bucket='my-bucket',
    Key='images/photo.jpg',
    Body=file_bytes,
    ContentType='image/jpeg',
    Metadata={'uploaded-by': 'user-123'},
)
```

**AWS CLI**

```bash
aws s3 cp ./photo.jpg s3://my-bucket/images/photo.jpg \
  --content-type image/jpeg \
  --endpoint-url "$AWS_ENDPOINT_URL_S3"
```

**Note:** `neonctl bucket object put` uploads via a presigned URL and supports files up to the presign size limit. For large or streaming uploads use the AWS SDK with [multipart upload](https://neon.com/docs/storage/objects#multipart-upload).

## Multipart upload

For large files, the AWS SDK automatically uses multipart upload above a configurable threshold. You can also initiate multipart upload manually for fine-grained control.

**TypeScript**

```typescript
import { Upload } from '@aws-sdk/lib-storage';
import { client } from './client';
import { createReadStream } from 'fs';

const upload = new Upload({
  client,
  params: {
    Bucket: 'my-bucket',
    Key: 'large-file.zip',
    Body: createReadStream('./large-file.zip'),
  },
  partSize: 10 * 1024 * 1024, // 10 MiB per part
});

await upload.done();
```

**Python**

```python
import boto3

# boto3 handles multipart automatically via upload_file/upload_fileobj
client.upload_file(
    './large-file.zip',
    'my-bucket',
    'large-file.zip',
    Config=boto3.s3.transfer.TransferConfig(
        multipart_threshold=10 * 1024 * 1024,
        multipart_chunksize=10 * 1024 * 1024,
    ),
)
```

## Download

**Files SDK**

```typescript
import { files } from './client';

const result = await files.download('images/photo.jpg');
const buffer = await result.arrayBuffer();
```

**neonctl**

```bash
# Downloads to ./photo.jpg by default; use --file to specify a different path
neonctl bucket object get my-bucket/images/photo.jpg
neonctl bucket object get my-bucket/images/photo.jpg --file ./downloads/photo.jpg
```

**S3 Client**

```typescript
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { client } from './client';

const response = await client.send(new GetObjectCommand({
  Bucket: 'my-bucket',
  Key: 'images/photo.jpg',
}));

// Stream to a file
const stream = response.Body as NodeJS.ReadableStream;
stream.pipe(fs.createWriteStream('./photo.jpg'));
```

**Python**

```python
response = client.get_object(Bucket='my-bucket', Key='images/photo.jpg')
with open('./photo.jpg', 'wb') as f:
    f.write(response['Body'].read())
```

**AWS CLI**

```bash
aws s3 cp s3://my-bucket/images/photo.jpg ./photo.jpg \
  --endpoint-url "$AWS_ENDPOINT_URL_S3"
```

You can use range requests for partial downloads:

```typescript
const response = await client.send(new GetObjectCommand({
  Bucket: 'my-bucket',
  Key: 'video.mp4',
  Range: 'bytes=0-1048575', // first 1 MiB
}));
```

## List objects

Use a prefix to filter results. The Files SDK returns a flat array of items; the S3 client supports a delimiter to simulate folder structure.

**Files SDK**

```typescript
import { files } from './client';

const { items } = await files.list({ prefix: 'images/' });
for (const item of items) {
  console.log(item.key, item.size);
}
```

**neonctl**

```bash
# Folder-collapsed view by default (same as aws s3 ls)
neonctl bucket object list my-bucket

# List objects under a prefix
neonctl bucket object list my-bucket/images/

# Flat listing of every key, no folder collapsing
neonctl bucket object list my-bucket --recursive
```

**S3 Client**

```typescript
import { ListObjectsV2Command } from '@aws-sdk/client-s3';
import { client } from './client';

const response = await client.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
  Prefix: 'images/',
  Delimiter: '/',
}));

// Objects in images/
console.log(response.Contents);

// Sub-folders (common prefixes)
console.log(response.CommonPrefixes);
```

**Python**

```python
response = client.list_objects_v2(
    Bucket='my-bucket',
    Prefix='images/',
    Delimiter='/',
)

# Objects in images/
print(response.get('Contents', []))

# Sub-folders
print(response.get('CommonPrefixes', []))
```

**AWS CLI**

```bash
aws s3 ls s3://my-bucket/images/ \
  --endpoint-url "$AWS_ENDPOINT_URL_S3"
```

For buckets with more than 1,000 objects, paginate using `ContinuationToken`:

```typescript
let token: string | undefined;
do {
  const response = await client.send(new ListObjectsV2Command({
    Bucket: 'my-bucket',
    ContinuationToken: token,
  }));
  for (const obj of response.Contents ?? []) {
    console.log(obj.Key);
  }
  token = response.NextContinuationToken;
} while (token);
```

## Delete objects

**Single object:**

**Files SDK**

```typescript
import { files } from './client';

await files.delete('images/photo.jpg');
```

**neonctl**

```bash
neonctl bucket object delete my-bucket/images/photo.jpg
```

**S3 Client**

```typescript
import { DeleteObjectCommand } from '@aws-sdk/client-s3';

await client.send(new DeleteObjectCommand({
  Bucket: 'my-bucket',
  Key: 'images/photo.jpg',
}));
```

**Python**

```python
client.delete_object(Bucket='my-bucket', Key='images/photo.jpg')
```

**AWS CLI**

```bash
aws s3 rm s3://my-bucket/images/photo.jpg \
  --endpoint-url "$AWS_ENDPOINT_URL_S3"
```

**Batch delete (up to 1,000 objects per request):**

**Files SDK**

```typescript
import { files } from './client';

await files.delete(['images/photo1.jpg', 'images/photo2.jpg']);
```

**S3 Client**

```typescript
import { DeleteObjectsCommand } from '@aws-sdk/client-s3';

await client.send(new DeleteObjectsCommand({
  Bucket: 'my-bucket',
  Delete: {
    Objects: [
      { Key: 'images/photo1.jpg' },
      { Key: 'images/photo2.jpg' },
    ],
  },
}));
```

**Python**

```python
client.delete_objects(
    Bucket='my-bucket',
    Delete={
        'Objects': [
            {'Key': 'images/photo1.jpg'},
            {'Key': 'images/photo2.jpg'},
        ]
    },
)
```

**Delete a folder (all objects under a prefix):**

**neonctl**

```bash
# The prefix must end with /
neonctl bucket object delete my-bucket/images/ --recursive
```

**Neon API**

```bash
curl -X DELETE \
  "https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/buckets/my-bucket/objects-by-prefix?prefix=images/" \
  -H "Authorization: Bearer $NEON_API_KEY"
```

## Presigned URLs

Generate a time-limited URL that allows a browser or unauthenticated client to upload or download a specific object, without exposing your credentials.

**Presigned GET (download):**

**Files SDK**

```typescript
import { files } from './client';

const url = await files.url('report.pdf', { expiresIn: 3600 }); // 1 hour
console.log(url); // share this URL — no credentials needed
```

**S3 Client**

```typescript
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { client } from './client';

const url = await getSignedUrl(
  client,
  new GetObjectCommand({ Bucket: 'my-bucket', Key: 'report.pdf' }),
  { expiresIn: 3600 }, // 1 hour
);

console.log(url); // share this URL — no credentials needed
```

**Python**

```python
url = client.generate_presigned_url(
    'get_object',
    Params={'Bucket': 'my-bucket', 'Key': 'report.pdf'},
    ExpiresIn=3600,  # 1 hour
)
print(url)
```

**Presigned PUT (upload from browser):**

**Files SDK**

```typescript
import { files } from './client';

// Returns { url, method, headers } — pass all three to fetch on the client side
const { url, method, headers } = await files.signedUploadUrl('uploads/user-avatar.png', {
  contentType: 'image/png',
  expiresIn: 300, // 5 minutes
});

// On the client side:
// await fetch(url, { method, headers, body: file });
```

**S3 Client**

```typescript
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { client } from './client';

const url = await getSignedUrl(
  client,
  new PutObjectCommand({
    Bucket: 'my-bucket',
    Key: 'uploads/user-avatar.png',
    ContentType: 'image/png',
  }),
  { expiresIn: 300 }, // 5 minutes
);

// On the client side:
// await fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': 'image/png' } });
```

**Python**

```python
url = client.generate_presigned_url(
    'put_object',
    Params={
        'Bucket': 'my-bucket',
        'Key': 'uploads/user-avatar.png',
        'ContentType': 'image/png',
    },
    ExpiresIn=300,
)
print(url)
```

## Object branching

Objects branch with your database. When you fork a branch, the child immediately inherits the parent's buckets and objects at that point in time. No data is copied. From that point:

- Uploading a new object to a child branch is only visible on that branch and its descendants.
- Deleting an object on a child branch does not affect the parent.
- The parent's objects remain unchanged regardless of what happens on child branches.

## Next steps

- [Buckets](https://neon.com/docs/storage/buckets): set access levels, understand bucket branching
- [Authentication](https://neon.com/docs/storage/authentication): credential scopes and read vs write access

---

## Related docs (Guides)

- [Buckets](https://neon.com/docs/storage/buckets)
- [Authentication](https://neon.com/docs/storage/authentication)
