Fixing \"Only plain objects can be passed to Client Components\"

See why Next.js throws this error when a Server Component passes a class instance or ORM row to a Client Component, and how to convert it to a plain object.

6 min read

Only plain objects can be passed to Client Components from a Server Component, so a class instance, an ORM row, or any other object with a prototype fails with this exact message, either during the build's prerender step or the first time the route renders.

texttext
Error: Only plain objects, and a few built-ins, can be passed to
Client Components from Server Components. Classes or null prototypes
are not supported.

Next.js also prints the specific object or prop it choked on, usually naming the property key and a short description of the value's shape, right after the message above. This error means a Server Component tried to hand a value with a prototype down to a Client Component as a prop, and React had no way to convert that prototype into serialized data.

The smallest reproduction

This Server Component defines a small class, creates an instance of it, and passes that instance straight into a Client Component as a prop named customer.

App.tsxApp.tsx
// app/customers/[id]/page.tsx
import CustomerCard from '@/app/ui/customer-card'
 
class Customer {
  constructor(public name: string, public email: string) {}
}
 
export default function CustomerPage() {
  const customer = new Customer('Ava Chen', 'ava@example.com')
  return <CustomerCard customer={customer} />
}

The card component below marks itself as a Client Component because it needs local state to toggle an edit form, and it just reads whatever object it receives through the customer prop.

App.tsxApp.tsx
// app/ui/customer-card.tsx
'use client'
import { useState } from 'react'
 
export default function CustomerCard({
  customer,
}: {
  customer: { name: string; email: string }
}) {
  const [editing, setEditing] = useState(false)
  return <button onClick={() => setEditing(!editing)}>{customer.name}</button>
}

Rendering CustomerPage throws the error at the top of this article. Both fields on the customer object are plain strings, so the data itself is not the problem. The Customer instance carries a prototype from its class, and React refuses to serialize that prototype into the payload sent to the browser.

Why this happens

A Client Component's props travel to the browser inside the React Server Component payload, a serialized tree built from every Server Component above it. React only knows how to reconstruct plain data shapes from that payload, such as plain objects, arrays, strings, numbers, and a short list of built-ins like Date, Map, and Set.

A class instance is not a plain object even when its own fields look like ordinary data, because it also carries a reference to its class's prototype, which can include methods, getters, and inherited behavior. React cannot rebuild that prototype in the browser, so it rejects the value outright instead of sending a broken, method-less copy.

For the complete list of which prop types serialize and which do not, see What Props Can You Pass from Server to Client Components in Next.js. This article only covers the class instance and non-plain-object case and its fix.

The fix: convert the value to a plain object

Map the class instance to a plain object literal that contains only the fields the Client Component actually needs, and pass that instead of the instance itself.

App.tsxApp.tsx
// app/customers/[id]/page.tsx
import CustomerCard from '@/app/ui/customer-card'
class Customer {
  constructor(public name: string, public email: string) {}
}
export default function CustomerPage() {
  const customer = new Customer('Ava Chen', 'ava@example.com')
  const plainCustomer = { name: customer.name, email: customer.email }
  return <CustomerCard customer={plainCustomer} />
}

plainCustomer is an object literal, so it has the default Object.prototype and nothing else attached to it. React can serialize that shape without any trouble, and the Client Component's own type already describes exactly the fields it expects.

For a repeated shape, write the mapping once as a small function next to where the data is fetched, instead of inline in every component that touches it.

typescripttypescript
// lib/customers.ts
class Customer {
  constructor(public name: string, public email: string) {}
}
 
export function toPlainCustomer(customer: Customer) {
  return { name: customer.name, email: customer.email }
}

Call toPlainCustomer right after you construct or receive the instance, before it ever reaches a Client Component's props. This keeps the conversion in one place instead of scattering field picks across the codebase.

How to confirm the error is gone

Reload the page or rerun the build and the error text should no longer appear in the terminal or the browser overlay. Open the customer card and confirm it still renders the expected name, which shows the plain object carried the same data the class instance held.

Where this error actually comes from in real projects

Most readers never write new SomeClass() directly inside a Server Component. The instance usually comes from a library instead, and the fix is the same: convert the result to a plain object before it reaches a Client Component.

Common sourceWhat it returns
ORM query resultsModel instances or row classes, not plain objects
Database driver documentsWrapper objects with methods like toObject
Date or money utility librariesWrapper classes around a primitive value

Check any object that comes back from a database call, an ORM, or a third-party SDK before passing it to a Client Component, since these are the values most likely to carry a prototype without looking like it in your own code. For more on fetching data directly inside a Server Component, see Fetching Data Directly from a Database in a Server Component, and for the function-shaped version of this same class of error, see Fixing "Functions Cannot Be Passed Directly to Client Components".

Rune AI

Rune AI

Key Insights

  • The error means a Server Component passed a class instance or other non-plain object as a prop to a Client Component.
  • A class instance carries a prototype and methods that React cannot serialize, even when its fields look like plain data.
  • The fix is to convert the value into a plain object with only the fields the Client Component actually needs.
  • ORM rows, database driver documents, and wrapper objects for dates or money are the most common real-world source.
  • Adding a toJSON method to the class does not fix this, because React's serialization does not call it.
  • Do the conversion once, close to where the data is fetched, instead of inside every component that receives it.
RunePowered by Rune AI

Frequently Asked Questions

Why does a class instance fail when its fields look like plain data?

React serializes an object by its own structure, but a class instance also carries a prototype and any methods defined on that class. React has no way to send the prototype across the boundary, so it rejects the whole value even if every field on it is a plain string or number.

Does this error only happen with custom classes I write myself?

No. It is just as common with values built by libraries, such as rows returned by an ORM, documents returned by a database driver, or wrapper objects from a date or money library, because many of those return class instances instead of plain objects.

Can I fix this by adding a toJSON method to the class?

No. React's Server Component serialization does not call toJSON on arbitrary classes the way JSON.stringify does. You still need to convert the value to a plain object yourself before passing it as a prop.

Does JSON.parse(JSON.stringify(value)) fix this safely?

It works for simple cases, but it silently drops values JSON cannot represent, such as undefined fields, and it does not handle Date, Map, or Set the way React's own serialization does. A small mapping function that picks the exact fields you need is safer and clearer.

Conclusion

This error means a Server Component tried to pass a value with a prototype, such as a class instance or an ORM row, into a Client Component's props. Map the value to a plain object with only the fields the Client Component needs before it crosses the boundary, and the error goes away because React only ever sees a plain data shape.