add-query-filter
Add custom query parameter filters to entity endpoints. Use when extending search/filter capabilities beyond the base pagination. Triggers on "add filter", "query parameter", "search filter", "filter by".
Best use case
add-query-filter is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Add custom query parameter filters to entity endpoints. Use when extending search/filter capabilities beyond the base pagination. Triggers on "add filter", "query parameter", "search filter", "filter by".
Teams using add-query-filter should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/add-query-filter/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How add-query-filter Compares
| Feature / Agent | add-query-filter | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Add custom query parameter filters to entity endpoints. Use when extending search/filter capabilities beyond the base pagination. Triggers on "add filter", "query parameter", "search filter", "filter by".
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Add Query Filter
Adds custom query parameter filters to entity schemas and repository implementations.
## Quick Reference
**Files to modify**:
1. `src/schemas/{entity}.schema.ts` - Add filter to query params schema
2. `src/repositories/mockdb/{entity}.mockdb.repository.ts` - Implement filter logic
3. `src/repositories/mongodb/{entity}.mongodb.repository.ts` - Implement filter logic
4. `tests/schemas/{entity}.schema.test.ts` - Test new filter
5. `tests/repositories/{entity}.*.repository.test.ts` - Test filter behavior
## Prerequisites
- Entity schema exists with `{entity}QueryParamsSchema`
- Repository implementations exist
## Instructions
### Step 1: Add Filter to Query Params Schema
Update `src/schemas/{entity}.schema.ts`:
```typescript
import { z } from "zod";
import { queryParamsSchema } from "@/schemas/shared.schema";
// Extend the base query params with entity-specific filters
export const {entity}QueryParamsSchema = queryParamsSchema.extend({
// Existing filters...
createdBy: z.string().optional(),
// Add new filter
{filterName}: z.string().optional(),
// OR for enum filter
status: z.enum(["draft", "published", "archived"]).optional(),
// OR for boolean filter (from query string)
isActive: z.coerce.boolean().optional(),
// OR for date range
createdAfter: z.coerce.date().optional(),
createdBefore: z.coerce.date().optional(),
// OR for numeric range
minPrice: z.coerce.number().optional(),
maxPrice: z.coerce.number().optional(),
});
export type {Entity}QueryParamsType = z.infer<typeof {entity}QueryParamsSchema>;
```
### Step 2: Implement Filter in MockDB Repository
Update `src/repositories/mockdb/{entity}.mockdb.repository.ts`:
```typescript
async findAll(query: {Entity}QueryParamsType): Promise<PaginatedResultType<{Entity}Type>> {
let filtered = [...this.{entities}];
// Existing filters
if (query.createdBy) {
filtered = filtered.filter((item) => item.createdBy === query.createdBy);
}
if (query.search) {
const searchLower = query.search.toLowerCase();
filtered = filtered.filter((item) =>
item.content.toLowerCase().includes(searchLower)
);
}
// NEW: Add your filter
if (query.{filterName}) {
filtered = filtered.filter((item) => item.{fieldName} === query.{filterName});
}
// For enum filter
if (query.status) {
filtered = filtered.filter((item) => item.status === query.status);
}
// For boolean filter
if (query.isActive !== undefined) {
filtered = filtered.filter((item) => item.isActive === query.isActive);
}
// For date range
if (query.createdAfter) {
filtered = filtered.filter((item) => item.createdAt >= query.createdAfter!);
}
if (query.createdBefore) {
filtered = filtered.filter((item) => item.createdAt <= query.createdBefore!);
}
// For numeric range
if (query.minPrice !== undefined) {
filtered = filtered.filter((item) => item.price >= query.minPrice!);
}
if (query.maxPrice !== undefined) {
filtered = filtered.filter((item) => item.price <= query.maxPrice!);
}
// ... sorting and pagination continue as before
}
```
### Step 3: Implement Filter in MongoDB Repository
Update `src/repositories/mongodb/{entity}.mongodb.repository.ts`:
```typescript
async findAll(query: {Entity}QueryParamsType): Promise<PaginatedResultType<{Entity}Type>> {
const collection = await this.getCollection();
const filter: Filter<{Entity}Document> = {};
// Existing filters
if (query.createdBy) {
filter.createdBy = query.createdBy;
}
if (query.search) {
filter.$text = { $search: query.search };
}
// NEW: Add your filter
if (query.{filterName}) {
filter.{fieldName} = query.{filterName};
}
// For enum filter
if (query.status) {
filter.status = query.status;
}
// For boolean filter
if (query.isActive !== undefined) {
filter.isActive = query.isActive;
}
// For date range
if (query.createdAfter || query.createdBefore) {
filter.createdAt = {};
if (query.createdAfter) {
filter.createdAt.$gte = query.createdAfter;
}
if (query.createdBefore) {
filter.createdAt.$lte = query.createdBefore;
}
}
// For numeric range
if (query.minPrice !== undefined || query.maxPrice !== undefined) {
filter.price = {};
if (query.minPrice !== undefined) {
filter.price.$gte = query.minPrice;
}
if (query.maxPrice !== undefined) {
filter.price.$lte = query.maxPrice;
}
}
// ... continue with sorting, pagination
}
```
### Step 4: Add Schema Tests
Update `tests/schemas/{entity}.schema.test.ts`:
```typescript
describe("{entity}QueryParamsSchema", () => {
// ... existing tests ...
it("accepts {filterName} filter", () => {
const parsed = {entity}QueryParamsSchema.parse({
{filterName}: "filter-value",
});
expect(parsed.{filterName}).toBe("filter-value");
});
// For enum filter
it("accepts valid status values", () => {
expect({entity}QueryParamsSchema.parse({ status: "draft" }).status).toBe("draft");
expect({entity}QueryParamsSchema.parse({ status: "published" }).status).toBe("published");
});
it("rejects invalid status values", () => {
expect(() => {entity}QueryParamsSchema.parse({ status: "invalid" })).toThrow();
});
// For boolean filter
it("coerces isActive to boolean", () => {
expect({entity}QueryParamsSchema.parse({ isActive: "true" }).isActive).toBe(true);
expect({entity}QueryParamsSchema.parse({ isActive: "false" }).isActive).toBe(false);
});
// For date filter
it("coerces date strings to Date objects", () => {
const parsed = {entity}QueryParamsSchema.parse({
createdAfter: "2024-01-01",
});
expect(parsed.createdAfter).toBeInstanceOf(Date);
});
// For numeric filter
it("coerces price filters to numbers", () => {
const parsed = {entity}QueryParamsSchema.parse({
minPrice: "10",
maxPrice: "100",
});
expect(parsed.minPrice).toBe(10);
expect(parsed.maxPrice).toBe(100);
});
});
```
### Step 5: Add Repository Tests
Update repository test files:
```typescript
describe("findAll", () => {
// ... existing tests ...
it("filters by {filterName}", async () => {
await repo.create({ content: "A", {fieldName}: "value1" }, userId);
await repo.create({ content: "B", {fieldName}: "value2" }, userId);
await repo.create({ content: "C", {fieldName}: "value1" }, userId);
const result = await repo.findAll({ {filterName}: "value1" });
expect(result.data.length).toBe(2);
expect(result.data.every((item) => item.{fieldName} === "value1")).toBe(true);
});
// For enum filter
it("filters by status", async () => {
await repo.create({ content: "A", status: "draft" }, userId);
await repo.create({ content: "B", status: "published" }, userId);
const result = await repo.findAll({ status: "published" });
expect(result.data.length).toBe(1);
expect(result.data[0].status).toBe("published");
});
// For date range
it("filters by date range", async () => {
// Create items with different dates
const old = await repo.create({ content: "Old" }, userId);
await new Promise((r) => setTimeout(r, 10));
const recent = await repo.create({ content: "Recent" }, userId);
const result = await repo.findAll({
createdAfter: old.createdAt,
});
expect(result.data.length).toBeGreaterThanOrEqual(1);
});
// For numeric range
it("filters by price range", async () => {
await repo.create({ content: "Cheap", price: 10 }, userId);
await repo.create({ content: "Mid", price: 50 }, userId);
await repo.create({ content: "Expensive", price: 100 }, userId);
const result = await repo.findAll({ minPrice: 20, maxPrice: 80 });
expect(result.data.length).toBe(1);
expect(result.data[0].content).toBe("Mid");
});
});
```
## Common Filter Patterns
### String Exact Match
```typescript
// Schema
categoryId: z.string().optional(),
// MockDB
if (query.categoryId) {
filtered = filtered.filter((item) => item.categoryId === query.categoryId);
}
// MongoDB
if (query.categoryId) {
filter.categoryId = query.categoryId;
}
```
### String Array (IN query)
```typescript
// Schema
tags: z.array(z.string()).optional(),
// OR from comma-separated string
tags: z.string().transform(s => s.split(",")).optional(),
// MockDB
if (query.tags?.length) {
filtered = filtered.filter((item) =>
query.tags!.some(tag => item.tags.includes(tag))
);
}
// MongoDB
if (query.tags?.length) {
filter.tags = { $in: query.tags };
}
```
### Partial Text Match
```typescript
// Schema
title: z.string().optional(),
// MockDB
if (query.title) {
const titleLower = query.title.toLowerCase();
filtered = filtered.filter((item) =>
item.title.toLowerCase().includes(titleLower)
);
}
// MongoDB (requires text index for $text, or use regex)
if (query.title) {
filter.title = { $regex: query.title, $options: "i" };
}
```
### Null/Not Null Check
```typescript
// Schema
hasParent: z.coerce.boolean().optional(),
// MockDB
if (query.hasParent !== undefined) {
if (query.hasParent) {
filtered = filtered.filter((item) => item.parentId != null);
} else {
filtered = filtered.filter((item) => item.parentId == null);
}
}
// MongoDB
if (query.hasParent !== undefined) {
if (query.hasParent) {
filter.parentId = { $ne: null };
} else {
filter.parentId = null;
}
}
```
## Adding MongoDB Indexes for Filters
If a filter is frequently used, add an index:
```typescript
// In MongoDB repository constructor or initialization
async ensureIndexes() {
const collection = await this.getCollection();
// Single field index
await collection.createIndex({ status: 1 });
// Compound index for common filter combinations
await collection.createIndex({ createdBy: 1, status: 1 });
// For date range queries
await collection.createIndex({ createdAt: -1 });
}
```
## Filter Validation Tips
### Coercion for Query Strings
Query parameters are always strings. Use `z.coerce` for non-string types:
```typescript
// Numbers
minPrice: z.coerce.number().optional(),
// Booleans
isActive: z.coerce.boolean().optional(),
// Dates
createdAfter: z.coerce.date().optional(),
```
### Default Values
```typescript
// With default
status: z.enum(["all", "active", "inactive"]).default("all"),
// Optional without default
status: z.enum(["draft", "published"]).optional(),
```
### Validation Constraints
```typescript
// Positive numbers only
minPrice: z.coerce.number().positive().optional(),
// Max length
search: z.string().max(100).optional(),
// Future dates only
eventDate: z.coerce.date().min(new Date()).optional(),
```
## What NOT to Do
- Do NOT forget to add the filter to both MockDB and MongoDB implementations
- Do NOT skip coercion for query string values
- Do NOT use complex filters without indexes in MongoDB
- Do NOT forget to test the filter behavior
- Do NOT filter on fields that don't exist in the schema
## See Also
- `create-schema` - Creating entity schemas
- `create-mockdb-repository` - MockDB repository implementation
- `create-mongodb-repository` - MongoDB repository implementation
- `test-schema` - Testing schema validation
- `test-mockdb-repository` - Testing repository filtersRelated Skills
AI Maestro Code Graph Query
PROACTIVELY query the code graph database to understand relationships and impact of changes. Use this skill WHEN READING any file to understand context, when searching for files, when exploring the codebase, or when you need to understand what depends on a component. This is your primary tool for understanding code structure and avoiding breaking changes.
ActiveRecord Query Patterns
Complete guide to ActiveRecord query optimization, associations, scopes, and PostgreSQL-specific patterns. Use this skill when writing database queries, designing model associations, creating migrations, optimizing query performance, or debugging N+1 queries and grouping errors.
Quick Query
Execute simple network status queries that require 1-2 commands. Use when user asks to "check device status", "show interface", "query routing table", "display BGP neighbors", or needs simple read-only information retrieval.
add-multi-filter
为Flutter插件视图添加多条件过滤功能(MultiFilterBar),支持标签、关键词、日期、优先级、复选框等多种过滤类型。替代传统的FilterDialog,提供更直观的两层级交互UI。适用场景:(1) 列表视图需要多维度筛选,(2) 数据量较大需要快速过滤,(3) 需要实时显示过滤条件的应用
bigquery-cli
Use when working with BigQuery from command line using bq tool, including querying data, loading/exporting tables, managing datasets, cost estimation with dry-run, or partitioning strategies
symfony:api-platform-filters
Use when symfony api platform filters
database-query-and-export
Query SQLite, PostgreSQL, and MySQL databases and export results to CSV/JSON. Use when: (1) Extracting data for reports, (2) Database backup and migration, (3) Data analysis workflows, or (4) Automated database queries.
bgo
Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.
backend-patterns
Backend patterns for ORPC routers, Drizzle schemas, and server-side code. Use when creating API endpoints, database tables, or services.
backend-passport-js
Authentication middleware for Express.js and Node.js applications. Use when building Express APIs that need JWT authentication, OAuth, or custom auth strategies. Provides 500+ authentication strategies. Choose Passport.js over Auth.js for Express backends, pure API servers, or when you need maximum control over auth flow.
backend-nodejs
Node.js/TypeScript backend expert. Handles Express/Fastify API routes, TypeScript strict mode, Prisma ORM, Zod validation, error handling, configuration management. Use when project is Node.js backend (package.json + TypeScript server).
Backend Node.js Expert
专注于 Node.js 后端开发模式与最佳实践。