Skip to main content
[wt.d]

Strapi v4 → v5: Refactor API Responses, Types & Mocks

Published on 2026-10-26 by GC Rivera

Strapi v4 has reached end of life, so teams running it in production need a path to v5. Strapi’s upgrade CLI (npx @strapi/upgrade major) takes care of a lot of the backend work. The frontend is a different story, because v5 changes the shape of every REST response.

If your frontend was built against v4, it probably has hundreds of lines that reach through .data and .attributes. In v5, that nesting is gone.

This guide covers what changed in the response format and how to update your TypeScript types, state selectors, and test fixtures to match.

The Core Breaking Change: Flattened JSON Responses

In Strapi v4, every API response wrapped properties in a deeply nested structure, requiring repetitive property access just to read a simple string. Strapi v5 strips this nesting completely.

Side-by-Side Response Comparison

Strapi v4 (Legacy Nested Format):

{
  "data": {
    "id": 101,
    "attributes": {
      "title": "Migrating to v5",
      "slug": "migrating-to-v5",
      "author": {
        "data": {
          "id": 12,
          "attributes": {
            "name": "Jane Doe"
          }
        }
      }
    }
  },
  "meta": {}
}

Strapi v5 (Modern Flattened Format):

{
  "data": {
    "id": 101,
    "documentId": "hgv1vny5cebq2l3czil1rpb3",
    "title": "Migrating to v5",
    "slug": "migrating-to-v5",
    "author": {
      "id": 12,
      "documentId": "znrlzntu9ei5onjvwfaalu2v",
      "name": "Jane Doe"
    }
  },
  "meta": {}
}

In v5, the attributes container is gone. Properties like title and slug sit directly on the item object alongside id and the newly introduced documentId. The flattening applies recursively: where an author relation previously required accessing author.data.attributes.name, it now resolves directly to author.name.

Media objects undergo the same simplification: media.data.attributes.url becomes simply media.url.

The id vs. documentId Trap

The flattening is easy to spot. The switch to documentId is easier to miss.

In Strapi v5, documentId (a 24-character string such as hgv1vny5cebq2l3czil1rpb3) replaces the auto-incrementing numeric id as the identifier for API queries and mutations.

The numeric id still exists in the database, but REST endpoints expect documentId when you fetch, update, or delete a specific record. If your frontend components or mutation hooks continue passing id to Strapi v5 endpoints, your API calls will fail or return unexpected results.

Component Refactor Example

Here is how a typical React component changes during the migration:

// BEFORE: Strapi v4 Component
function ArticleList({ articles }) {
  return (
    <ul>
      {articles.data.map((article) => (
        <li key={article.id}>
          <h3>{article.attributes.title}</h3>
          <p>By: {article.attributes.author.data.attributes.name}</p>
          <img src={article.attributes.coverImage.data.attributes.url} alt="" />
        </li>
      ))}
    </ul>
  );
}

// AFTER: Strapi v5 Component
function ArticleList({ articles }) {
  return (
    <ul>
      {articles.data.map((article) => (
        <li key={article.documentId}>
          <h3>{article.title}</h3>
          <p>By: {article.author.name}</p>
          <img src={article.coverImage.url} alt="" />
        </li>
      ))}
    </ul>
  );
}

Note the updated key prop using documentId rather than id.

Updating Your Frontend Architecture

Migrating UI templates is straightforward, but your types, state selectors, and test mocks also need attention.

1. TypeScript Interface Refactoring

In Strapi v4, codebases often relied on a generic wrapper interface to model the nested structure:

// Legacy Strapi v4 Types
export interface StrapiV4Response<T> {
  data: {
    id: number;
    attributes: T;
  };
  meta: Record<string, unknown>;
}

// Usage
type ArticleAttributes = { title: string; body: string };
type ArticleResponse = StrapiV4Response<ArticleAttributes>;

In Strapi v5, your type definitions become much cleaner. The base entity intersects directly with the identifier fields:

// Modern Strapi v5 Types
export interface StrapiV5Response<T> {
  data: T & { 
    id: number; 
    documentId: string; 
  };
  meta: Record<string, unknown>;
}

// Usage
type Article = { title: string; body: string };
type ArticleResponse = StrapiV5Response<Article>;

If you are migrating an enterprise application incrementally, you can use a transitional type guard to safely handle both formats during the transition:

// Transitional Type Guard
export function isStrapiV5<T>(
  data: StrapiV4Response<T> | StrapiV5Response<T>
): data is StrapiV5Response<T> {
  return !('attributes' in data.data);
}

2. State Management Selectors (Redux / Zustand / TanStack Query)

If your application normalizes Strapi responses in a client cache, check your selectors:

// Legacy v4 Redux Selector
const selectArticleTitle = (state: RootState, id: number) => 
  state.articles.entities[id]?.attributes?.title ?? 'Untitled';

// Modern v5 Redux Selector
const selectArticleTitle = (state: RootState, documentId: string) => 
  state.articles.entities[documentId]?.title ?? 'Untitled';

Also check for custom unflatten() or stripAttributes() helper functions. Many teams wrote custom utilities in v4 to recursively strip .attributes before storing data in state. If you leave those helpers in place, they may attempt to access non-existent properties on already-flat v5 responses, leading to unexpected undefined runtime errors.

3. Test Fixtures and Mock Payloads

Mock JSON fixtures used in Jest, Vitest, Cypress, or Playwright suites represent frozen snapshots of old responses. Once you update your UI components to expect flat v5 structures, tests with v4 mock files will fail across your test suite.

Manually editing dozens of JSON mock files is error-prone work that should be automated.

The Backward-Compatibility Escape Hatch

If your backend is ready to upgrade to v5 but your frontend team needs more time to refactor components across multiple branches, Strapi v5 provides a built-in backward-compatibility header: Strapi-Response-Format.

By sending this header in your client requests, you can instruct the v5 backend to reconstruct the response into the legacy nested v4 shape:

// Request legacy response format from a Strapi v5 server
fetch('https://api.example.com/api/articles', {
  headers: {
    'Strapi-Response-Format': 'v4'
  }
});

This is useful for buying time: you can ship the v5 backend to production while frontend branches catch up. Strapi presents it as a migration aid rather than a long-term setting, so plan to remove it endpoint by endpoint as each client is updated and tested. Note that even with the header, responses include documentId, so you can start switching identifiers before the rest of the refactor lands.

Converting Mock Payloads Instantly

Rewriting TypeScript interfaces and component logic requires developer judgment, but converting static JSON payloads, mock fixtures, and seed data is mechanical work.

To save time during your migration, you can use my free, client-side Strapi v4 to v5 Payload Converter.

Paste a v4 response into the left panel and the flattened v5 version appears on the right. It also handles responses fetched with the Strapi-Response-Format: v4 header. Everything runs in your browser and nothing is sent over the network, so you can paste real production payloads without exposing customer data.

Frontend Migration Checklist

Before you call the migration done, check that you have:

  • Updated TypeScript interfaces to the flat format (removed .data.attributes wrappers)
  • Swapped API query and mutation identifiers from id to documentId
  • Audited state management selectors and removed legacy normalization helpers
  • Batch-converted test fixtures and mock API payloads to the flat format
  • Verified related entities and media objects resolve directly without nested .data.attributes
  • Removed temporary Strapi-Response-Format: v4 headers from production fetch calls
  • Validated frontend behavior against a live Strapi v5 staging environment
Published: 2026-10-26•Author: GC Rivera
#strapi#migration#typescript#api#frontend
←All Postsworktools.dev Blog