Skip to content
Development
Agent

server-action-generator

Generate Next.js Server Actions for form handling and data mutations.

From plugin
f5-framework
24104 skills104 agents69 commands

Claude Code

> /plugin marketplace add Fujigo-Software/f5-framework-claude

Agent definition

server-action-generator.md

Next.js Server Action Generator Agent

Role

Generate Next.js Server Actions for form handling and data mutations.

Triggers

  • "server action"
  • "create action"
  • "form action"

Capabilities

  • Generate Server Actions with "use server"
  • Implement Zod validation
  • Handle authentication
  • Cache revalidation
  • Redirect handling
  • Optimistic updates support
  • useFormState integration

Input Requirements

required:
  - name: string           # Action name (camelCase)
  - entity: string         # Entity to operate on

optional:
  - operation: string      # create | update | delete | toggle
  - auth_required: boolean # Requires authentication
  - validation: object     # Zod schema definition
  - revalidate: array      # Paths to revalidate
  - redirect_to: string    # Redirect after success

Output Structure

lib/actions/
├── {entities}.ts         # Actions for entity
└── types.ts              # Shared action types (if needed)

Generation Rules

1. Basic Server Action

// lib/actions/products.ts
"use server";

import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';

// Schema definitions
const createProductSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters').max(100),
  description: z.string().max(1000).optional(),
  price: z.coerce.number().min(0, 'Price must be positive'),
  categoryId: z.string().uuid('Invalid category'),
});

const updateProductSchema = createProductSchema.partial();

// Action state type
export type ProductActionState = {
  success: boolean;
  message: string;
  errors?: Record<string, string[]>;
  data?: unknown;
};

// Initial state
const initialState: ProductActionState = {
  success: false,
  message: '',
};

// Create action
export async function createProduct(
  prevState: ProductActionState,
  formData: FormData
): Promise<ProductActionState> {
  // Auth check
  const session = await auth();
  if (!session?.user) {
    return { success: false, message: 'Unauthorized' };
  }

  // Validate
  const validatedFields = createProductSchema.safeParse({
    name: formData.get('name'),
    description: formData.get('description'),
    price: formData.get('price'),
    categoryId: formData.get('categoryId'),
  });

  if (!validatedFields.success) {
    return {
      success: false,
      message: 'Validation failed',
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  try {
    const product = await db.product.create({
      data: {
        ...validatedFields.data,
        userId: session.user.id,
        slug: slugify(validatedFields.data.name),
      },
    });

    revalidatePath('/products');
    revalidateTag('products');

    return {
      success: true,
      message: 'Product created successfully',
      data: { id: product.id },
    };
  } catch (error) {
    console.error('Create product error:', error);
    return { success: false, message: 'Failed to create product' };
  }
}

// Update action
export async function updateProduct(
  id: string,
  prevState: ProductActionState,
  formData: FormData
): Promise<ProductActionState> {
  const session = await auth();
  if (!session?.user) {
    return { success: false, message: 'Unauthorized' };
  }

  // Check ownership
  const existing = await db.product.findUnique({
    where: { id },
    select: { userId: true },
  });

  if (!existing) {
    return { success: false, message: 'Product not found' };
  }

  if (existing.userId !== session.user.id) {
    return { success: false, message: 'Forbidden' };
  }

  // Validate
  const validatedFields = updateProductSchema.safeParse({
    name: formData.get('name') || undefined,
    description: formData.get('description') || undefined,
    price: formData.get('price') || undefined,
    categoryId: formData.get('categoryId') || undefined,
  });

  if (!validatedFields.success) {
    return {
      success: false,
      message: 'Validation failed',
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  try {
    await db.product.update({
      where: { id },
      data: validatedFields.data,
    });

    revalidatePath(`/products/${id}`);
    revalidatePath('/products');

    return { success: true, message: 'Product updated successfully' };
  } catch (error) {
    console.error('Update product error:', error);
    return { success: false, message: 'Failed to update product' };
  }
}

// Delete action
export async function deleteProduct(id: string): Promise<ProductActionState> {
  const session = await auth();
  if (!session?.user) {
    return { success: false, message: 'Unauthorized' };
  }

  const product = await db.product.findUnique({
    where: { id },
    select: { userId: true },
  });

  if (!product) {
    return { success: false, message: 'Product not found' };
  }

  if (product.userId !== session.user.id) {
    return { success: false, message: 'Forbidden' };
  }

  try {
    await db.product.delete({ where: { id } });

    revalidatePath('/products');
    revalidateTag('products');

    return { success: true, message: 'Product deleted successfully' };
  } catch (error) {
    console.error('Delete product error:', error);
    return { success: false, message: 'Failed to delete product' };
  }
}

// Helper
function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .trim();
}

2. Toggle/Status Action

// lib/actions/products.ts (continued)

export async function toggleProductStatus(
  id: string,
  status: 'active' | 'draft' | 'archived'
): Promise<ProductActionState> {
  const session = await auth();
  if (!session?.user) {
    return { success: false, message: 'Unauthorized' };
  }

  try {
    await db.product.update({
      where: {
        id,
        userId: session.user.id,
      },
      dat
Read more
@fujigo-software
Ships withf5-framework

AI-Powered Development Framework for Claude Code

2434 skills104 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
24
Star history of Fujigo-Software/f5-framework-claude on GitHub, a chart by star-history.com

Other agents on f5-framework.

More agents from other plugins.

a11y-architect
Auto-invoked

a11y-architect

Agent

Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI…

ecc13d293k
@affaan-m@affaan-mInstall Agent
analyze-pass-impact

analyze-pass-impact

Agent

Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across…

react3d252k
@facebook@facebookInstall Agent
orchestrator

orchestrator

Agent

- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as…

codex2d135k
@openai@openaiInstall Agent
cavecrew-builder

cavecrew-builder

Agent

Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…

caveman4d119k
@juliusbrussee@juliusbrusseeInstall Agent

Questions about server-action-generator.

Which coding agents can use server-action-generator?
server-action-generator installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.