copywriting
Marketing copy for homepages, landing pages, pricing pages, and other conversion-focused web pages.
zodipus-custom-schemas
Define custom Zod schemas for Prisma JSON fields using @zodSchema annotations. Use when working with typed JSON, metadata fields, settings objects, complex JSON structures in Prisma, or custom-schemas.ts.
Full skill instructions
Type your Prisma JSON fields with custom Zod schemas using @zodSchema annotations.
@zodSchema annotationcustom-schemas.tsPrisma's Json type has no runtime type safety:
model Post {
id String @id
metadata Json? // Type: JsonValue (any)
}
// No type safety - anything goes
post.metadata = { random: 'stuff', 123: true };
post.metadata = 'just a string';
post.metadata = [1, 2, 3];
Use @zodSchema to define exactly what your JSON should contain.
Add a triple-slash comment with @zodSchema SchemaName above your JSON field:
model Post {
id String @id @default(cuid())
title String
content String?
/// @zodSchema PostMetadataSchema
metadata Json?
}
model User {
id String @id @default(cuid())
email String @unique
/// @zodSchema UserSettingsSchema
settings Json @default("{}")
/// @zodSchema UserPreferencesSchema
preferences Json?
}
npx prisma generate
This creates a custom-schemas.ts file with placeholder schemas:
// generated/custom-schemas.ts
import { z } from 'zod';
// Placeholder - replace with your schema
export const PostMetadataSchema = z.unknown();
export const UserSettingsSchema = z.unknown();
export const UserPreferencesSchema = z.unknown();
Replace the placeholders with real schemas:
// generated/custom-schemas.ts
import { z } from 'zod';
export const PostMetadataSchema = z.object({
tags: z.array(z.string()).default([]),
views: z.number().int().nonnegative().default(0),
featured: z.boolean().default(false),
seo: z.object({
title: z.string().max(60).optional(),
description: z.string().max(160).optional(),
keywords: z.array(z.string()).optional(),
}).optional(),
});
export const UserSettingsSchema = z.object({
theme: z.enum(['light', 'dark', 'system']).default('system'),
notifications: z.object({
email: z.boolean().default(true),
push: z.boolean().default(false),
sms: z.boolean().default(false),
}).default({}),
language: z.string().default('en'),
timezone: z.string().default('UTC'),
});
export const UserPreferencesSchema = z.object({
dashboard: z.object({
layout: z.enum(['grid', 'list']).default('grid'),
itemsPerPage: z.number().int().min(10).max(100).default(20),
}).optional(),
privacy: z.object({
showEmail: z.boolean().default(false),
showActivity: z.boolean().default(true),
}).optional(),
});
Now your schemas have full type safety:
import { PostSchema, UserSchema } from './generated';
// Validated with your custom schema
const post = PostSchema.parse({
id: '123',
title: 'My Post',
content: 'Hello world',
metadata: {
tags: ['tutorial', 'prisma'],
views: 100,
featured: true,
seo: {
title: 'My Post - Tutorial',
description: 'Learn about Prisma',
},
},
});
// TypeScript knows the shape
post.metadata.tags; // string[]
post.metadata.views; // number
post.metadata.seo?.title; // string | undefined
export const AppSettingsSchema = z.object({
theme: z.enum(['light', 'dark', 'system']).default('system'),
fontSize: z.enum(['small', 'medium', 'large']).default('medium'),
compactMode: z.boolean().default(false),
sidebar: z.object({
collapsed: z.boolean().default(false),
width: z.number().int().min(200).max(400).default(280),
}).default({}),
});
export const AuditMetadataSchema = z.object({
createdBy: z.string().optional(),
updatedBy: z.string().optional(),
version: z.number().int().default(1),
changelog: z.array(z.object({
date: z.coerce.date(),
user: z.string(),
action: z.string(),
})).default([]),
});
export const NotificationConfigSchema = z.object({
enabled: z.boolean().default(true),
channels: z.object({
email: z.boolean().default(true),
slack: z.boolean().default(false),
webhook: z.string().url().optional(),
}).default({}),
frequency: z.enum(['instant', 'hourly', 'daily']).default('instant'),
quietHours: z.object({
enabled: z.boolean().default(false),
start: z.string().regex(/^\d{2}:\d{2}$/).optional(),
end: z.string().regex(/^\d{2}:\d{2}$/).optional(),
}).optional(),
});
export const ProductVariantsSchema = z.array(z.object({
sku: z.string(),
name: z.string(),
price: z.number().positive(),
stock: z.number().int().nonnegative(),
attributes: z.record(z.string()).optional(),
}));
// Simple union
export const PaymentMethodSchema = z.union([
z.object({
type: z.literal('card'),
last4: z.string().length(4),
brand: z.enum(['visa', 'mastercard', 'amex']),
expiryMonth: z.number().int().min(1).max(12),
expiryYear: z.number().int(),
}),
z.object({
type: z.literal('bank'),
bankName: z.string(),
accountLast4: z.string().length(4),
}),
z.object({
type: z.literal('paypal'),
email: z.string().email(),
}),
]);
// Discriminated union (recommended for type inference)
export const PaymentMethodSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('card'),
last4: z.string().length(4),
brand: z.enum(['visa', 'mastercard', 'amex']),
}),
z.object({
type: z.literal('bank'),
bankName: z.string(),
accountLast4: z.string().length(4),
}),
]);
// Tree structure (menu, categories, etc.)
interface TreeNode {
id: string;
name: string;
children?: TreeNode[];
}
export const TreeNodeSchema: z.ZodType<TreeNode> = z.lazy(() =>
z.object({
id: z.string(),
name: z.string(),
children: z.array(TreeNodeSchema).optional(),
})
);
export const MenuSchema = z.array(TreeNodeSchema);
// For arbitrary key-value pairs
export const CustomFieldsSchema = z.record(
z.string(),
z.union([z.string(), z.number(), z.boolean(), z.null()])
);
// With constraints
export const LabeledFieldsSchema = z.record(
z.string().regex(/^[a-z_][a-z0-9_]*$/), // lowercase snake_case keys
z.string().max(500)
);
Prevents null/undefined issues when creating new records:
export const SettingsSchema = z.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
}).default({}); // Default for the entire object
Be clear about what's required vs optional:
export const ProfileSchema = z.object({
// Required
displayName: z.string().min(1).max(50),
// Optional (can be undefined)
bio: z.string().max(500).optional(),
// Nullable (can be null)
avatarUrl: z.string().url().nullable(),
// Optional with default
isPublic: z.boolean().default(true),
});
Add business rule validations:
export const PriceSchema = z.object({
amount: z.number().positive(),
currency: z.enum(['USD', 'EUR', 'GBP']),
discount: z.number().min(0).max(100).optional(),
}).refine(
(data) => !data.discount || data.amount > 0,
{ message: 'Discount requires positive amount' }
);
.describe()Add descriptions for documentation:
export const ApiConfigSchema = z.object({
endpoint: z.string().url().describe('API base URL'),
timeout: z.number().int().positive().default(30000).describe('Request timeout in ms'),
retries: z.number().int().min(0).max(5).default(3).describe('Number of retry attempts'),
});
When you run npx prisma generate:
@zodSchema annotations: Adds placeholder to custom-schemas.tsEnsure the annotation is a triple-slash comment:
// Wrong - single slash
// @zodSchema MySchema
metadata Json?
// Wrong - double slash doc comment
/** @zodSchema MySchema */
metadata Json?
// Correct - triple slash
/// @zodSchema MySchema
metadata Json?
Run generation again to update model schemas:
npx prisma generate
The annotation name must exactly match the export:
/// @zodSchema PostMetadataSchema
metadata Json?
// Must match exactly
export const PostMetadataSchema = z.object({...});
Marketing copy for homepages, landing pages, pricing pages, and other conversion-focused web pages.
Fast image generation with Flux 2 Klein, optimized for sub-second iteration and multi-reference brand styling.
Psychological principles and mental models for understanding customer behavior and influencing marketing decisions.
Plan searchable and shareable content that drives traffic, builds authority, and generates leads.
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Brainstorm and prioritize marketing strategies tailored to your SaaS stage, budget, and goals.
Optimize content for discovery, citation, and visibility across AI search platforms and LLM assistants.
Systematic improvement of existing marketing copy through seven focused editing passes.
Plan and optimize your website's page hierarchy, navigation, URL structure, and internal linking.
Design and optimize systems connecting marketing, sales, and customer success into a unified revenue engine.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Convert websites into professional HyperFrames videos with guided storyboarding and brand-aligned composition.
Revolutionize Video Creation with AI-Generated Avatars and Voices
Social media on autopilot
Design custom logos instantly with AI.
Text-heavy marketing and design images
Boost Your SEO with Content and SEO Silos
Revolutionize Your Marketing with AI-Powered MarketingBlocks!
10X faster & free AI content creation for 100+ use cases
Tribescaler | Make Your Tweets Go Viral