agentsclimarketplace

Nextjs server action validation

Skill jacob-balslev/skill-graph/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation

Skills that know your codebase. Repo-grounded, contract-validated, agent-routable.

Install
npx -y skills add jacob-balslev/skill-graph --skill nextjs-server-action-validation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when writing a Next.js Server Action that accepts user-submitted form data, mutation parameters, or any client-originated input. Every Server Action is a public HTTP endpoint regardless of how it is called — validate with Zod and check authentication as the first two operations before touching the database. Do NOT use for GET route handlers or Server Components that fetch data (those have no user-supplied input); do NOT use for Stripe webhook handlers (use stripe-webhook-signature-verification instead).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

10.1 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it

Next.js Server Action Validation

Concept of the skill

What it is: The validation and authorization discipline for Next.js Server Actions that accept client-originated input. Mental model: A Server Action is a public POST endpoint wearing function-call syntax. Why it exists: Convenience syntax can hide the HTTP boundary, leading developers to skip auth, input validation, or org scoping. What it is NOT: It is not GET route handling, Server Component data fetching, or Stripe webhook verification. Adjacent concepts: Zod schemas, auth checks, org-scoped mutations, controlled error surfaces. One-line analogy: It is the security checkpoint every form submission crosses before touching the database. Common misconception: Only the project UI can call the action; any HTTP client can call the generated endpoint.

Coverage

  • The public endpoint reality — 'use server' functions are exposed as POST endpoints that any HTTP client can call directly; the Next.js call graph is not a security boundary
  • Validation order — auth check BEFORE Zod parse, Zod parse BEFORE database access; any other order produces an exploitable window
  • Zod schema design for actions — using .safeParse() over .parse() to return structured errors rather than throwing; returning { error: ... } shapes that the calling Client Component can render
  • Org-scoping after authentication — verifying that input.orgId matches the session's org, not just that the user is logged in
  • Error surface control — how try/catch around database calls prevents internal errors from propagating to the client response

Philosophy of the skill

Server Actions are a convenience feature that makes form submission feel like a function call. That convenience obscures a critical fact: the action is a public HTTP POST endpoint. A developer who writes const { data } = await myAction(formData) in a Client Component is writing what looks like a local function call, but the runtime sends an HTTP request that any script can replicate. Skipping auth or validation because "it's called from our own UI" is the same reasoning that made getServerSideProps data-fetching functions leaky in the Pages Router — the server boundary does not restrict callers.

Standard Action Pattern

"use server";
import { z } from "zod";
import { getServerSession } from "@/lib/auth";
import { orgQuery } from "@/lib/db";

const CreateOrderSchema = z.object({
  orgId: z.string().uuid(),
  planId: z.string().min(1),
  quantity: z.number().int().positive(),
});

export async function createOrder(input: unknown) {
  // 1. Auth check — before anything else
  const session = await getServerSession();
  if (!session?.user) {
    return { error: "Unauthorized" };
  }

  // 2. Zod parse — structured errors, not thrown exceptions
  const parsed = CreateOrderSchema.safeParse(input);
  if (!parsed.success) {
    return { error: parsed.error.flatten() };
  }

  // 3. Org-scope check — user must belong to the org they are acting on
  if (session.user.orgId !== parsed.data.orgId) {
    return { error: "Forbidden" };
  }

  // 4. Database write — inside orgQuery for RLS enforcement
  try {
    const [order] = await orgQuery(parsed.data.orgId, (tx) =>
      tx`INSERT INTO orders (org_id, plan_id, quantity) VALUES (
          ${parsed.data.orgId}, ${parsed.data.planId}, ${parsed.data.quantity}
        ) RETURNING *`
    );
    return { data: order };
  } catch {
    return { error: "Failed to create order" };
  }
}

Validation Order Rationale

OrderRisk
Auth → Zod → orgScope → DBCorrect — each gate eliminates the next attack surface
Zod → Auth → DBAttacker can probe the schema structure without authenticating
Auth → DB (no Zod)Malformed input reaches the query layer; injection risk
DB → Auth (anywhere)Unauthenticated database reads before rejection

Verification

  • Every 'use server' function calls getServerSession() as its first statement
  • Every 'use server' function runs Schema.safeParse() before any database call
  • The session org ID is compared to the input org ID before the database call
  • Database calls are wrapped in orgQuery, not bare SQL
  • Errors returned to the client do not include stack traces or query text

Do NOT Use When

Use insteadWhen
stripe-webhook-signature-verificationThe entry point is a webhook route handler, not a Server Action
(a data-fetching skill)The function is a Server Component that fetches data, with no user-submitted input
postgres-rls-patternThe task is defining the database-layer policy, not the action-layer validation

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.