# y-gui R2 Storage Solution: How User Data Is Structured in Cloudflare R2

> Discover how y-gui's R2 storage solution structures user data with unique prefixes for complete isolation. Learn about file organization in Cloudflare R2.

- Repository: [luohy15/y-gui](https://github.com/luohy15/y-gui)
- Tags: architecture
- Published: 2026-03-06

---

**y-gui persists user-specific data in a Cloudflare R2 bucket bound as `CHAT_R2`, organizing all files under unique user prefixes generated from MD5-hashed and sanitized email addresses to ensure complete data isolation.**

The y-gui application leverages Cloudflare's R2 object storage as its primary persistence layer for user data. By implementing a deterministic prefix generation strategy based on user email addresses, y-gui creates isolated namespaces within a single R2 bucket. This article examines the complete y-gui storage solution R2 implementation, including the user data structure, prefix calculation logic, and practical implementation details drawn directly from the source code.

## Cloudflare R2 Storage Architecture in y-gui

The storage layer is defined in [`backend/src/worker-configuration.d.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/worker-configuration.d.ts), where the R2 bucket is bound to the Cloudflare Worker environment as `CHAT_R2`. This binding allows the application to perform object storage operations directly within the serverless runtime without external API calls.

### R2 Bucket Binding and Configuration

The worker configuration declares `CHAT_R2` as an `R2Bucket` type, making it available throughout the application. The actual bucket connection is managed through [`backend/wrangler.toml`](https://github.com/luohy15/y-gui/blob/main/backend/wrangler.toml) and `backend/wrangler.toml.example`, which define the R2 bucket binding for deployment.

### User Data Isolation Strategy

Rather than provisioning separate buckets per user, y-gui implements a **prefix-based isolation** strategy. All user data resides in the same `CHAT_R2` bucket, but each user operates within a unique directory prefix. This approach simplifies infrastructure management while maintaining strict data separation and access control.

## How y-gui Structures User Data in R2

The user data structure centers on a deterministic prefix generation algorithm implemented in [`backend/src/utils/user.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/user.ts). This utility creates unique identifiers that serve as the root directory for all user-specific files.

### Generating the User Prefix from Email

The `calculateUserPrefix` function transforms a user's email address into a unique storage prefix through a three-step process:

1. The email is hashed using **MD5** to create a unique identifier.
2. Special characters are sanitized: `@` becomes `_at_` and `.` becomes `_dot_`.
3. The final prefix combines both elements in the format:

```

{md5(email)}_{sanitizedEmail}

```

For example, `alice@example.com` becomes `3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com`.

### File Organization Under User Directories

Once the prefix is generated, all user data is stored as **JSONL (JSON Lines)** files under that prefix path. The repository layer in [`backend/src/repository/r2/chat-r2-repository.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/repository/r2/chat-r2-repository.ts) handles CRUD operations for files such as:

- `{userPrefix}/integration_config.jsonl` — Stores third-party service configurations
- `{userPrefix}/chat_history.jsonl` — Persists conversation records

This structure ensures that querying for a specific prefix returns all objects belonging to that user, while the flat bucket architecture enables global operations like maintenance or analytics.

## Working with User Data: Code Examples

The following examples demonstrate how to interact with the y-gui R2 storage solution using the utility functions provided in the codebase.

### Calculating User Prefixes

Use the `calculateUserPrefix` function from [`backend/src/utils/user.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/user.ts) to generate the storage prefix for any user email:

```typescript
// Generate a user‑specific prefix
import { calculateUserPrefix } from './utils/user';

const email = 'alice@example.com';
const prefix = await calculateUserPrefix(email);
// Example output: "3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com"

```

### Listing All User Directories

The `listUserPrefixes` function in [`backend/src/utils/storage.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/storage.ts) scans the entire R2 bucket and returns all unique user prefixes:

```typescript
// List all user prefixes stored in the R2 bucket
import { listUserPrefixes } from './utils/storage';

const prefixes = await listUserPrefixes(env.CHAT_R2);
// prefixes = ['3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com', ...]

```

This utility extracts the directory portion of each object key (everything before the final `/`), deduplicates the results, and enables bulk operations such as global token refreshes or maintenance tasks across all user accounts.

### Storing Files in R2

To persist data for a specific user, construct the object key using the prefix and invoke the `put` method on the `CHAT_R2` binding:

```typescript
// Storing a new JSONL file for a user
const key = `${prefix}/integration_config.jsonl`;
await env.CHAT_R2.put(key, jsonlData, {
  httpMetadata: { contentType: 'application/jsonl' },
});

```

## Key Implementation Files

The y-gui R2 storage solution is implemented across the following source files:

| File | Role |
|------|------|
| [`backend/src/worker-configuration.d.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/worker-configuration.d.ts) | Declares the `CHAT_R2` binding for the Cloudflare worker. |
| [`backend/src/utils/user.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/user.ts) | Generates the unique user prefix from an e‑mail address. |
| [`backend/src/utils/storage.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/storage.ts) | Provides `listUserPrefixes` to enumerate all user directories in R2. |
| [`backend/src/repository/r2/chat-r2-repository.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/repository/r2/chat-r2-repository.ts) | Handles CRUD operations for chat data stored under user‑prefix folders. |
| [`backend/wrangler.toml`](https://github.com/luohy15/y-gui/blob/main/backend/wrangler.toml) & `backend/wrangler.toml.example` | Configuration for deploying the worker with the R2 bucket binding. |

## Summary

The y-gui application implements a robust storage solution using Cloudflare R2 with the following key characteristics:

- **Single bucket architecture**: All user data resides in one `CHAT_R2` bucket bound to the Cloudflare Worker.
- **Deterministic user prefixes**: Each user is assigned a unique directory prefix generated from an MD5 hash of their email plus a sanitized version of the address (replacing `@` with `_at_` and `.` with `_dot_`).
- **Isolated file organization**: User-specific files such as `integration_config.jsonl` and `chat_history.jsonl` are stored under the user's prefix, ensuring complete data separation.
- **Bulk operation support**: The `listUserPrefixes` utility enables scanning the entire bucket to identify all active users for maintenance or global updates.

## Frequently Asked Questions

### What storage solution does y-gui use?

y-gui uses **Cloudflare R2** as its primary storage solution. The R2 bucket is bound to the Cloudflare Worker as `CHAT_R2`, allowing direct object storage operations within the serverless environment. This provides S3-compatible API access without egress fees, making it cost-effective for storing user chat histories and configuration data.

### How does y-gui ensure user data isolation in a shared R2 bucket?

y-gui ensures isolation through **prefix-based namespacing** rather than separate buckets. Each user receives a unique directory prefix generated by the `calculateUserPrefix` function in [`backend/src/utils/user.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/user.ts). This prefix combines an MD5 hash of the user's email with a sanitized version of the address, ensuring that all files belonging to a user are stored under their exclusive namespace while sharing the underlying `CHAT_R2` bucket infrastructure.

### What is the format of user data files in y-gui?

User data is stored as **JSONL (JSON Lines)** files, with each line representing a separate JSON record. Common files include `integration_config.jsonl` for third-party service settings and `chat_history.jsonl` for conversation records. This append-only format supports efficient streaming writes and reads, which is optimal for chat applications that generate continuous event logs. Files are organized under each user's unique prefix in the R2 bucket.

### How can I list all users stored in the y-gui R2 bucket?

Use the `listUserPrefixes` function exported from [`backend/src/utils/storage.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/storage.ts). This utility scans all objects in the `CHAT_R2` bucket, extracts the directory portion of each object key (everything before the final `/`), and returns a deduplicated list of active user prefixes. This enables bulk operations such as global token refreshes, maintenance tasks, or analytics across all user accounts without requiring a separate user registry database.