Skip to content

server-action-generator

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

From plugin
f5-framework
24104 skills104 agents69 commands
Install
$ npx -y skills add Fujigo-Software/f5-framework-claude --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

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

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
Ships withf5-framework

AI-Powered Development Framework for Claude Code

Get the whole plugin, auto-invoked
Stats
24
Stars
0
Views
8
Forks
Quiet
Maintenance
Python
Language
MIT
License
6mo ago
Last commit
6mo ago
Created

Repo: Fujigo-Software/f5-framework-claude