# Change Request Versioning System for Prompt Modifications in prompts.chat

> Learn about the prompts.chat immutable versioning system for prompt modifications. Review, approve, or reject changes while tracking every evolution with an audit trail.

- Repository: [Fatih Kadir Akın/prompts.chat](https://github.com/f/prompts.chat)
- Tags: architecture
- Published: 2026-04-02

---

**The prompts.chat platform implements an immutable versioning system where contributors submit change requests rather than editing prompts directly, enabling owners to review, approve, or reject modifications while maintaining a complete audit trail of every content evolution.**

The change request versioning system for prompt modifications in prompts.chat treats every prompt as an immutable record that evolves through controlled contributions. Instead of allowing direct edits, the platform requires users to submit formal change requests that prompt owners must approve, creating a new version only upon acceptance. This workflow ensures content integrity and provides a complete history of who changed what and why.

## Core Data Models in Prisma

The architecture rests on three interconnected models defined in `prisma/schema.prisma`. These entities separate the live prompt from its historical snapshots and pending proposals.

**`Prompt`** represents the public-facing entity. It stores the current `title`, `content`, and `slug`, while maintaining a relation to `contributors`—users whose changes have been accepted.

**`PromptVersion`** acts as an immutable snapshot. Each row captures a prompt's state at a specific moment, including `content`, `title`, `changeNote`, `authorId`, and `createdAt`. The system queries these records to build version histories.

**`ChangeRequest`** handles the proposal workflow. It stores `proposedContent`, `proposedTitle`, and a `reason` string, along with a `status` enum that tracks whether the request is `PENDING`, `APPROVED`, or `REJECTED`. When approved, the system promotes these values into a new `PromptVersion` and updates the live `Prompt` record.

## Feature Flag Configuration

The entire workflow can be toggled via the centralized configuration in [`src/lib/config/index.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/config/index.ts). This allows administrators to enable or disable community contributions without deploying new code.

```typescript
// src/lib/config/index.ts
export const config = {
  features: {
    changeRequests: envBool('PCHAT_FEATURE_CHANGE_REQUESTS', false),
  },
};

```

When `changeRequests` evaluates to `true`, the UI renders "Suggest Change" links generated by `getPromptChangesUrl` in [`src/lib/urls.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/urls.ts), and the API routes become accessible. By default, the feature remains disabled.

## API Endpoints and Workflow

The system exposes a RESTful API that separates owner actions from contributor proposals. All routes utilize the Prisma client exported from [`src/lib/db.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/db.ts) and enforce permissions through helper functions like `checkPromptAccess`.

### Creating Versions (Owner Edits)

When a prompt owner modifies their own content, the system creates a new version immediately. The endpoint `POST /api/prompts/:id/versions` validates that the content differs from the latest snapshot before inserting a record.

```typescript
// src/app/api/prompts/[id]/versions/route.ts
const schema = z.object({
  changeNote: z.string().max(500).optional(),
});
const { content, changeNote } = await request.json().then(schema.parse);

const latest = await db.promptVersion.findFirst({
  where: { promptId: id },
  orderBy: { versionNumber: "desc" },
});

if (latest?.content === content) {
  return NextResponse.json({ error: "no_change" }, { status: 400 });
}

const newVersion = await db.promptVersion.create({
  data: {
    promptId: id,
    content,
    changeNote: changeNote ?? `Version ${latest?.versionNumber + 1 || 1}`,
    authorId: session.user.id,
  },
});

```

This prevents duplicate versions and auto-generates version numbers when contributors omit change notes.

### Submitting Change Requests (Contributor Proposals)

Non-owners must use `POST /api/prompts/:id/changes` to propose modifications. The route explicitly forbids owners from creating requests against their own prompts.

```typescript
// src/app/api/prompts/[id]/changes/route.ts
if (prompt.ownerId === session.user.id) {
  return NextResponse.json(
    { error: "forbidden", message: "You cannot create a change request for your own prompt" },
    { status: 403 }
  );
}

const changeRequest = await db.changeRequest.create({
  data: {
    promptId: id,
    authorId: session.user.id,
    proposedContent: body.content,
    proposedTitle: body.title,
    reason: body.reason,
    status: "PENDING",
  },
});

```

The request remains in `PENDING` status until the owner reviews it.

### Reviewing and Approving Requests (Owner Actions)

Owners manage proposals through `PATCH /api/prompts/:id/changes/:changeId`. Setting `status` to `"APPROVED"` triggers a multi-step transaction that atomically creates a version, updates the prompt, and records the contributor.

```typescript
// src/app/api/prompts/[id]/changes/[changeId]/route.ts
if (status === "APPROVED") {
  // 1. Archive the proposed content as a new version
  await db.promptVersion.create({
    data: {
      promptId,
      content: changeRequest.proposedContent,
      changeNote: `Contribution by @${changeRequest.author.username}: ${changeRequest.reason ?? ""}`,
      authorId: changeRequest.authorId,
    },
  });

  // 2. Update the live prompt and link the contributor
  await db.prompt.update({
    where: { id: promptId },
    data: {
      content: changeRequest.proposedContent,
      title: changeRequest.proposedTitle ?? undefined,
      contributors: { connect: { id: changeRequest.authorId } },
    },
  });

  // 3. Finalize the request status
  await db.changeRequest.update({
    where: { id: changeId },
    data: { status: "APPROVED" },
  });
}

```

Alternatively, owners may `"REJECT"` the request, or authors may `DELETE` their own pending proposals via `DELETE /api/prompts/:id/changes/:changeId`.

## Key Source Files

- **`prisma/schema.prisma`** – Defines `Prompt`, `PromptVersion`, and `ChangeRequest` models with their relations.
- **[`src/lib/config/index.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/config/index.ts)** – Contains the `changeRequests` feature flag that toggles the workflow.
- **`src/app/api/prompts/[id]/versions/route.ts`** – Handles version creation and listing for owner-initiated changes.
- **`src/app/api/prompts/[id]/changes/route.ts`** – Endpoint for submitting new change requests with ownership validation.
- **`src/app/api/prompts/[id]/changes/[changeId]/route.ts`** – Supports retrieval, approval, rejection, and dismissal of specific requests.
- **[`src/lib/urls.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/urls.ts)** – Exports `getPromptChangesUrl` for generating frontend navigation links.
- **[`src/lib/db.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/db.ts)** – Exports the Prisma client used across all API routes for database operations.

## Summary

- **Immutable versioning** ensures every accepted change becomes a permanent `PromptVersion` record linked to the author.
- **Feature-flagged workflow** allows administrators to enable or disable change requests via the `PCHAT_FEATURE_CHANGE_REQUESTS` environment variable.
- **Strict permission model** prevents owners from creating change requests on their own prompts and restricts approval rights to the prompt owner.
- **Automatic attribution** adds approving contributors to the prompt's `contributors` relation, creating a visible credit system.
- **Complete audit trail** preserves the full history of proposed, rejected, and accepted modifications through discrete database records.

## Frequently Asked Questions

### How does the change request versioning system prevent unauthorized edits?

The system performs ownership checks in every API route. In `src/app/api/prompts/[id]/changes/route.ts`, the server compares `prompt.ownerId` against `session.user.id` and returns a 403 Forbidden error if they match, preventing self-requests. Additionally, the `PATCH` endpoint in `[changeId]/route.ts` verifies that only the prompt owner can approve or reject requests, while the `DELETE` endpoint restricts dismissal to the original author.

### What happens when a prompt owner approves a change request?

Upon approval, the system executes a three-step transaction. First, it creates a new `PromptVersion` record containing the proposed content and a change note attributing the contributor. Second, it updates the live `Prompt` record with the new content and title, simultaneously connecting the contributor to the prompt's `contributors` relation. Third, it updates the `ChangeRequest` status to `APPROVED`, completing the workflow.

### Can contributors delete their pending change requests?

Yes. Contributors may withdraw proposals that have not yet been reviewed by sending a `DELETE` request to `/api/prompts/:id/changes/:changeId`. The route verifies that the requesting user matches the `authorId` of the change request before removing the record from the database. Once approved or rejected, the request cannot be deleted by the contributor.

### How do I enable the change request feature in a self-hosted prompts.chat instance?

Set the environment variable `PCHAT_FEATURE_CHANGE_REQUESTS` to `true` before starting the application. This variable is read by `envBool()` in [`src/lib/config/index.ts`](https://github.com/f/prompts.chat/blob/main/src/lib/config/index.ts), which sets the `changeRequests` feature flag to enable the UI links and API endpoints. Without this flag set to true, the "Suggest Change" interface remains hidden and the change request routes return 404 responses.