Cross-app push notifications with LOADRECORDS
This example shows how to deliver in-app "push notifications" to Fulcrum users by combining a dedicated notifications app with LOADRECORDS and STORAGE. When a record is opened, the data event checks for new messages from the notifications app and displays them via CONFIRM, with support for role targeting and expiration dates.
Cross-app push notifications with LOADRECORDS
Fulcrum doesn't have a native push notification system, but you can simulate one using a dedicated notifications app and LOADRECORDS. When a field worker opens a record in any app that includes this Data Event, it checks the notifications app for new messages. If a new, un-seen message exists that targets the user's role and hasn't expired, it is displayed as a CONFIRM dialog.
STORAGE is used to track the timestamp of the last message the user dismissed, so they only see each notification once.
Setup
1. Create the notifications app
Build a Fulcrum app with the following fields:
| Field label | Data name / key | Type | Purpose |
|---|---|---|---|
| Title | (note its field key) | Text | Notification headline |
| Message | (note its field key) | Text | Notification body text |
| Allowed Roles | (note its field key) | Classification / Choice | Which roles should see the notification (leave blank for everyone) |
| Expiration Date | (note its field key) | Date | Optional date after which the notification stops showing |
Note the Form ID for the notifications app and the field key for each of the four fields above.
2. Add the Data Event to your collection apps
Paste the code below into any app where you want notifications to appear. Update the configuration section with your form ID and field keys.
3. Create a notification record
Add a record to the notifications app with a title, message, and optionally an expiration date. The next time a user opens a record in the configured app, the notification will appear.
Data Event Code
// ─── Configuration ───────────────────────────────────────────────────────────
// Form ID of the notifications app (find in the URL when viewing the form)
const NOTIFICATIONS_FORM_ID = 'YOUR-NOTIFICATIONS-FORM-ID-HERE';
// Field keys from the notifications app (found in the form builder or via API)
const FIELD_KEYS = {
title: 'YOUR-TITLE-FIELD-KEY', // Text field: notification headline
message: 'YOUR-MESSAGE-FIELD-KEY', // Text field: notification body
allowedRoles: 'YOUR-ROLES-FIELD-KEY', // Choice field: roles to target (blank = all)
expirationDate: 'YOUR-EXPIRATION-DATE-FIELD-KEY' // Date field: when to stop showing (blank = never)
};
// Storage key used to track the last notification the user dismissed
const STORAGE_KEY = 'lastReceivedMessage';
// ─── Main Logic ──────────────────────────────────────────────────────────────
let storage = STORAGE();
// Initialize the "last seen" timestamp in storage if this is the first run
if (!storage.getItem(STORAGE_KEY)) {
storage.setItem(STORAGE_KEY, 0);
}
// Parse a YYYY-MM-DD date field as a local calendar date and return the end of
// that day, so a notification stays visible through its expiration date
// regardless of the device's time zone.
function endOfLocalDay(dateStr) {
const [year, month, day] = dateStr.split('-').map(Number);
return new Date(year, month - 1, day, 23, 59, 59, 999).getTime();
}
ON('load-record', () => {
// Fetch only the most recent notifications, newest first, so form open time
// doesn't grow as the notifications app accumulates records.
// (limit/order require Fulcrum mobile app 2502.2.0+)
LOADRECORDS({
form_id: NOTIFICATIONS_FORM_ID,
order: [['updated_at', 'desc']],
limit: 25
}, (err, result) => {
if (err) {
console.log(INSPECT(err));
return;
}
const notificationRecords = result.records;
if (!notificationRecords || notificationRecords.length === 0) return;
const lastSeen = Number(storage.getItem(STORAGE_KEY)) || 0;
for (const rec of notificationRecords) {
// Convert the notification's last-updated timestamp to milliseconds
const messageTimestamp = new Date(rec.updated_at).getTime();
// Records are sorted newest first, so once we reach one the user has
// already seen, every remaining record is older and can be skipped.
if (messageTimestamp <= lastSeen) break;
// Get the roles this notification is targeted to (null = everyone)
const allowedRoles = rec.form_values?.[FIELD_KEYS.allowedRoles]
? CHOICEVALUES(rec.form_values[FIELD_KEYS.allowedRoles])
: null;
// Get the expiration date, if set (as the end of that calendar day)
const expirationValue = rec.form_values?.[FIELD_KEYS.expirationDate];
const expiresAt = expirationValue ? endOfLocalDay(expirationValue) : null;
// Check if this notification is targeted to the current user's role
const isTargeted = allowedRoles === null || allowedRoles.includes(ROLE());
if (!isTargeted) continue;
// Check if the notification has expired
const isExpired = expiresAt !== null && Date.now() > expiresAt;
if (isExpired) continue;
// Display the notification and mark it as seen when the user dismisses it
CONFIRM(
rec.form_values[FIELD_KEYS.title], // Dialog title
rec.form_values[FIELD_KEYS.message], // Dialog body
function () {
// Keep the newest dismissed timestamp so this and any older
// notifications won't show again
const current = Number(storage.getItem(STORAGE_KEY)) || 0;
storage.setItem(STORAGE_KEY, Math.max(current, messageTimestamp));
}
);
}
});
});How it works
- When a record is opened,
LOADRECORDSfetches the 25 most recently updated records from the notifications app, newest first. - For each notification record, the event checks three conditions:
- Is it new? The notification's
updated_attimestamp is compared to the last timestamp stored inSTORAGE. If it's older than or equal to what the user last dismissed, processing stops, since every remaining record is older. - Is it targeted to this user? If the Allowed Roles field has a value, the current user's
ROLE()must appear in the list. A blank roles field means everyone sees it. - Has it expired? If an Expiration Date is set and that calendar day (in the device's local time zone) has passed, the notification is skipped.
- Is it new? The notification's
- Notifications that pass all three checks are displayed using
CONFIRM. When the user dismisses the dialog, the timestamp is written toSTORAGEso the notification won't appear again.
Notes
- Multiple notifications can be active at the same time. Each is evaluated independently.
- Because
STORAGEis per-device, a user who switches devices will see the notification again on the new device. - To "resend" a notification to users who have already dismissed it, simply update the notification record in the notifications app — its
updated_attimestamp will advance past what's stored inSTORAGE. - This approach works offline: if the device has previously synced the notifications app,
LOADRECORDScan return cached records even without a network connection.
Updated about 11 hours ago