Skip to content

Configuration ​

FancyCRUD provides a powerful configuration system that allows you to customize forms, tables, HTTP behavior, notifications, styling, and more. All configuration is done through the defineConfig function.

Basic Configuration ​

The minimum configuration requires an HTTP client and UI components from a wrapper:

ts
import { defineConfig } from '@fancy-crud/vue'
import axios from 'axios'
import components, { styles } from '@fancy-crud/wrapper-vuetify'

export const fancyCrud = defineConfig({
  http: {
    request: axios,
  },
  components,
  styles,
})

HTTP Configuration ​

HTTP Client ​

FancyCRUD supports any HTTP client that follows a promise-based interface. The most common choice is Axios:

ts
import axios from 'axios'

const axiosInstance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
})

export const fancyCrud = defineConfig({
  http: {
    request: axiosInstance,
  },
  // ... other config
})

Custom HTTP Client ​

You can use any HTTP client as long as it returns promises:

ts
import { defineConfig } from '@fancy-crud/vue'

export const fancyCrud = defineConfig({
  http: {
    request: {
      get: (url, config) => fetch(url, config).then(r => r.json()),
      post: (url, data, config) => fetch(url, { method: 'POST', body: JSON.stringify(data), ...config }).then(r => r.json()),
      put: (url, data, config) => fetch(url, { method: 'PUT', body: JSON.stringify(data), ...config }).then(r => r.json()),
      patch: (url, data, config) => fetch(url, { method: 'PATCH', body: JSON.stringify(data), ...config }).then(r => r.json()),
      delete: (url, config) => fetch(url, { method: 'DELETE', ...config }).then(r => r.json()),
    },
  },
  // ... other config
})

Pagination Configuration ​

Configure how FancyCRUD interprets paginated API responses:

ts
export const fancyCrud = defineConfig({
  http: {
    request: axios,
    pagination: {
      // Default Django REST Framework style
      results: 'results',  // Path to results array
      count: 'count',      // Path to total count
    },
  },
  // ... other config
})

For different API response structures:

ts
// Laravel pagination
pagination: {
  results: 'data',
  count: 'total',
}

// Custom nested structure
pagination: {
  results: 'response.items',
  count: 'response.pagination.total',
}

Default Settings ​

Set default values for forms and tables that will be applied unless overridden:

ts
export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  // Default form settings
  defaults: {
    // Field defaults by type
    text: {
      class: 'my-text-field',
      wrapper: {
        class: 'col-12 col-md-6',
      },
    },
    email: {
      class: 'my-email-field',
      wrapper: {
        class: 'col-12',
      },
    },
    select: {
      class: 'my-select-field',
      wrapper: {
        class: 'col-12 col-md-4',
      },
    },
    
    // Button defaults
    mainButton: {
      class: 'btn-primary',
      label: 'Save',
    },
    auxButton: {
      class: 'btn-secondary',
      label: 'Cancel',
    },
    
    // Table button defaults
    addButton: {
      class: 'btn-success',
      label: 'Add New',
      icon: 'mdi-plus',
    },
    editButton: {
      class: 'btn-info',
      label: 'Edit',
      icon: 'mdi-pencil',
    },
    removeButton: {
      class: 'btn-danger',
      label: 'Delete',
      icon: 'mdi-delete',
    },
  },
})

Notifications ​

Configure default notifications for form operations:

ts
export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  notifications: {
    // Notifications when creating records
    onCreateRecord: {
      success: {
        title: 'Success!',
        message: 'Record created successfully',
        type: 'success',
        duration: 3000,
      },
      error: {
        title: 'Error',
        message: 'Failed to create record',
        type: 'error',
        duration: 5000,
      },
    },
    
    // Notifications when updating records
    onUpdateRecord: {
      success: {
        title: 'Updated!',
        message: 'Record updated successfully',
        type: 'success',
        duration: 3000,
      },
      error: {
        title: 'Error',
        message: 'Failed to update record',
        type: 'error',
        duration: 5000,
      },
    },
  },
})

Response Interceptors ​

Define global response interceptors based on HTTP status codes:

ts
export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  responseInterceptor: {
    // Handle validation errors (422)
    422: (response, formId) => {
      const errors = response.response?.data?.errors || {}
      
      // Set field errors automatically
      bus.execute(
        new SetFieldsErrorsCommand(form.fields, errors)
      )
    },
    
    // Handle unauthorized (401)
    401: (response) => {
      console.log('User unauthorized, redirecting to login...')
      window.location.href = '/login'
    },
    
    // Handle forbidden (403)
    403: (response) => {
      console.log('Access forbidden')
      // Show notification or redirect
    },
    
    // Handle server errors (500)
    500: (response) => {
      console.error('Server error:', response)
      // Log to error tracking service
    },
  },
})

Validation Rules ​

Configure global validation rule parsers:

