The Extended CRUD pattern is used when a resource has a lifecycle that goes beyond simple create/read/update/delete.

Instead of overloading PATCH or PUT with magic “status flips,” you expose explicit lifecycle transitions as first-class operations (e.g., submit, approve, decline, publish).

5.1. Overview

Extended CRUD starts with a normal CRUD resource, then adds lifecycle transition endpoints to express the workflow clearly.

Typical examples:

  • POST /articles/{id}/submit
  • POST /articles/{id}/approve
  • POST /articles/{id}/decline
  • POST /articles/{id}/publish

These operations:

  • Express intent (“submit for review”) rather than just “update status”
  • Capture workflow rules in the API shape, not only in documentation
  • Make authorization, audit, and governance much easier

5.2. When to Use Extended CRUD

Use Extended CRUD when:

  • The resource moves through defined lifecycle states

    • e.g., DRAFT → SUBMITTED → APPROVED → PUBLISHED
  • There are business rules about valid transitions

    • e.g., you can’t publish without approval
  • Different roles/actors perform different transitions

    • Author submits; editor approves; publisher publishes
  • You need clear auditability; “who did what and when” matters
  • You want to avoid pushing workflow logic into the client or hidden in generic updates

5.3. When NOT to Use Extended CRUD

Avoid Extended CRUD when:

  • The resource has no lifecycle, just static data
  • There is only one meaningful state (e.g., simple reference data)
  • A “status” field is purely informational and doesn’t drive behavior
  • The transitions are extremely simple and don’t justify extra endpoints

If the “transition” is actually a business action with broader side-effects (e.g., “cancel order and trigger refunds and notifications”), consider the Functional Resource pattern (Commands) instead of Extended CRUD.

5.4. What the Pattern Looks Like

Extended CRUD keeps the basic CRUD shape and adds explicit transition endpoints.

Base CRUD (simplified)

  • POST /articles → create a new article in DRAFT
  • GET /articles/{articleId} → read current article state

Extended lifecycle transitions

  • POST /articles/{articleId}/submit

    • DRAFT → SUBMITTED
  • POST /articles/{articleId}/approve

    • SUBMITTED → APPROVED
  • POST /articles/{articleId}/decline

    • SUBMITTED → DECLINED (with reason)
  • POST /articles/{articleId}/publish

    • APPROVED → PUBLISHED

Each transition:

  • Has its own endpoint (and sometimes its own request body)
  • Can have its own authorization, logging, and metrics
  • Makes the workflow visible in the API contract

5.5. Anti-Patterns to Avoid

1. “Status-over-PATCH” instead of transitions

PATCH /articles/{id}
{
  "status": "PUBLISHED"
}
  • Hides which transitions are valid
  • Makes authorization coarse-grained (“can edit status”)
  • Makes audit logs less clear (“status changed” vs “publish requested/approved”)

2. Single “transition” endpoint with action field

POST /articles/{id}/transition
{
  "action": "SUBMIT"
}
  • Recreates a command bus inside one endpoint
  • Loses the clarity, discoverability, and per-action governance benefits

3. Over-exploding endpoints

Creating endpoints for trivial, reversible, low-value changes (e.g., /articles/{id}/highlight) may create noise. Reserve Extended CRUD for business-significant lifecycle steps.

5.6. OpenAPI Example

A lean but complete OpenAPI 3.0.3 document showing:

  • POST /articles and GET /articles/{articleId} (base CRUD)
  • Extended endpoints: submit, approve, decline, publish
  • Minimal schemas focused on status transitions and a decline reason
openapi: 3.0.3
info:
  title: Articles API - Extended CRUD Pattern Example
  version: 1.0.0
servers:
  - url: https://api.example.com

