Fix: Action Returned Invalid Data in Astro

Error message:
Action returned invalid data.
Actions 2025-01-25

What Causes This Error?

This error occurs when an action handler returns data that can’t be serialized. Actions communicate between server and client, so return values must be JSON-serializable.

The Problem

// src/actions/index.ts
import { defineAction, z } from 'astro:actions';

export const server = {
  getUser: defineAction({
    handler: async () => {
      // ❌ Functions can't be serialized
      return {
        name: 'John',
        greet: () => 'Hello!',  // Invalid!
      };
    },
  }),

  getDate: defineAction({
    handler: async () => {
      // ❌ Dates need special handling
      return new Date();  // Not directly serializable
    },
  }),
};

The Fix

Return Serializable Data

// src/actions/index.ts
import { defineAction, z } from 'astro:actions';

export const server = {
  getUser: defineAction({
    handler: async () => {
      // ✅ Only serializable data
      return {
        name: 'John',
        greeting: 'Hello!',  // String instead of function
      };
    },
  }),

  getDate: defineAction({
    handler: async () => {
      // ✅ Convert to serializable format
      return {
        date: new Date().toISOString(),
      };
    },
  }),
};

Common Scenarios

Valid Return Types

export const server = {
  validAction: defineAction({
    handler: async () => {
      // ✅ All valid return types
      return {
        string: 'hello',
        number: 42,
        boolean: true,
        null: null,
        array: [1, 2, 3],
        nested: { a: { b: 'c' } },
        date: new Date().toISOString(),  // String format
      };
    },
  }),
};

Invalid Return Types

export const server = {
  invalidAction: defineAction({
    handler: async () => {
      // ❌ These will cause errors
      return {
        func: () => {},           // Functions
        symbol: Symbol('test'),   // Symbols
        map: new Map(),           // Maps
        set: new Set(),           // Sets
        circular: null,           // Circular references
        undefined: undefined,     // Undefined values
      };
    },
  }),
};

Converting Complex Types

export const server = {
  getData: defineAction({
    handler: async () => {
      // Convert Map to array
      const map = new Map([['a', 1], ['b', 2]]);
      const mapArray = Array.from(map.entries());

      // Convert Set to array
      const set = new Set([1, 2, 3]);
      const setArray = Array.from(set);

      // Convert Date to string
      const date = new Date();
      const dateString = date.toISOString();

      return {
        map: mapArray,
        set: setArray,
        date: dateString,
      };
    },
  }),
};

Database Results

export const server = {
  getProducts: defineAction({
    handler: async () => {
      const products = await db.query('SELECT * FROM products');

      // ✅ Transform to plain objects
      return products.map(p => ({
        id: p.id,
        name: p.name,
        price: p.price,
        createdAt: p.createdAt.toISOString(),  // Date to string
      }));
    },
  }),
};

Class Instances

class User {
  constructor(public name: string, public email: string) {}

  greet() {
    return `Hello, ${this.name}`;
  }
}

export const server = {
  getUser: defineAction({
    handler: async () => {
      const user = new User('John', 'john@example.com');

      // ✅ Extract data from class instance
      return {
        name: user.name,
        email: user.email,
        greeting: user.greet(),  // Call method, return result
      };
    },
  }),
};

Handling Errors

export const server = {
  riskyAction: defineAction({
    handler: async () => {
      try {
        const result = await fetchData();

        // ✅ Ensure result is serializable
        return JSON.parse(JSON.stringify(result));
      } catch (error) {
        // ✅ Return error as string
        return {
          error: error instanceof Error ? error.message : 'Unknown error',
        };
      }
    },
  }),
};

Testing Serializability

function isSerializable(value: unknown): boolean {
  try {
    JSON.stringify(value);
    return true;
  } catch {
    return false;
  }
}

export const server = {
  debugAction: defineAction({
    handler: async () => {
      const result = await getSomeData();

      // Debug: check if serializable
      if (!isSerializable(result)) {
        console.error('Non-serializable data:', result);
        throw new Error('Data is not serializable');
      }

      return result;
    },
  }),
};

BigInt Handling

export const server = {
  getLargeNumber: defineAction({
    handler: async () => {
      const bigNumber = BigInt(9007199254740991);

      // ✅ Convert BigInt to string
      return {
        value: bigNumber.toString(),
      };
    },
  }),
};

Quick Checklist

  • Return only JSON-serializable data
  • Convert Dates to ISO strings
  • Don’t return functions or methods
  • Convert Maps/Sets to arrays
  • Stringify BigInt values
  • Avoid circular references