ts
export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  rulesConfig: {
    // Custom parser for validation results
    parser: (result) => {
      // Transform your validation library's result
      // to FancyCRUD's expected format
      if (result.valid) return true
      return result.message || 'Validation failed'
    },
    
    // Prevent error messages from showing
    preventErrorMessage: false,
  },
})

Integration with Validation Libraries ​

FancyCRUD can integrate with any validation library. Here are examples for the most popular ones:

Vuelidate ​

ts
import { useVuelidate } from '@vuelidate/core'
import { required, email, minLength } from '@vuelidate/validators'

export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  rulesConfig: {
    parser: (result) => {
      if (result.$error) {
        return result.$errors[0].$message
      }
      return true
    },
  },
})

Usage in forms:

ts
import { required, email, minLength } from '@vuelidate/validators'

const form = useForm({
  fields: {
    name: {
      type: 'text',
      label: 'Name',
      rules: (value) => {
        const validator = required
        const result = validator(value)
        if (!result) return 'Name is required'
        return true
      },
    },
    email: {
      type: 'text',
      label: 'Email',
      rules: (value) => {
        const emailValidator = email
        const result = emailValidator(value)
        if (!result) return 'Invalid email format'
        return true
      },
    },
  },
})

Yup ​

ts
import * as yup from 'yup'

export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  rulesConfig: {
    parser: async (result) => {
      if (result instanceof Promise) {
        try {
          await result
          return true
        } catch (error) {
          return error.message
        }
      }
      return result
    },
  },
})

Usage in forms:

ts
import * as yup from 'yup'

const form = useForm({
  fields: {
    name: {
      type: 'text',
      label: 'Name',
      rules: (value) => {
        const schema = yup.string().required('Name is required').min(3, 'Name must be at least 3 characters')
        return schema.validate(value)
      },
    },
    email: {
      type: 'text',
      label: 'Email',
      rules: (value) => {
        const schema = yup.string().email('Invalid email format').required('Email is required')
        return schema.validate(value)
      },
    },
    age: {
      type: 'text',
      label: 'Age',
      rules: (value) => {
        const schema = yup.number().positive('Age must be positive').integer('Age must be an integer').required('Age is required')
        return schema.validate(value)
      },
    },
  },
})

Zod ​

ts
import { z } from 'zod'

export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  rulesConfig: {
    parser: (result) => {
      if (result.success === false) {
        return result.error.errors[0].message
      }
      return true
    },
  },
})

Usage in forms:

ts
import { z } from 'zod'

const form = useForm({
  fields: {
    name: {
      type: 'text',
      label: 'Name',
      rules: (value) => {
        const schema = z.string().min(3, 'Name must be at least 3 characters')
        return schema.safeParse(value)
      },
    },
    email: {
      type: 'text',
      label: 'Email',
      rules: (value) => {
        const schema = z.string().email('Invalid email format')
        return schema.safeParse(value)
      },
    },
    age: {
      type: 'text',
      label: 'Age',
      rules: (value) => {
        const schema = z.coerce.number().positive('Age must be positive').int('Age must be an integer')
        return schema.safeParse(value)
      },
    },
    website: {
      type: 'text',
      label: 'Website',
      rules: (value) => {
        if (!value) return true // Optional field
        const schema = z.string().url('Invalid URL format')
        return schema.safeParse(value)
      },
    },
  },
})

Valibot ​

ts
import * as v from 'valibot'

export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  styles,
  
  rulesConfig: {
    parser: (result) => {
      if (result.success === false) {
        return result.issues[0].message
      }
      return true
    },
  },
})

Usage in forms:

ts
import * as v from 'valibot'

const form = useForm({
  fields: {
    name: {
      type: 'text',
      label: 'Name',
      rules: (value) => {
        const schema = v.pipe(
          v.string(),
          v.minLength(3, 'Name must be at least 3 characters')
        )
        return v.safeParse(schema, value)
      },
    },
    email: {
      type: 'text',
      label: 'Email',
      rules: (value) => {
        const schema = v.pipe(
          v.string(),
          v.email('Invalid email format')
        )
        return v.safeParse(schema, value)
      },
    },
    age: {
      type: 'text',
      label: 'Age',
      rules: (value) => {
        const schema = v.pipe(
          v.number(),
          v.integer('Age must be an integer'),
          v.minValue(1, 'Age must be at least 1'),
          v.maxValue(120, 'Age must be less than 120')
        )
        return v.safeParse(schema, Number(value))
      },
    },
    password: {
      type: 'password',
      label: 'Password',
      rules: (value) => {
        const schema = v.pipe(
          v.string(),
          v.minLength(8, 'Password must be at least 8 characters'),
          v.regex(/[A-Z]/, 'Password must contain at least one uppercase letter'),
          v.regex(/[a-z]/, 'Password must contain at least one lowercase letter'),
          v.regex(/[0-9]/, 'Password must contain at least one number')
        )
        return v.safeParse(schema, value)
      },
    },
  },
})

Installation Commands ​

