Skip to main content
Refine’s notification system provides automatic notifications for CRUD operations and allows you to display custom messages throughout your application.

Understanding Notification Providers

A notification provider implements methods to show and hide notifications:
import { NotificationProvider } from "@refinedev/core";

const notificationProvider: NotificationProvider = {
  open: (params: OpenNotificationParams) => void,
  close: (key: string) => void,
};

interface OpenNotificationParams {
  key?: string;
  message: string;
  type: "success" | "error" | "progress";
  description?: string;
  cancelMutation?: () => void;
  undoableTimeout?: number;
}

Quick Setup

1
Choose a Solution
2
Refine provides built-in notification providers for popular UI frameworks:
3
  • Ant Design - @refinedev/antd
  • Material UI - @refinedev/mui
  • Mantine - @refinedev/mantine
  • Chakra UI - @refinedev/chakra-ui
  • react-toastify - Custom implementation
  • 4
    Install Dependencies
    5
    Ant Design
    npm install @refinedev/antd antd
    
    Material UI
    npm install @refinedev/mui @mui/material
    
    Mantine
    npm install @refinedev/mantine @mantine/notifications
    
    react-toastify
    npm install react-toastify
    
    6
    Configure the Provider
    7
    Ant Design
    import { Refine } from "@refinedev/core";
    import { useNotificationProvider } from "@refinedev/antd";
    import { App as AntdApp } from "antd";
    
    const App = () => (
      <AntdApp>
        <Refine
          notificationProvider={useNotificationProvider}
          // ... other props
        >
          {/* Your app */}
        </Refine>
      </AntdApp>
    );
    
    Material UI
    import { Refine } from "@refinedev/core";
    import { useNotificationProvider, RefineSnackbarProvider } from "@refinedev/mui";
    
    const App = () => (
      <RefineSnackbarProvider>
        <Refine
          notificationProvider={useNotificationProvider}
          // ... other props
        >
          {/* Your app */}
        </Refine>
      </RefineSnackbarProvider>
    );
    
    Mantine
    import { Refine } from "@refinedev/core";
    import { useNotificationProvider } from "@refinedev/mantine";
    import { NotificationsProvider } from "@mantine/notifications";
    
    const App = () => (
      <NotificationsProvider position="top-right">
        <Refine
          notificationProvider={useNotificationProvider}
          // ... other props
        >
          {/* Your app */}
        </Refine>
      </NotificationsProvider>
    );
    
    react-toastify
    import { Refine } from "@refinedev/core";
    import { ToastContainer, toast } from "react-toastify";
    import "react-toastify/dist/ReactToastify.css";
    
    const notificationProvider: NotificationProvider = {
      open: ({ message, type, key, undoableTimeout, cancelMutation }) => {
        if (type === "progress") {
          if (toast.isActive(key as string)) {
            toast.update(key as string, {
              progress: (undoableTimeout || 0) / 10,
              render: message,
            });
          } else {
            toast(message, {
              toastId: key,
              progress: (undoableTimeout || 0) / 10,
              type: "default",
            });
          }
        } else {
          toast(message, { toastId: key, type });
        }
      },
      close: (key) => toast.dismiss(key),
    };
    
    const App = () => (
      <Refine notificationProvider={notificationProvider}>
        {/* Your app */}
        <ToastContainer />
      </Refine>
    );
    

    Automatic Notifications

    Refine automatically shows notifications for CRUD operations:

    Create Operations

    import { useCreate } from "@refinedev/core";
    
    const PostCreate = () => {
      const { mutate } = useCreate();
    
      const handleSubmit = (values: any) => {
        mutate({
          resource: "posts",
          values,
        });
        // Automatically shows:
        // ✅ "Successfully created posts"
      };
    };
    

    Update Operations

    import { useUpdate } from "@refinedev/core";
    
    const PostEdit = () => {
      const { mutate } = useUpdate();
    
      const handleSubmit = (values: any) => {
        mutate({
          resource: "posts",
          id: "1",
          values,
        });
        // Automatically shows:
        // ✅ "Successfully updated posts"
      };
    };
    

    Delete Operations

    import { useDelete } from "@refinedev/core";
    
    const PostList = () => {
      const { mutate } = useDelete();
    
      const handleDelete = (id: string) => {
        mutate({
          resource: "posts",
          id,
        });
        // Automatically shows:
        // ✅ "Successfully deleted posts"
      };
    };
    

    Error Notifications

    Automatic error notifications on failure:
    const { mutate } = useCreate();
    
    mutate({
      resource: "posts",
      values,
    });
    // On error:
    // ❌ "Error when creating posts (status code: 500)"
    

    Custom Notifications

    Using useNotification

    import { useNotification } from "@refinedev/core";
    
    const MyComponent = () => {
      const { open, close } = useNotification();
    
      const showSuccess = () => {
        open?.({
          type: "success",
          message: "Operation completed!",
          description: "Everything went smoothly.",
        });
      };
    
      const showError = () => {
        open?.({
          type: "error",
          message: "Something went wrong",
          description: "Please try again later.",
        });
      };
    
      const showCustom = () => {
        const key = "custom-notification";
        
        open?.({
          key,
          type: "success",
          message: "Processing...",
        });
    
        // Update later
        setTimeout(() => {
          open?.({
            key, // Same key updates the notification
            type: "success",
            message: "Complete!",
          });
        }, 2000);
      };
    
      return (
        <div>
          <button onClick={showSuccess}>Success</button>
          <button onClick={showError}>Error</button>
          <button onClick={showCustom}>Custom</button>
        </div>
      );
    };
    

    Closing Notifications

    const MyComponent = () => {
      const { open, close } = useNotification();
    
      const showTemporary = () => {
        const key = "temp-notification";
        
        open?.({
          key,
          type: "success",
          message: "This will close in 3 seconds",
        });
    
        setTimeout(() => {
          close?.(key);
        }, 3000);
      };
    };
    

    Customizing Automatic Notifications

    Success Notifications

    import { useCreate } from "@refinedev/core";
    
    const { mutate } = useCreate({
      successNotification: (data, values, resource) => ({
        message: `Post "${data.data.title}" created!`,
        description: "You can now view it in the list.",
        type: "success",
      }),
    });
    

    Error Notifications

    import { useUpdate } from "@refinedev/core";
    
    const { mutate } = useUpdate({
      errorNotification: (error, values, resource) => ({
        message: "Failed to update post",
        description: error?.message || "Unknown error occurred",
        type: "error",
      }),
    });
    

    Disable Notifications

    import { useDelete } from "@refinedev/core";
    
    // Disable success notification
    const { mutate } = useDelete({
      successNotification: false,
    });
    
    // Disable error notification
    const { mutate } = useDelete({
      errorNotification: false,
    });
    
    // Disable all notifications
    const { mutate } = useDelete({
      successNotification: false,
      errorNotification: false,
    });
    

    Undoable Notifications

    For undoable mutations, show progress notifications:
    import { useUpdate } from "@refinedev/core";
    
    const PostEdit = () => {
      const { mutate } = useUpdate({
        mutationMode: "undoable",
        undoableTimeout: 5000, // 5 seconds to undo
      });
    
      const handleUpdate = (values: any) => {
        mutate({
          resource: "posts",
          id: "1",
          values,
        });
        // Shows progress notification:
        // "You have 5 seconds to undo" with countdown
        // Includes "Undo" button
      };
    };
    

    Custom Undoable Component

    const UndoableNotification = ({ message, cancelMutation }: {
      message: string;
      cancelMutation?: () => void;
    }) => (
      <div>
        <span>{message}</span>
        <button onClick={cancelMutation}>Undo</button>
      </div>
    );
    
    const notificationProvider: NotificationProvider = {
      open: ({ message, type, cancelMutation, undoableTimeout }) => {
        if (type === "progress") {
          toast(
            <UndoableNotification
              message={message}
              cancelMutation={cancelMutation}
            />,
            {
              progress: undoableTimeout ? undoableTimeout / 10 : undefined,
            }
          );
        } else {
          toast(message, { type });
        }
      },
      close: (key) => toast.dismiss(key),
    };
    

    Advanced Patterns

    Notification Queue

    const NotificationQueue = () => {
      const [queue, setQueue] = useState<Notification[]>([]);
      const { open } = useNotification();
    
      const addToQueue = (notification: Notification) => {
        setQueue((prev) => [...prev, notification]);
      };
    
      useEffect(() => {
        if (queue.length > 0) {
          const [first, ...rest] = queue;
          
          open?.(first);
          
          setTimeout(() => {
            setQueue(rest);
          }, 3000);
        }
      }, [queue]);
    
      return null;
    };
    

    Grouped Notifications

    const GroupedNotifications = () => {
      const { open } = useNotification();
      const [errors, setErrors] = useState<string[]>([]);
    
      const showGrouped = () => {
        if (errors.length === 1) {
          open?.({
            type: "error",
            message: errors[0],
          });
        } else if (errors.length > 1) {
          open?.({
            type: "error",
            message: `${errors.length} errors occurred`,
            description: (
              <ul>
                {errors.map((error, i) => (
                  <li key={i}>{error}</li>
                ))}
              </ul>
            ),
          });
        }
      };
    };
    

    Persistent Notifications

    const PersistentNotification = () => {
      const { open } = useNotification();
    
      const showPersistent = () => {
        open?.({
          key: "persistent",
          type: "success",
          message: "This stays until manually closed",
          // Configure your notification library to not auto-close
        });
      };
    };
    

    Action Notifications

    const ActionNotification = () => {
      const { open } = useNotification();
      const navigate = useNavigate();
    
      const showWithAction = () => {
        open?.({
          type: "success",
          message: "Post created successfully",
          description: (
            <button onClick={() => navigate("/posts/1")}>
              View Post
            </button>
          ),
        });
      };
    };
    

    Loading Notifications

    const AsyncOperation = () => {
      const { open, close } = useNotification();
    
      const performOperation = async () => {
        const key = "operation";
        
        open?.({
          key,
          type: "progress",
          message: "Processing...",
        });
    
        try {
          await someAsyncOperation();
          
          open?.({
            key,
            type: "success",
            message: "Operation complete!",
          });
        } catch (error) {
          open?.({
            key,
            type: "error",
            message: "Operation failed",
          });
        }
      };
    };
    

    Position and Styling

    Notification Position

    // Positions: topLeft, topRight, bottomLeft, bottomRight
    import { notification } from "antd";
    
    notification.config({
      placement: "topRight",
      duration: 3,
    });
    

    Custom Styling

    const notificationProvider: NotificationProvider = {
      open: ({ message, type }) => {
        toast(message, {
          type,
          style: {
            background: "#1a1a1a",
            color: "#ffffff",
            borderRadius: "8px",
          },
          progressStyle: {
            background: "#4caf50",
          },
        });
      },
      close: toast.dismiss,
    };
    

    Dark Mode

    import { useColorMode } from "your-ui-library";
    
    const notificationProvider: NotificationProvider = {
      open: ({ message, type }) => {
        const { colorMode } = useColorMode();
        
        toast(message, {
          type,
          theme: colorMode, // "light" or "dark"
        });
      },
      close: toast.dismiss,
    };
    

    Testing

    Mock Notification Provider

    import { render } from "@testing-library/react";
    import { NotificationProvider } from "@refinedev/core";
    
    const mockNotificationProvider: NotificationProvider = {
      open: jest.fn(),
      close: jest.fn(),
    };
    
    test("shows notification on success", async () => {
      render(
        <Refine notificationProvider={mockNotificationProvider}>
          <MyComponent />
        </Refine>
      );
    
      // Trigger action
      // ...
    
      expect(mockNotificationProvider.open).toHaveBeenCalledWith({
        type: "success",
        message: expect.any(String),
      });
    });
    

    E2E Tests

    import { test, expect } from "@playwright/test";
    
    test("shows success notification", async ({ page }) => {
      await page.goto("/posts/create");
      
      await page.fill('input[name="title"]', "Test Post");
      await page.click('button[type="submit"]');
      
      // Wait for notification
      await expect(page.locator('.notification')).toContainText(
        "Successfully created"
      );
    });
    

    Best Practices

    1. Keep messages concise - Users should understand at a glance
    2. Use appropriate types - success, error, warning, info
    3. Provide context - Include relevant details in description
    4. Set reasonable timeouts - 3-5 seconds for most notifications
    5. Don’t overuse - Too many notifications are annoying
    6. Make them dismissible - Let users close notifications
    7. Group related notifications - Combine multiple similar messages
    8. Use undoable for destructive actions - Give users a chance to revert

    Common Patterns

    Bulk Operation Notifications

    const BulkDelete = () => {
      const { open } = useNotification();
      const { mutate } = useDeleteMany();
    
      const handleBulkDelete = (ids: string[]) => {
        mutate(
          {
            resource: "posts",
            ids,
          },
          {
            onSuccess: () => {
              open?.({
                type: "success",
                message: `Successfully deleted ${ids.length} posts`,
              });
            },
          }
        );
      };
    };
    

    Form Validation Notifications

    const PostForm = () => {
      const { open } = useNotification();
    
      const handleSubmit = (values: any) => {
        // Client-side validation
        if (!values.title) {
          open?.({
            type: "error",
            message: "Validation Error",
            description: "Title is required",
          });
          return;
        }
    
        // Submit form
      };
    };
    

    Connection Status

    const ConnectionStatus = () => {
      const { open, close } = useNotification();
      const [isOnline, setIsOnline] = useState(navigator.onLine);
    
      useEffect(() => {
        const handleOnline = () => {
          setIsOnline(true);
          open?.({
            key: "connection",
            type: "success",
            message: "Back online",
          });
        };
    
        const handleOffline = () => {
          setIsOnline(false);
          open?.({
            key: "connection",
            type: "error",
            message: "No internet connection",
          });
        };
    
        window.addEventListener("online", handleOnline);
        window.addEventListener("offline", handleOffline);
    
        return () => {
          window.removeEventListener("online", handleOnline);
          window.removeEventListener("offline", handleOffline);
        };
      }, []);
    };
    

    Next Steps

    Build docs developers (and LLMs) love