Zod AI Coding Rules

Zod AI rules help engineering teams get better results from AI coding assistants like Cursor, Windsurf, and GitHub Copilot. By defining clear conventions for code style, architecture patterns, error handling, and module organisation, Zod AI rules ensure that generated code is consistent, maintainable, and production-ready. Whether you are working on a side project or a large-scale enterprise system, community-curated rules on AI Rules Hub provide a solid foundation you can adopt instantly and customise to fit your team's standards.

Why Use AI Rules for Zod?

  • Ensure AI-generated Zod code follows your team's conventions
  • Prevent common anti-patterns that degrade maintainability
  • Reduce code review cycles by getting AI output right the first time
  • Standardise error handling, logging, and module structure
  • Make AI assistants produce secure and performance-conscious code

Best Practices for Zod AI Coding

Define a Consistent Code Style

Specify formatting preferences (indentation, quotes, trailing commas) for Zod so AI output matches your linter configuration without manual edits.

Enforce Error Handling Patterns

Instruct AI to always handle errors explicitly, use structured logging, and avoid swallowing exceptions silently.

Set Module Organisation Rules

Define how Zod modules should be organised — feature folders, barrel exports, and separation of concerns — so AI keeps the project structure clean.

Require Security-Conscious Patterns

Add rules that enforce input validation, sanitisation, and safe dependency usage so AI never introduces obvious security vulnerabilities.

Common Patterns & Standards

#01

Separation of Concerns

Keep business logic, data access, and presentation layers separate in Zod projects so each layer is independently testable.

#02

Dependency Injection

Pass dependencies explicitly through constructors or function parameters — avoiding global state that makes testing difficult.

#03

Consistent Naming Conventions

Rule AI to follow Zod community naming standards for files, classes, functions, and constants.

#04

Automated Testing Standards

Define what test types are required (unit, integration) and where test files should live so AI generates tests alongside implementation code.

Top Zod Rules on AI Rules Hub

No description provided.

# Pure State — Zustand Patterns That Actually Scale

> **AI Instruction**: This document defines the rules, conventions, and architecture decisions for implementing Zustand state management in a React + TypeScript project. Do not deviate from these patterns. When generating stores, hooks, or components — always refer back to these rules first.

---

## Stack

- **Zustand v4+** — primary state management
- **TypeScript strict mode** — all stores must be fully typed
- **Immer middleware** — for any nested state mutations
- **DevTools middleware** — applied to every store, always
- **Persist middleware** — only for state that must survive page refresh

---

## Folder Structure

```
src/
├── stores/
│   ├── index.ts                  # Re-export all stores + resetAllStores()
│   ├── useAuthStore.ts
│   ├── useUIStore.ts
│   ├── useUserStore.ts
│   └── slices/                  # Only for large, multi-concern stores
│       ├── types.ts
│       ├── authSlice.ts
│       └── cartSlice.ts
├── hooks/
│   └── useStoreSelectors.ts     # All memoized selectors live here
└── types/
    └── store.d.ts               # Shared store type declarations
```

---

## Store Architecture Rules

### One Store Per Domain
Every store must own exactly one concern. Never mix auth logic with UI state, or user profile with cart data. If two pieces of state are unrelated, they belong in separate stores. The store name must clearly reflect what it owns — `useAuthStore`, `useCartStore`, `useUIStore`.

### Always Define an Initial State Object
Every store must declare a separate `initialState` const above the store definition. This object is what the `reset()` action returns to. It also documents at a glance what shape the store holds without reading the full type definition.

### Every Store Must Have a Reset Action
Without exception, every store must implement a `reset()` action that restores the store to its `initialState`. This is called on logout, session expiry, or any global teardown. A store without `reset()` is incomplete.

### Always Apply DevTools Middleware
Every store — no matter how small — must be wrapped with `devtools()` middleware. The store name must be passed as the `name` option. Every `set()` call must include a descriptive action name as the third argument so that DevTools traces are meaningful and debuggable.

### Actions Belong Inside the Store
No business logic or state mutation should live outside the store. Components call store actions — they never call `set()` directly or manage async themselves. If a component is doing `setState` based on API data, that logic needs to move into a store action.

---

## TypeScript Conventions

### Always Separate State from Actions in the Interface
Define the store interface with a clear visual separation — state fields first, then actions. Use comments to divide them. This makes the interface scannable and prevents confusion between what is data and what is callable.

### Use Strict Typing for All State Fields
Never use `any`. Every field must have an explicit type. Optional fields must be typed as `T | null` — never as `T | undefined` unless the field is genuinely optional in a form or partial update context. Null signals "not yet loaded". Undefined signals "doesn't exist".

### Infer Action Parameter Types from Zod Schemas When Available
If the project uses Zod for API validation, action input types should be inferred from those schemas using `z.infer<typeof schema>`. Never duplicate type definitions between Zod schemas and store interfaces.

---

## Async Action Rules

### Every Async Action Must Follow the Three-Phase Pattern
When an async action starts, immediately set `isLoading: true` and `error: null`. On success, set the result data and `isLoading: false`. On failure, set the error message and `isLoading: false`. No async action is complete without all three phases handled.

### Always Name Async Action States in DevTools
When calling `set()` inside an async action, name each phase clearly — for example `login/pending`, `login/fulfilled`, `login/rejected`. This mirrors Redux Toolkit conventions and makes DevTools traces readable.

### Never Let Errors Silently Fail
Every catch block must write to the store's `error` field. The error message must be a human-readable string. Always check if the caught value is an instance of `Error` before accessing `.message` — otherwise fall back to a generic string.

### Access Current State Inside Async Actions with `get()`
When an async action needs to read current state mid-execution — for example to attach a token to a request — use `get()` from the store creator. Never close over state values from outside the action, as they may be stale.

### Never Trigger Actions from Inside Other Actions
Actions must not call sibling actions directly. If two actions share logic, extract that logic into a private utility function inside the store file and call it from both. Cross-action dependencies cause unpredictable execution order.

---

## Selector Rules

### Never Subscribe to the Whole Store
Components must never destructure the full store object. Subscribing to the whole store causes the component to re-render on every single state change regardless of relevance. Always pass a selector function that picks exactly the value the component needs.

### All Selectors Live in `useStoreSelectors.ts`
Every named selector — `useIsAuthenticated`, `useCurrentUser`, `useAuthLoading` — must be defined in the dedicated selectors file and exported from there. Components import from this file, not directly from the store. This centralizes all selector logic and makes refactoring easier.

### Actions Are Safe to Destructure Together
Action functions are stable references and do not cause re-renders. It is acceptable to select a group of actions together in one selector. State values must always be selected individually.

### Use Shallow Comparison for Object Selections
If a selector must return an object with multiple fields, use `shallow` from Zustand as the equality function. Without this, the component re-renders even when the object's values haven't changed because a new object reference is returned on every call.

---

## Immer Middleware Rules

### Use Immer Only When State is Nested Two or More Levels Deep
For flat state with primitive values, use regular `set()`. Apply Immer middleware to stores where deeply nested objects need to be updated — user profiles with nested preferences, addresses, or settings. Immer is not needed for simple top-level assignments.

### Never Return and Mutate in the Same Immer Callback
Inside an Immer `set()` callback, either mutate the draft directly or return a new object — never both. Mixing the two causes Immer to throw. Choose mutation style for nested updates, return style for complete state replacements.

### Always Guard Against Null Before Mutating Nested Objects
Before mutating a nested object inside an Immer callback, always check that the parent exists. If the parent is null and the action tries to mutate a child property, Immer will throw a runtime error. Defensive null checks are mandatory.

---

## Persist Middleware Rules

### Only Persist What Is Truly Necessary
Never persist entire store state by default. Always use `partialize` to explicitly whitelist fields that should survive a page refresh. Persisting loading states, error messages, or transient UI values is a bug.

### Always Name the Storage Key Explicitly
The `name` field in persist config is the localStorage key. It must be explicit, unique, and human-readable — for example `auth-storage` or `theme-preferences`. Never use auto-generated or ambiguous names.