bash
npm install @vuelidate/core @vuelidate/validators
# or
pnpm add @vuelidate/core @vuelidate/validators
# or
yarn add @vuelidate/core @vuelidate/validators
bash
npm install yup
# or
pnpm add yup
# or
yarn add yup
bash
npm install zod
# or
pnpm add zod
# or
yarn add zod
bash
npm install valibot
# or
pnpm add valibot
# or
yarn add valibot

Comparison ​

LibraryBundle SizeType SafetyAsync SupportPerformance
Vuelidate~5kb⭐⭐⭐✅Good
Yup~15kb⭐⭐⭐⭐✅Good
Zod~8kb⭐⭐⭐⭐⭐✅Excellent
Valibot~1kb⭐⭐⭐⭐⭐✅Excellent

TIP

Zod and Valibot are recommended for new projects due to their excellent TypeScript support and small bundle size. Valibot is especially good if bundle size is a critical concern.

Styling Configuration ​

The styles object from wrappers contains default CSS classes for components. You can override or extend them:

ts
import components, { styles as defaultStyles } from '@fancy-crud/wrapper-vuetify'

export const fancyCrud = defineConfig({
  http: { request: axios },
  components,
  
  styles: {
    ...defaultStyles,
    
    // Override form container styles
    formContainer: 'custom-form-container my-form',
    
    // Override field wrapper styles
    fieldWrapper: 'custom-field-wrapper',
    
    // Override button styles
    primaryButton: 'custom-btn custom-btn-primary',
    secondaryButton: 'custom-btn custom-btn-secondary',
  },
})

Complete Configuration Example ​

Here's a comprehensive configuration example:

ts
import { defineConfig } from '@fancy-crud/vue'
import axios from 'axios'
import components, { styles } from '@fancy-crud/wrapper-vuetify'

// Configure axios instance
const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
})

// Add auth token to requests
axiosInstance.interceptors.request.use((config) => {
  const token = localStorage.getItem('auth_token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

export const fancyCrud = defineConfig({
  http: {
    request: axiosInstance,
    pagination: {
      results: 'results',
      count: 'count',
    },
  },
  
  components,
  styles,
  
  defaults: {
    text: {
      class: 'text-field',
      wrapper: { class: 'col-12 col-md-6' },
    },
    textarea: {
      class: 'textarea-field',
      wrapper: { class: 'col-12' },
    },
    select: {
      class: 'select-field',
      wrapper: { class: 'col-12 col-md-4' },
    },
    mainButton: {
      class: 'btn-primary',
      label: 'Save',
    },
    auxButton: {
      class: 'btn-secondary',
      label: 'Cancel',
    },
    addButton: {
      class: 'btn-success',
      label: 'Add New',
      icon: 'mdi-plus',
    },
    editButton: {
      class: 'btn-info',
      label: 'Edit',
      icon: 'mdi-pencil',
    },
    removeButton: {
      class: 'btn-danger',
      label: 'Delete',
      icon: 'mdi-delete',
    },
  },
  
  notifications: {
    onCreateRecord: {
      success: {
        title: 'Success!',
        message: 'Record created successfully',
        type: 'success',
        duration: 3000,
      },
      error: {
        title: 'Error',
        message: 'Failed to create record',
        type: 'error',
        duration: 5000,
      },
    },
    onUpdateRecord: {
      success: {
        title: 'Updated!',
        message: 'Changes saved successfully',
        type: 'success',
        duration: 3000,
      },
      error: {
        title: 'Error',
        message: 'Failed to save changes',
        type: 'error',
        duration: 5000,
      },
    },
  },
  
  responseInterceptor: {
    422: (response, formId) => {
      // Handle validation errors
      const errors = response.response?.data?.errors || {}
      // Set field errors
    },
    401: () => {
      // Redirect to login
      window.location.href = '/login'
    },
    403: () => {
      // Show access denied message
      console.log('Access denied')
    },
  },
  
  rulesConfig: {
    preventErrorMessage: false,
  },
})

Environment-Specific Configuration ​

You can create different configurations for different environments:

ts
import { defineConfig } from '@fancy-crud/vue'
import axios from 'axios'
import components, { styles } from '@fancy-crud/wrapper-vuetify'

const axiosInstance = axios.create({
  baseURL: 'http://localhost:8000/api',
})

export const fancyCrud = defineConfig({
  http: { request: axiosInstance },
  components,
  styles,
})
ts
import { defineConfig } from '@fancy-crud/vue'
import axios from 'axios'
import components, { styles } from '@fancy-crud/wrapper-vuetify'

const axiosInstance = axios.create({
  baseURL: 'https://api.production.com',
})

export const fancyCrud = defineConfig({
  http: { request: axiosInstance },
  components,
  styles,
})
ts
import { createApp } from 'vue'
import { FancyCrud } from '@fancy-crud/vue'

const config = import.meta.env.PROD
  ? await import('./config.production')
  : await import('./config.development')

app.use(FancyCrud, config.fancyCrud)

Next Steps ​

Now that you've configured FancyCRUD, you can: