How to Add Custom Validation Rules to FckSignups: A Complete Guide for Front-End and Back-End

Add custom validation rules to FckSignups by implementing Zod schemas in both the React front-end (src/hooks/useModal/fieldsMaker.tsx) and the Cloudflare Worker back-end (cloudflare-worker/urlHandlers/handleSubmitTool.ts) to ensure data integrity and security.

FckSignups is an open-source tool submission platform that currently performs minimal validation on user-provided data. Whether you need to enforce minimum character lengths, restrict special characters, validate URL formats, or run asynchronous checks, this guide walks you through adding robust custom validation rules to both layers of the application.

Understanding FckSignups' Current Validation Architecture

Before adding custom rules, you need to understand where FckSignups currently handles data:

Neither layer currently implements schema-based validation, leaving the system vulnerable to malformed submissions.

Step 1: Install a Validation Library

Zod is the recommended choice for FckSignups because it runs in both browser and worker environments with zero dependencies.

npm install zod

Add this to your package.json and commit the lockfile changes.

Step 2: Define Your Custom Validation Schema

Create a dedicated validation file that centralizes all rules for tool submissions.

// src/validation/toolSchema.ts
import { z } from "zod";

export const toolSchema = z.object({
  name: z
    .string()
    .min(3, "Tool name must be at least 3 characters")
    .max(50, "Tool name cannot exceed 50 characters")
    .regex(/^[a-zA-Z0-9\s-]+$/, "Name may only contain letters, numbers, spaces and dashes"),
  url: z
    .string()
    .url("Please provide a valid URL")
    .max(200, "URL is too long"),
  description: z
    .string()
    .min(10, "Description must be at least 10 characters")
    .max(300, "Description cannot exceed 300 characters"),
});

This schema demonstrates several custom validation rules: length constraints, pattern matching with regex, and URL format verification. Extend this file for any additional fields your form collects.

Step 3: Apply Validation in the Front-End

Import your schema into src/hooks/useModal/fieldsMaker.tsx and validate before transmitting data.

import { toolSchema } from "../../validation/toolSchema";
import { z } from "zod";

// Inside your submit handler
const handleSubmit = async () => {
  try {
    // Throws ZodError if validation fails
    const validData = toolSchema.parse(formState);
    await sendToWorker(validData);
  } catch (e) {
    if (e instanceof z.ZodError) {
      const fieldErrors = e.format();
      setErrors(fieldErrors);
      return; // Prevent submission
    }
    throw e; // Re-throw unexpected errors
  }
};

Display errors in your modal UI:

{errors.name && <p className="error">{errors.name._errors[0]}</p>}
{errors.url && <p className="error">{errors.url._errors[0]}</p>}

Step 4: Guard the Back-End Endpoint

Even with front-end validation, always validate server-side. Malicious clients can bypass your UI entirely.

In cloudflare-worker/urlHandlers/handleSubmitTool.ts:

import { toolSchema } from "../../../src/validation/toolSchema";
import { z } from "zod";

export async function handleSubmitTool(request: Request) {
  const body = await request.json();

  try {
    const valid = toolSchema.parse(body);
    // Existing logic: push to GitHub
    return new Response(JSON.stringify({ success: true }), { status: 200 });
  } catch (err) {
    if (err instanceof z.ZodError) {
      return new Response(
        JSON.stringify({ 
          error: err.errors.map(e => e.message),
          fields: err.issues.map(i => i.path[0])
        }),
        { status: 400, headers: { "Content-Type": "application/json" } }
      );
    }
    return new Response(
      JSON.stringify({ error: "Invalid payload" }),
      { status: 400, headers: { "Content-Type": "application/json" } }
    );
  }
}

This ensures that even crafted requests reaching your Cloudflare Worker are rejected before touching the GitHub API.

Step 5: Return Structured Error Responses

Both layers should return consistent error shapes:

Property Purpose
error Human-readable message or array of messages
field Which input failed validation (for UI highlighting)