paths:
  /articles:
    post:
      summary: Create a new article
      tags: [Articles]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleCreateRequest'
            examples:
              default:
                value:
                  title: "How to Design APIs with Extended CRUD"
                  body: "Extended CRUD helps model workflows as lifecycle transitions..."
      responses:
        '201':
          description: Created
          headers:
            Location:
              schema:
                type: string
                example: https://api.example.com/articles/ar_12345
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: DRAFT

  /articles/{articleId}:
    get:
      summary: Get an article by ID
      tags: [Articles]
      parameters:
        - in: path
          name: articleId
          required: true
          schema:
            type: string
            example: ar_12345
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: SUBMITTED
        '404':
          description: Not Found

  /articles/{articleId}/submit:
    post:
      summary: Submit an article for review
      tags: [Articles]
      parameters:
        - in: path
          name: articleId
          required: true
          schema:
            type: string
            example: ar_12345
      responses:
        '200':
          description: Submitted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: SUBMITTED
        '409':
          description: Invalid state to submit (e.g., already published)
        '404':
          description: Not Found

  /articles/{articleId}/approve:
    post:
      summary: Approve an article
      tags: [Articles]
      parameters:
        - in: path
          name: articleId
          required: true
          schema:
            type: string
            example: ar_12345
      responses:
        '200':
          description: Approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: APPROVED
        '409':
          description: Invalid state to approve
        '404':
          description: Not Found

  /articles/{articleId}/decline:
    post:
      summary: Decline an article
      tags: [Articles]
      parameters:
        - in: path
          name: articleId
          required: true
          schema:
            type: string
            example: ar_12345
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeclineArticleRequest'
            examples:
              default:
                value:
                  reason: "Article lacks sufficient technical depth."
      responses:
        '200':
          description: Declined
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: DECLINED
        '409':
          description: Invalid state to decline
        '404':
          description: Not Found

  /articles/{articleId}/publish:
    post:
      summary: Publish an article
      tags: [Articles]
      parameters:
        - in: path
          name: articleId
          required: true
          schema:
            type: string
            example: ar_12345
      responses:
        '200':
          description: Published
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
              examples:
                default:
                  value:
                    id: ar_12345
                    title: "How to Design APIs with Extended CRUD"
                    body: "Extended CRUD helps model workflows as lifecycle transitions..."
                    status: PUBLISHED
        '409':
          description: Invalid state to publish
        '404':
          description: Not Found

components:
  schemas:
    Article:
      type: object
      properties:
        id:
          type: string
          example: ar_12345
        title:
          type: string
          example: "How to Design APIs with Extended CRUD"
        body:
          type: string
          example: "Extended CRUD helps model workflows as lifecycle transitions..."
        status:
          type: string
          enum: [DRAFT, SUBMITTED, APPROVED, DECLINED, PUBLISHED]
          example: DRAFT
      required:
        - id
        - title
        - body
        - status

    ArticleCreateRequest:
      type: object
      properties:
        title:
          type: string
          example: "How to Design APIs with Extended CRUD"
        body:
          type: string
          example: "Extended CRUD helps model workflows as lifecycle transitions..."
      required:
        - title
        - body

    DeclineArticleRequest:
      type: object
      properties:
        reason:
          type: string
          example: "Article lacks sufficient technical depth."
      required:
        - reason

This example keeps the focus strictly on the lifecycle: you can show teams how Extended CRUD cleanly separates lifecycle transitions from generic updates.

5.7. Visualizing Extended CRUD (Mermaid Diagram)

For Extended CRUD, a state diagram is usually more expressive than a simple sequence diagram, because the pattern is all about lifecycle transitions.

stateDiagram-v2
    [*] --> DRAFT

    DRAFT --> SUBMITTED: POST /articles/{id}/submit
    SUBMITTED --> APPROVED: POST /articles/{id}/approve
    SUBMITTED --> DECLINED: POST /articles/{id}/decline
    APPROVED --> PUBLISHED: POST /articles/{id}/publish

    DRAFT: status = "DRAFT"
    SUBMITTED: status = "SUBMITTED"
    APPROVED: status = "APPROVED"
    DECLINED: status = "DECLINED"
    PUBLISHED: status = "PUBLISHED"