### Persist Tokens, Not Sensitive Data
Authentication tokens may be persisted in localStorage for session continuity. Passwords, raw API responses, or personally identifiable information must never be persisted. When in doubt, do not persist.

---

## Slice Pattern Rules

### Use Slices Only for Large Multi-Domain Stores
The slice pattern — combining multiple `StateCreator` functions into one bound store — is appropriate when a single store legitimately crosses domains, such as a checkout flow that needs both cart state and auth state together. For typical apps, separate stores are preferred over slices.

### Define All Slice Types in a Shared Types File
Every slice interface must be defined in `slices/types.ts`. The combined `AppStore` type that merges all slice interfaces also lives there. Individual slice files import from types — they never redefine their own interfaces inline.

### Each Slice Is Responsible Only for Its Own State
A slice action must never directly mutate another slice's state. If coordination is needed between slices, it must happen at the bound store level through composition, not by cross-referencing slice internals.

---

## Reset and Cleanup Rules

### Export a `resetAllStores()` Function from `stores/index.ts`
The `index.ts` file must export a single `resetAllStores()` function that calls `.getState().reset()` on every store. This function is called in exactly one place — the logout handler. It is never called from components directly.

### Subscriptions Created Outside React Must Be Unsubscribed
If `store.subscribe()` is used outside a React component — for example to attach an auth token to an API client — the returned unsubscribe function must be stored and called during application teardown. Leaking subscriptions causes stale callbacks.

---

## Component Integration Rules

### Components Are Consumers Only
A component's only job with respect to state is to read values via selectors and call actions on user interaction. Components must not derive new state, filter arrays, or compute totals inline. That logic belongs in the store or in a dedicated selector.

### Co-locate Loading and Error State with Their Data
Whenever a component renders async data, it must also handle the corresponding `isLoading` and `error` states from the same store. Rendering data without handling its loading and error states is incomplete.

### Clear Errors Explicitly
After an async action fails and the error is displayed, always provide a mechanism — a dismiss button, a timeout, or a navigation event — to call `clearError()`. Errors must not persist in state indefinitely.

---

## Anti-Patterns — Never Do These

| ❌ Never | ✅ Always |
|---|---|
| Subscribe to the full store object | Select only the value needed |
| Put async logic inside components | Move async into store actions |
| Mutate state directly | Use `set()` or Immer |
| Create a store without DevTools | Every store gets `devtools()` |
| Skip loading or error state | All three async phases are required |
| Mix UI state and domain state | Separate stores per concern |
| Persist loading, errors, or UI flags | Only persist tokens and preferences |
| Call `reset()` from components | Use `resetAllStores()` from logout only |
| Duplicate types already in Zod schemas | Infer with `z.infer<>` |
| Hardcode localStorage key strings | Define as named constants |

---

## Final Checklist for AI Initialization

- [ ] One store file created per domain concern
- [ ] Every store has `initialState` const defined above the store
- [ ] Every store has a `reset()` action returning `initialState`
- [ ] Every store is wrapped with `devtools()` middleware and named
- [ ] All async actions handle `pending`, `fulfilled`, and `rejected` phases
- [ ] All selectors are defined in `useStoreSelectors.ts`
- [ ] No component subscribes to the full store object
- [ ] `resetAllStores()` is exported from `stores/index.ts`
- [ ] Persist middleware uses `partialize` to whitelist only necessary fields
- [ ] Immer middleware applied only to stores with nested state
- [ ] All store interfaces separate state fields from action fields
- [ ] No async logic lives inside React components
4 views

Share Your Zod AI Rules

Have rules that improved your Zod workflow? Submit them to AI Rules Hub and help the community get better results from AI coding assistants.

Frequently Asked Questions

Zod AI rules are context files (like `.cursorrules` or `AGENTS.md`) that instruct AI coding assistants to follow Zod best practices — covering code style, architecture, error handling, and testing conventions.

Command Palette

Search for a command to run...