Example worker response for multiple failures:

{
  "error": ["Tool name must be at least 3 characters", "Please provide a valid URL"],
  "fields": ["name", "url"]
}

Step 6: Add Unit and Integration Tests

Create src/validation/toolSchema.test.ts to verify your rules:

import { toolSchema } from "./toolSchema";
import { describe, it, expect } from "vitest";

describe("toolSchema", () => {
  it("rejects names shorter than 3 characters", () => {
    expect(() => 
      toolSchema.parse({ name: "Ab", url: "https://example.com", description: "Valid description here" })
    ).toThrow();
  });

  it("rejects URLs without protocol", () => {
    expect(() =>
      toolSchema.parse({ name: "ValidTool", url: "example.com", description: "Valid description" })
    ).toThrow("Please provide a valid URL");
  });

  it("accepts valid complete submissions", () => {
    expect(() =>
      toolSchema.parse({
        name: "MyTool",
        url: "https://example.com/tool",
        description: "A helpful description"
      })
    ).not.toThrow();
  });
});

Add integration tests for the worker endpoint to verify end-to-end behavior.

Advanced Custom Validation Patterns

Async Validation (URL Reachability)

Extend your schema with refinements that fetch URLs:

const toolSchemaWithReachability = toolSchema.extend({
  url: z.string().url().max(200).refine(
    async (val) => {
      try {
        const res = await fetch(val, { method: "HEAD", mode: "no-cors" });
        return true;
      } catch {
        return false;
      }
    },
    { message: "URL appears unreachable" }
  ),
});

Note: Use async validation only in the back-end or with loading states in the UI to prevent blocking interactions.

Conditional Validation

Apply rules based on other field values:

const conditionalSchema = toolSchema.extend({
  category: z.enum(["free", "paid"]),
  price: z.number().optional().refine(
    (val, ctx) => {
      if (ctx.parent.category === "paid" && (val == null || val <= 0)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "Paid tools require a valid price",
        });
      }
      return true;
    }
  ),
});

Key Files Reference

File Purpose
src/hooks/useModal/fieldsMaker.tsx React modal building and form submission
cloudflare-worker/urlHandlers/handleSubmitTool.ts Cloudflare Worker request handler
src/validation/toolSchema.ts Central Zod schema (create this)
src/utils/formatters.ts Utility helpers for error formatting

Summary

  • Dual-layer validation is mandatory: Front-end for UX, back-end for security
  • Use Zod for isomorphic validation across browser and worker runtimes
  • Centralize schemas in src/validation/ to share between front-end and back-end
  • Always parse with schema.parse() in handleSubmitTool.ts before processing submissions
  • Return structured errors with field names to enable precise UI feedback
  • Test your schemas independently from UI components for maintainability

Frequently Asked Questions

What validation library works best with FckSignups?

Zod is optimal because it has zero runtime dependencies, excellent TypeScript inference, and runs identically in browsers and Cloudflare Workers. Alternatives like Yup or Joi require polyfills or have larger bundle sizes.

Can I skip front-end validation if I validate in the worker?

No. Front-end validation provides immediate feedback that improves user experience. Back-end validation alone creates frustrating cycles where users submit forms, wait for network round-trips, then correct errors. Implement both layers as demonstrated in fieldsMaker.tsx and handleSubmitTool.ts.

How do I validate file uploads or images?

FckSignups' current architecture stores tools as GitHub repository data, not blob storage. For image validation, add URL validation to check that provided image URLs return valid content-type headers, or integrate a service like Cloudflare Images and validate upload tokens in your worker.

Why does my schema fail in the worker but work in the browser?

Check that your import path in cloudflare-worker/urlHandlers/handleSubmitTool.ts correctly resolves to ../../../src/validation/toolSchema.ts. The Cloudflare Worker build may need path aliases configured, or you can duplicate the schema into the cloudflare-worker/ directory if module resolution becomes problematic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →