Guide on how to create in-app notifications in Nextbase.
In-app notifications are a great way to communicate with your users. They can be used to inform users about new features, promotions, or any other important information.
Nextbase Ultimate comes with a built-in in-app notification system that allows you to create and send notifications to your users.
User notifications are managed in the user_notifications table which is created automatically by Nextbase Ultimate supabase migrations during installation. Here is the relevant part of the table schema:
CREATE TABLE user_notifications ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES user_profiles(id) ON DELETE CASCADE, is_read BOOLEAN NOT NULL DEFAULT FALSE, is_seen BOOLEAN NOT NULL DEFAULT FALSE, payload JSONB NOT NULL DEFAULT '{}'::JSONB, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP);ALTER TABLE "public"."user_notifications" ENABLE ROW LEVEL SECURITY;ALTER PUBLICATION supabase_realtimeADD TABLE user_notifications;CREATE policy Only_user_can_read_their_own_notification ON user_notifications AS permissive FORSELECT TO authenticated USING ((auth.uid () = user_id));CREATE policy Only_user_can_update_their_notification ON user_notifications AS permissive FORUPDATE TO authenticated USING (auth.uid() = user_id);CREATE policy Only_user_can_delete_their_notification ON user_notifications AS permissive FOR DELETE TO authenticated USING (auth.uid() = user_id);CREATE policy Any_user_can_create_notification ON user_notifications AS permissive FORINSERT TO authenticated WITH CHECK (TRUE);
As you can see, the table has a foreign key to the user_profiles table, which means that each notification is associated with a user. This is important because it allows you to send notifications to specific users.
Also, the table has a payload column which is a JSONB column that can be used to store any data you want to send to the user.
Additionally, the table has a is_read and is_seen columns that can be used to track whether the user has read or seen the notification.
Finally the RLS policies ensure that users can only read, update, and delete their own notifications.
It is important to note that the payload column is of type JSONB which means that you can store any data you want in it. However, this also means that you will not get any type safety when accessing the data.
For that purpose, we use zod schemas to define the payload type. It is present in src/utils/zod-schemas/notifications.ts
As you can see, we define a UserNotification type which is a union of all the possible notification types. This allows us to get type safety when accessing the notification payload.
When you want to create a new notification type you can simply add it to the union type and define the zod schema for it.
For eg. if you want to create a notification type for when a user's profile was seen by another user, you can do the following:
We utilize a parse notification function in src/utils/parseNotification.ts, where we parse and normalize the notification payload. This is useful when we want to display the notification in the UI.
src/utils/parseNotification.ts
import { UserNotification, userNotificationPayloadSchema,} from './zod-schemas/notifications';type NormalizedNotification = { title: string; description: string; image: string; type: UserNotification['type'] | 'unknown';} & ( | { actionType: 'link'; href: string; } | { actionType: 'button'; });export const parseNotification = ( notificationPayload: unknown,): NormalizedNotification => { try { const notification = userNotificationPayloadSchema.parse(notificationPayload); switch (notification.type) { case 'invitedToOrganization': return { title: 'Invitation to join organization', description: `You have been invited to join ${notification.organizationName}`, // 2 days ago href: `/invitations/${notification.invitationId}`, image: '/logos/logo-black.png', actionType: 'link', type: notification.type, }; case 'acceptedOrganizationInvitation': return { title: 'Accepted invitation to join organization', description: `${notification.userFullName} has accepted your invitation to join your organization`, href: `/organization/${notification.organizationId}/settings/members`, image: '/logos/logo-black.png', actionType: 'link', type: notification.type, }; case 'welcome': return { title: 'Welcome to the Nextbase', description: 'Welcome to the Nextbase Ultimate. We are glad to see you here!', actionType: 'button', image: '/logos/logo-black.png', type: notification.type, }; default: { return { title: 'Unknown notification type', description: 'Unknown notification type', href: '#', image: '/logos/logo-black.png', actionType: 'link', type: 'unknown', }; } } } catch (error) { return { title: 'Unknown notification type', description: 'Unknown notification type', image: '/logos/logo-black.png', actionType: 'button', type: 'unknown', }; }};
So to add our new notification type, we can simply add a new case to the switch statement: