Reward systems, check-ins, and rate-limited actions all share the same risk: without a hard limit, a user can claim the same reward multiple times in one day — especially if they click fast or the request fires twice under a race condition.
Momen supports two ways to enforce a daily claim limit: a composite unique constraint at the database level, and a state counter tracked per user. Both projects below are built entirely with Momen's in-product AI Copilot, and both come with an editor link you can clone directly.
Overview
In application development, limiting the number of times a user can claim rewards or perform specific actions per day (e.g., "claim points 3 times a day") is a common requirement. Momen offers two distinct implementation paths.
Method Comparison
| Dimension | Method 1: Unique Constraint | Method 2: State Counter |
|---|---|---|
| Design Approach | Append‑only: each claim creates a new row | State‑based: each user has a fixed record row, updated on each claim |
| Concurrency Defense | Database unique index | Update node filter (daily_claim_count < 3) |
| Data Volume | Grows linearly with claims over time | Grows with the number of active users |
| Audit Traceability | Built‑in (each claim is a row) | Requires additional configuration (database trigger) |
| Best For | High‑value rewards, strict audit requirements | Lightweight daily tasks, check‑in counters |
Daily Claim Limit (Unique Constraint)
Demo Project
Clone the Unique Constraint project
Introduction
- Goal: Create a daily reward system where users can claim a reward up to 3 times per day, with database-level protection against concurrent over-claiming.
- Use Cases: Daily check-ins, limited coupon distributions, or daily point systems.
- Core Logic: Use a Composite Unique Constraint in the database (Account + Date + Sequence) combined with an Actionflow that calculates the next sequence number and handles insertion conflicts.
Steps
This tutorial uses pre-styled layout blocks from the "Common UI Presets" template page. These presets include basic styling and typography only; they contain no conditional logic, database bindings, or Actionflows. You can copy them into your own app to skip manual styling and focus on the core logic.
Data Storage
To implement a daily claim limit system, we need to establish a dedicated table in the database to store claim records.
Data Model
Table: claim_record
Logs every successful reward claim.
| Field Name | Type | Note |
|---|---|---|
| id | Bigint | Primary Key |
| claim_date | Date | The date the reward was claimed |
| claim_sequence | Bigint | The index of the claim for that day (1, 2, or 3) |
| account_id | Bigint | Foreign Key linked to the account table |
Field Name
Type
Note
id
Bigint
Primary Key
claim_date
Date
The date the reward was claimed
claim_sequence
Bigint
The index of the claim for that day (1, 2, or 3)
account_id
Bigint
Foreign Key linked to the account table
Database Constraints
To ensure data integrity at the hardware level, we must prevent any duplicate entries for the same user on the same day with the same sequence number.
- Select Table: Select the claim_record table.
- New constraint: Click on the table settings and select Edit constraint.
- Composite unique columns: Add a new unique constraint named unique_claim_record_account_date_sequence.
- Fields: Select claim_date, claim_sequence, and account_id. This ensures that the combination of these three fields must be unique across the entire database.
Logic & State ConfigurationActionflow: Claim Reward
This Actionflow validates and executes the reward claim.
- Actionflow variable: Create a variable named status with the type Boolean to return the result to the frontend.
- Get ID: Add a Get ID node. Its output field is current_account_id.
- Query data: Add a Query data node to fetch the user's claims for the current day.
Table: claim_record.
- Filter:
claim_date Equal to Current date.
account_id Equal to current_account_id (from the Get ID node).
Limit: Set to 3.
- Condition: Add a Condition node to check the current claim count.
Case 1 (Less than 3 times): Set the condition to Actionflow data/Fetch today's claim records/Count Less than 3.
- Insert data: In the "Less than 3 times" branch, add an Insert data node.
Table: claim_record.
- Parameters:
claim_date: Set to Current date.
- account_id: Set to current_account_id.
claim_sequence: Use a formula to calculate the next index: Actionflow data/Fetch today's claim records/Count + 1.
Conflict resolution: Select the unique_claim_record_account_date_sequence constraint and set the resolution to Do nothing. This silently ignores the request if a race condition occurs.
- Set variable (Less than 3 times): In the "Less than 3 times" branch, add a Set variable node after the insertion.
Condition: Set the condition to Actionflow data/Insert data/id Is not null.
Value: Set status to True.
- Set variable (Reached 3 times): In the "3 times reached" branch, add a Set variable node.
Value: Set status to False.
- Actionflow output: Configure the output to return the status variable.
By setting the Conflict resolution to "Do nothing," the Actionflow will not crash if a user clicks the button multiple times simultaneously. The database will simply reject the second request, and the status will return False.
UI Construction & Interaction
The frontend uses a Conditional View to dynamically switch between login prompts, active claim buttons, and disabled states based on the user's real-time data.
- Page Setup
- Create Page: In the Pages tab, click + and add a new page named Page Daily Reward Claim.
- Add Conditional View: Drag a Conditional View component onto the canvas. This will act as the container for the different reward states.
- Configure Cases: Rename the default cases in the Component Tree:
Case 1: Case Less than 3 times
- Case 2: Case Reached 3 times
- Case 3: Initializing
- Active Claim State (Less than 3 times)
- Add Button: Inside the Case Less than 3 times case, add a Button component.
- Data Binding (Button Text): Click the Databinding icon next to the Button text field.
Combine static text with dynamic data.
- Expression: Claim daily reward ( + Logged in user/claim_record/Count + /3)
- Filter: In the databinding panel, add a filter to the claim_record relation with claim_date equals Current date, so the count only includes today's records.
- Interaction (OnClick): Go to the Action tab of the button.
Trigger: OnClick -> Actionflow.
- Select Actionflow: Choose Claim Reward.
- Feedback Logic (On Success): Click + under On success and select Condition.
Case Claim Success: Set the condition to Action result/Actionflow/status Is true.
Action: Show toast with the message "Claimed successfully".
- Case Claim Failed: Set the condition to Action result/Actionflow/status Is false.
Action: Show toast with the message "Claim failed".
- Data Refresh: Add a Refresh logged-in user data action at the end of the On success sequence. This ensures the UI counter and conditional view update immediately after a successful claim.
- Limit Reached & Initializing States
- Disabled Button: In the Case Reached 3 times case, add a Button component.
Button text: Set to Claim daily reward (3/3).
Interaction: Remove all actions to ensure it is non-interactive.
Login Prompt: In the Initializing case, add a Text component.
Content: Set to "Please log in first".
- Visibility Logic Configuration
Select the Conditional View and click Config in the right panel to define when each case should be displayed.
- Case Less than 3 times:
Condition: And
Global/is logged in Is true
- Logged in user/claim_record/Count (filtered by today's date – apply the same filter as in the button binding) Less than 3.
- Case Reached 3 times:
Condition: Global/is logged in Is true. (Since this is the second branch, it will only execute if the "Less than 3" condition fails.)
- Initializing:
Displays when the user is not authenticated.
VerificationStep 1: Authentication Test
- Click Preview and use the Login simulation at the bottom of the screen.
- Select Restore user to logged out state.
- Expected Result: The page displays "Please log in first".
Step 2: Claiming Rewards
- Use Login simulation -> Create new to log in as a test user.
- Click the "Claim daily reward (0/3)" button.
- Expected Result: A "Claimed successfully" toast appears, and the button text updates to "(1/3)".
Step 3: Reaching the Daily Limit
- Click the button two more times.
- Expected Result: After the third claim, the button style changes to the Disabled state and displays "(3/3)".
Step 4: Database Integrity Check
- Go to the Data Source tab and open the claim_record table.
- Expected Result: You should see exactly 3 records for the test user with sequence numbers 1, 2, and 3 under today's date.
If you test this in a high-concurrency environment, you might see the Actionflow return "Claim failed." This is precisely the unique constraint in action, preventing duplicate over-claiming records from being created.
That covers the unique-constraint approach. Next, here's the same feature built with a state counter instead — one record per user, updated in place, with a database trigger handling the audit trail.
Daily Claim Limit (State Counter)
Demo Project
Clone the State Counter project
Introduction
- Goal: Create a secure daily reward system that limits users to 3 claims per day.
- Use Cases: Daily login rewards, free API rate limiting, daily lucky draws, or high-frequency business action auditing.
- Core Logic: Use a claim_status table to track the "Single State" of a user's progress. Backend Actionflow logic handles date verification and counter increments, while an On database changed trigger automates audit logging into a claim_log table.
Steps
This tutorial uses pre-styled layout blocks from the Common UI Presets template page to streamline the visual setup. These preset elements contain only basic styling and typography; they do not include any conditional logic, database bindings, or Actionflows. When building your own app, you can directly copy elements from this template page to skip manual styling and focus entirely on core frontend logic.
Data Storage
To implement a daily claim limit system, we need to establish a dedicated table in the database to map user data and its processing status.
Data Model
Configure the relational database to store user status and transactional history. Every table automatically includes system fields such as id, created_at, and updated_at; only custom fields are listed in detail below.
- Table: claim_status
Used for state control; each account holds at most one corresponding record. The account_id field has a built-in unique constraint due to the 1:1 relationship with the account table.
Field Name
Type
Note
id
Bigint
Primary Key (system default)
last_claim_date
Date
Used to verify if the request falls on a "new day"
daily_claim_count
Bigint
Increments sequentially (1‑3)
account_id
Bigint
Foreign key to account (1:1 relationship, automatically unique)
- Table: claim_log An append‑only audit log table.
Field Name
Type
Note
id
Bigint
Primary Key (system default)
claim_sequence
Bigint
Logs the index of the claim (1, 2, or 3)
account_id
Bigint
Foreign key to account (1:N relationship)
Logic & State Configuration"Daily Claim" Actionflow Construction
This Actionflow handles the core validation rules: checking if a record exists, resetting the counter on a new day, and incrementing the count if under the limit.
- Actionflow variable: Add a global variable inside the Actionflow named status with the type Boolean to track the overall success of the request execution.
- Get ID: Add a custom code node named "Get ID" to retrieve the logged-in user's account ID (current_account_id).
- Query data: Add a Query Record node named "Get Claim Status".
Table: claim_status.
- Filter: account_id Equal to Actionflow data/Get ID/current_account_id.
Limit: 1.
Condition - Root Branching: Add a Branch Separation node named "Condition" to verify if the status record exists in the database.
Case Existing Status Record: Actionflow data/Get Claim Status/id Is not null. (Proceeds to Step 5)
Case No Records (Else): If the user has never claimed before (the record is null). (Proceeds to Step 9)
Condition - Nested Date Check: Inside the existing record branch, add a nested Branch Separation node named "Condition".
Case Already Claimed Today: Actionflow data/Get Claim Status/last_claim_date Equal to getCurrentDate. (Proceeds to Step 6)
Case Not Claimed Today (Else): If the last claim date belongs to a previous day. (Proceeds to Step 8)
Update data (In Case Already Claimed Today): Add an Update Record node named "Update Daily Claim Count".
Table: claim_status.
- Parameters: daily_claim_count -> Arithmetic Operator: increment by 1.
Filter: account_id Equal to Actionflow data/Get ID/current_account_id AND daily_claim_count Less than 3.
Set variable: Add a Set Variable node to determine the value of the status variable based on the execution result of the counter increment.
Case Updated Successfully: Actionflow data/Update Daily Claim Count/id Is not null -> Set to True.
Case Update Failed (Else): Set to False (this happens if the daily_claim_count is already 3 or more, violating the filter constraint).
Update data (In Case Not Claimed Today): Since it is a brand new day, add an Update Record node named "Update Claim Status" to reset the counter tracking.
Table: claim_status.
- Parameters: last_claim_date -> getCurrentDate, daily_claim_count -> Set directly to 1.
- Filter: account_id Equal to Actionflow data/Get ID/current_account_id AND last_claim_date Not equal to getCurrentDate.
Set variable: Update the Actionflow variable status. If Update Claim Status/id Is not null, set to True, else False.
Insert data (In Case No Records): For first-time users, add an Insert Record node named "Add Claim Status Record" to create the initial tracking state.
Table: claim_status.
- Parameters: last_claim_date -> getCurrentDate, daily_claim_count -> 1, account_id -> Actionflow data/Get ID/current_account_id.
- On Conflict: Do nothing. (The account_id field has a unique constraint due to its 1:1 relationship with the account table, ensuring this clause works as intended.)
Set variable: Update the Actionflow variable status. If Add Claim Status Record/id Is not null, set to True, else False.
Actionflow output: Merge all conditional branches into the Flow End node and configure the output data binding to return Actionflow data/Variable/status.
"Claim Log" Actionflow & Trigger
Automate the audit trail whenever the claim_status table is modified.
- Actionflow input: Create a new Actionflow named "Claim Log". Add inputs: account_id (Bigint) and sequence (Bigint).
- Configure the Database Trigger: Enable the Database Trigger for this Actionflow to capture automated updates.
Trigger type: DB_TRIGGER.
- Select DB Operation Type: INSERT_OR_UPDATE.
- Select Table: claim_status.
- Actionflow inputs mapping:
account_id -> Inserted or updated data/account_id.
sequence -> Inserted or updated data/daily_claim_count.
Insert data: Add an Insert Record node named "Insert data" to persist the changes.
Table: claim_log.
- Parameters: claim_sequence -> Actionflow data/Input/sequence, account_id -> Actionflow data/Input/account_id.
By using an On database changed trigger set to INSERT_OR_UPDATE, you ensure that every change to the claim_status table is logged.
UI Construction & Interaction
Configure the frontend to dynamically display the claim status and handle user interactions based on the backend logic.
Page: Daily Reward Claim
- Conditional View Setup: In the Component Tree, select the Conditional View component. Rename the default cases to reflect the business logic:
Case Under 3 Times: For users who can still claim rewards.
- Case 3 Times Reached: For users who have hit their daily limit.
Initializing: The default state for logged-out users.
Configure Active Button: Within Case Under 3 Times, select the Button / Primary component.
Button text: Click the Data Binding icon and select Condition.
Case No Records: If Logged in user -> claim_status -> daily_claim_count Is null, set the display data to 0.
- Case Record Exists: Else, bind the data to Logged in user -> claim_status -> daily_claim_count.
Final String: Set the static text to Claim Daily Reward ({Condition}/3).
Configure Button Interaction: Select the active button, go to the Interaction tab, and configure its behavior.
Action: Add an OnClick event and select the Daily Claim Actionflow. Enable the Loading animation toggle.
- On success:
Show toast: Add a Show toast node. Click the Data Binding icon for the Message and select Condition.
Case Claim Successful: If Action result -> Daily Claim -> status Is true, set the message to Claim successful.
Case Claim Failed: Else, set the message to Claim failed.
Refresh login user data: Add a Refresh login user data node to ensure the frontend counter updates immediately after the database change.
- Configure Disabled Button: Within Case 3 Times Reached, select the Button / Disabled component to configure the UI when the daily limit is hit.
Button text: Click the Data Binding icon and select Condition (following the identical binding logic as the active button), or directly enter the static text: Claim Daily Reward (3/3).
- Configure Initializing View: Within Initializing, select the Text component to prompt unauthenticated users.
Text text: Directly enter the static text string: Please log in first.
- Configure Conditional View Logic: Select the master Conditional View and click Config in the right panel to define when each case is displayed.
Case Under 3 Times:
Add a condition: Global -> Is logged in Is true.
Add an And condition: Logged in user -> claim_status -> daily_claim_count Less than 3.
Case 3 Times Reached:
Add a condition: Global -> Is logged in Is true.
Add an And condition: Logged in user -> claim_status -> daily_claim_count Greater than or equal 3.
Initializing:
Add a condition: Global -> Is logged in Is false.
VerificationStep 1: Logged-out State
- Click the Preview icon in the top right.
- Expected Result: The page displays the text "Please log in first."
Step 2: First Claim
- Use the Login simulation tool in the bottom bar. Click Create new and select the Logged-in user role.
- Click the button labeled Claim Daily Reward (0/3).
- Expected Result: A "Claim successful" toast appears, and the button label updates to Claim Daily Reward (1/3).
Step 3: Reaching the Limit
- Click the button two more times.
- Expected Result: After the third claim, the button label shows Claim Daily Reward (3/3) and becomes disabled (switching to the Case 3 Times Reached UI).
Step 4: Database Audit
- Navigate to the Data Source tab.
- Check the claim_status table: The daily_claim_count should be 3.
- Check the claim_log table: There should be three distinct records with claim_sequence values of 1, 2, and 3, all linked to the same account_id.
If the counter does not update on the frontend, verify that the Refresh login user data node is correctly placed in the On success branch of the button's OnClick interaction.
| Field Name | Type | Note |
|---|---|---|
| id | Bigint | Primary Key (system default) |
| last_claim_date | Date | Used to verify if the request falls on a "new day" |
| daily_claim_count | Bigint | Increments sequentially (1‑3) |
| account_id | Bigint | Foreign key to account (1:1 relationship, automatically unique) |
Try It Yourself
Both projects are ready to clone and explore in the Momen editor:
Once you have a project open, try changing the daily limit from 3 to your own number, or extend the state counter method with a reset schedule for weekly instead of daily limits. For more on the building blocks used here, see the database configuration guide and the Actionflow trigger reference.
Conclusion
Both methods stop the same failure mode — a user claiming more than their daily limit — but they enforce it at different layers. Unique Constraint pushes the guarantee down to the database, which is the safer default when real value is on the line. State Counter keeps things lighter for simple, low-stakes limits. Momen's AI Copilot can scaffold either pattern — the tables, the Actionflow logic, and the UI states — directly inside the editor. Clone one of the projects above and adapt it to your own limit.


















Top comments (0)