<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Momen</title>
    <description>The latest articles on DEV Community by Momen (momen_hq).</description>
    <link>https://dev.to/momen_hq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F9502%2Fba15f93c-571b-4460-b459-f94793229c6c.png</url>
      <title>DEV Community: Momen</title>
      <link>https://dev.to/momen_hq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/momen_hq"/>
    <language>en</language>
    <item>
      <title>Damian Malliaros Built a Workout Tracker by Giving Claude Code a Real Backend</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:11:17 +0000</pubDate>
      <link>https://dev.to/momen_hq/damian-malliaros-built-a-workout-tracker-by-giving-claude-code-a-real-backend-3o2b</link>
      <guid>https://dev.to/momen_hq/damian-malliaros-built-a-workout-tracker-by-giving-claude-code-a-real-backend-3o2b</guid>
      <description>&lt;p&gt;Damian Malliaros, a YouTube creator who tests AI tools, built a full-stack exercise tracking app by pairing Claude Code with Momen: Momen handles the backend — database, logins, permissions, server-side logic — and Claude Code builds the React frontend on top of it.&lt;/p&gt;

&lt;p&gt;He didn't configure the backend by hand. Momen's &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;no-code plugin&lt;/a&gt; connects Claude Code to a Momen account, so he described the app in plain language and Claude Code created the tables, permissions, and Actionflows directly in the project.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-uLuyfkdI9w"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What the app does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log a workout — start a session, add exercises, record weight, reps, and RPE (how many reps you had left in the tank) for each set&lt;/li&gt;
&lt;li&gt;Custom exercises — pick from a seeded library (bench press, squat, deadlift) or add your own&lt;/li&gt;
&lt;li&gt;Automatic personal-record detection — the backend compares each new set against your history and flags it as a PR if you've beaten your previous best on that lift&lt;/li&gt;
&lt;li&gt;Weekly training volume — recalculated nightly on the server, whether or not the app is open&lt;/li&gt;
&lt;li&gt;Calorie tracking — log meals with calories, protein, carbs, and fat against a daily goal&lt;/li&gt;
&lt;li&gt;Per-account data scoping — a logged-in user's workouts, sets, and meals are visible only to them, while the exercise library is shared&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The data model: five tables
&lt;/h2&gt;

&lt;p&gt;The whole app runs on five tables — Momen's built-in account table plus four Claude Code added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account — Momen's built-in user table, extended here with daily_calorie_goal, unit_preference, and two fields the backend writes to on its own: weekly_volume and weekly_volume_updated_at&lt;/li&gt;
&lt;li&gt;exercise — name, category, tracking_type, description, and an owner_id, which is what makes custom exercises possible: seeded exercises have no owner, user-created ones point back to their account&lt;/li&gt;
&lt;li&gt;workout_session — one row per workout: performed_at, title, duration_min, notes&lt;/li&gt;
&lt;li&gt;exercise_set — the core log table: weight, reps, set_order, rpe, foreign keys to the session, the exercise, and the account, and an is_pr boolean the backend sets&lt;/li&gt;
&lt;li&gt;calorie_entry — logged_on, meal, food_name, calories, protein_g, carbs_g, fat_g&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Damian asked Claude Code to propose this structure before building anything, reviewed it, then approved it. That order matters — get the &lt;a href="https://momen.app/blogs/beginner-guide-data-modeling-momen-no-code-web-app/" rel="noopener noreferrer"&gt;data model&lt;/a&gt; right first and the frontend has something solid to bind to. It's also the difference between an app that survives its second feature and one that doesn't, which is the argument for &lt;a href="https://momen.app/blogs/why-backend-structure-always-matters/" rel="noopener noreferrer"&gt;why backend structure matters even if you don't write code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logins and permissions, configured not coded
&lt;/h2&gt;

&lt;p&gt;Momen's built-in &lt;a href="https://momen.app/blogs/build-user-authentication-system-with-no-code/" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; covers signup and login, so there's no separate auth provider to wire up. Username-and-password is what the app uses; email and phone are available on the same config.&lt;/p&gt;

&lt;p&gt;Permissions are set per role, per table, per column. The project defines a Logged-in User role that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read, edit, and delete only its own workout_session, exercise_set, and calorie_entry rows — the read and write rules on all three filter on the logged-in user&lt;/li&gt;
&lt;li&gt;read the whole exercise library, but edit or delete only rows whose owner_id matches, so seeded lifts stay read-only while custom ones don't&lt;/li&gt;
&lt;li&gt;update its own account fields — calorie goal, unit preference, profile image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part that would normally mean writing row-level security policies by hand. Here it's a matrix of toggles per table, and Claude Code can flip them through the plugin — Damian had it set the rules up and then reviewed them in the editor. Worth noting that only the logged-in role was tightened in this build; the anonymous role is still on Momen's permissive defaults, which is the next thing to lock down before anything like this goes live. &lt;a href="https://docs.momen.app/docs/publish_operate/permissions" rel="noopener noreferrer"&gt;Momen's permissions documentation&lt;/a&gt; covers how the role, table, and column layers fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Actionflows doing the work on the server
&lt;/h2&gt;

&lt;p&gt;The reason to put this logic on a backend at all: if PR detection runs in the browser, it only works while the tab is open, and the numbers are editable from devtools. Both of these flows run server-side, in Momen.&lt;/p&gt;

&lt;p&gt;Detect PR on new set — a database trigger on exercise_set insert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fires automatically on every new set, no frontend call needed&lt;/li&gt;
&lt;li&gt;queries the account's previous best weight for that same exercise&lt;/li&gt;
&lt;li&gt;if the new set beats it, updates that row's is_pr to true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weekly volume rollup — a scheduled job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs nightly at 3 AM regardless of user activity&lt;/li&gt;
&lt;li&gt;sums weight × reps across every set logged in the trailing 7 days, grouped by account&lt;/li&gt;
&lt;li&gt;writes each total back to that account's weekly_volume field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are built in Momen's &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows" rel="noopener noreferrer"&gt;Actionflow&lt;/a&gt; editor: an input node, a code step that runs GraphQL queries and mutations against the project's own data, and a return value. What differs is only the &lt;a href="https://docs.momen.app/docs/actions/reference/trigger_list" rel="noopener noreferrer"&gt;trigger&lt;/a&gt; — a table insert versus a cron expression. That's the distinction Damian drew in the video: a spreadsheet-style database stores what you hand it, while a backend keeps working on it when nobody's watching. The same scheduled-job pattern shows up in things like &lt;a href="https://momen.app/blogs/build-an-automatic-membership/" rel="noopener noreferrer"&gt;an automatic membership downgrade&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No AI agents, no third-party API integrations, and no payment provider in this project. The logic is deterministic comparison and arithmetic, which is exactly what it should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frontend, and where it lives
&lt;/h2&gt;

&lt;p&gt;Claude Code generated the React interface from a short brief — dark theme, smooth animations — as four screens: a dashboard, a workout logger, a nutrition tab, and a progress view. Each one talks to the GraphQL API Momen generates automatically from the data model, so there's no backend code in the frontend repo.&lt;/p&gt;

&lt;p&gt;Because the UI was built outside Momen, it doesn't use Momen's &lt;a href="https://docs.momen.app/docs/publish_operate/app_deployment" rel="noopener noreferrer"&gt;one-click deploy&lt;/a&gt; — that path is for frontends built in the Momen editor. Claude Code pushed this one to Vercel instead, with the Momen backend staying where it is.&lt;/p&gt;

&lt;p&gt;Damian walks through all of it — the proposed data model, the permission setup, both Actionflows, the generated frontend, and the deploy — in &lt;a href="https://www.youtube.com/watch?v=-uLuyfkdI9w" rel="noopener noreferrer"&gt;his video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To build something along these lines: install the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen plugin&lt;/a&gt; in Claude Code, describe the data and server-side logic you need and let it build the backend first, then have it generate the frontend against the API you just created.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>exercise</category>
      <category>tracking</category>
    </item>
    <item>
      <title>James Nocode's 2026 Stack: Codex Orchestrates, Momen Runs the Backend, Stitch Designs the UI</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:11:05 +0000</pubDate>
      <link>https://dev.to/momen_hq/james-nocodes-2026-stack-codex-orchestrates-momen-runs-the-backend-stitch-designs-the-ui-812</link>
      <guid>https://dev.to/momen_hq/james-nocodes-2026-stack-codex-orchestrates-momen-runs-the-backend-stitch-designs-the-ui-812</guid>
      <description>&lt;p&gt;James Nocode, a YouTube creator who builds and reviews AI development tools, published a full end-to-end build to demonstrate the stack he's settled on for 2026. The subject of the video is the stack itself, not the app: three tools with clear boundaries, one agent driving all of them.&lt;/p&gt;

&lt;p&gt;The app he uses to demonstrate it is Creator Circle — a gated community where members request access, take courses and lessons, track progress, and post in discussions, with an admin view that approves or revokes membership. It's a vehicle for showing how the pieces connect, and it's deliberately a shape that can't be faked with a frontend alone.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iMZUp6E3lJ0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tools, three jobs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex (GPT-5.6) — the only agent in the build. It plans, writes the React and TypeScript frontend, and provisions the backend. Everything else is a tool it operates rather than a service the builder wires up by hand.&lt;/li&gt;
&lt;li&gt;Momen — a managed visual backend, consumed over its auto-generated GraphQL API. Codex builds inside it through the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;; the result stays open in the Momen editor afterward.&lt;/li&gt;
&lt;li&gt;Google Stitch — interface design from a prompt. James is direct about why it's in the stack: Codex is improving at UI but still isn't as good as a dedicated design tool, so Stitch produces the screens and Codex imports them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;His framing for the division of labor: Codex builds every layer, Momen stays visual, Stitch shapes the UI. Backend first, because the backend is the source of truth the frontend has to match — a point Momen has made in &lt;a href="https://momen.app/blogs/why-backend-structure-always-matters/" rel="noopener noreferrer"&gt;Why Backend Structure Always Matters&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four documents before a single line of app code
&lt;/h2&gt;

&lt;p&gt;The most transferable part of the video isn't the tooling, it's how much James writes down before building. Four planning passes, each in plan mode so Codex can ask clarifying questions, and each explicitly instructed not to install or build anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;V1 scope — member and admin roles, gated community, courses and lessons, progress tracking, discussions, one admin access view, plus an explicit list of what's being left out. His reasoning: Creator Circle could balloon into a full course-platform competitor, so the boundary gets fixed first.&lt;/li&gt;
&lt;li&gt;Product spec — screen list and user flows, saved into the repo as a file so a later agent or developer can read what is and isn't in scope.&lt;/li&gt;
&lt;li&gt;Backend plan — the entities and relationships in plain English, reviewed and saved before anything is provisioned.&lt;/li&gt;
&lt;li&gt;Repo instructions — an AGENTS.md and spec.md covering how to run the app, how to re-read the Momen backend after a change, and the hard rule that no admin token ever reaches the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;He calls that last step harness engineering: files that exist for the agent's benefit, so it stays consistent across a long build. The pattern is the counter-argument to one-shot prompting, which Momen has written about in &lt;a href="https://momen.app/blogs/why-your-ai-coding-app-breaks-at-80-start-architecting/" rel="noopener noreferrer"&gt;Stop Prompting, Start Architecting&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paying for reasoning only where it changes the outcome
&lt;/h2&gt;

&lt;p&gt;Codex exposes three model tiers, and James switches between them deliberately rather than staying on the strongest one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top tier, high effort — for the four planning passes and for provisioning the Momen backend, where a wrong architectural call is expensive to undo later&lt;/li&gt;
&lt;li&gt;Balanced tier, medium effort — for most implementation work, including the Stitch import and fleshing out screens&lt;/li&gt;
&lt;li&gt;Cheapest tier — for the initial React scaffold and other boilerplate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;His rule of thumb is that anything architectural gets the strongest model, and everything downstream of a locked decision does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the plugin gives the agent that it would otherwise invent
&lt;/h2&gt;

&lt;p&gt;This is the part of the stack that does the most work, and James spends real time on why. Without a plugin, a coding agent guesses at the shape of your API from context. The &lt;a href="https://momen.app/blogs/momen-plugin-is-here-let-ai-build-your-backend-and-vibe-code-your-frontend/" rel="noopener noreferrer"&gt;Momen plugin&lt;/a&gt; replaces the guess with three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Momen platform skill — the interface through which Codex builds and operates a Momen project: creating tables, configuring relations, setting up auth, adding Actionflows. Not just reading an existing schema.&lt;/li&gt;
&lt;li&gt;A Momen MCP command-line tool — handles execution: table creation, schema code generation, GraphQL operations, Actionflow calls. Codex drives this rather than calling Momen's APIs directly.&lt;/li&gt;
&lt;li&gt;Session-start schema loading — the agent reads the current backend state before the first prompt, so frontend code references real fields from the beginning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup is a marketplace install plus a browser login that returns a token, then pasting the Momen project URL into the prompt so the plugin knows which project to operate on. The &lt;a href="https://momen.app/blogs/momen-codex-complete-setup-guide/" rel="noopener noreferrer"&gt;step-by-step setup for Codex&lt;/a&gt; covers the same flow in writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend Codex provisioned, watched live
&lt;/h2&gt;

&lt;p&gt;James keeps the Momen editor open while Codex works, and the build appears in it as it goes. What the video shows landing in the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Momen's built-in account table plus seven application tables — community, membership, course, lesson, lesson progress, post, comment — with the one-to-many relationships between them&lt;/li&gt;
&lt;li&gt;Membership as a row, not a role — community access is represented by a membership record rather than creating a separate Momen permission role per community, so joining never grants access automatically. States are requested, active, and removed.&lt;/li&gt;
&lt;li&gt;Four &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows" rel="noopener noreferrer"&gt;Actionflows&lt;/a&gt; — Join community, Mark lesson complete, Grant or remove access, and a Creator Circle Gateway, each with typed inputs, execution nodes, and bound return fields&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.momen.app/docs/publish_operate/permissions" rel="noopener noreferrer"&gt;Permissions&lt;/a&gt; per role, table, column, and operation — direct table writes default to deny, preserving scoped reads and author-owned discussion edits; Actionflow access is also scoped by role, so the non-logged-in role can reach almost nothing&lt;/li&gt;
&lt;li&gt;Email-and-password sign-in only, with the other default methods disabled to avoid duplicate account identities&lt;/li&gt;
&lt;li&gt;Seed data — five demo accounts, one published community, two courses, seven ordered lessons, and memberships covering every state, so the frontend has something real to render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point James returns to is that none of it is opaque. The tables, flows, and permission rules are all sitting in the editor afterward, inspectable and editable by hand or by another agent — which is not true of a backend an agent scaffolds into code you then own outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring the frontend one slice at a time
&lt;/h2&gt;

&lt;p&gt;Rather than connecting the whole UI at once, James builds the interface against local mock data first — including mock sign-in personas so role-gated screens can be demonstrated — then replaces the mocks slice by slice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Momen auth plus the member dashboard&lt;/li&gt;
&lt;li&gt;Community discovery and joining, through the Join community Actionflow&lt;/li&gt;
&lt;li&gt;Courses, lessons, progress marking, and discussions&lt;/li&gt;
&lt;li&gt;The admin access view and end-to-end verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each slice goes all the way down — API call, UI, states — before the next one starts. Deployment is the last step, to Vercel through Codex, since the frontend lives outside Momen and so doesn't use Momen's own one-click deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Momen charges for a backend like this
&lt;/h2&gt;

&lt;p&gt;Momen prices per project per month, and every plan includes a baseline of resources rather than metering from zero:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free — $0. The full visual builder, one API, one Actionflow, one AI agent, and publishing on a Momen subdomain. Enough to build and explore.&lt;/li&gt;
&lt;li&gt;Basic — $33/project/month billed annually, $39 monthly. Unlimited APIs, Actionflows, and AI agents, a custom domain, white labeling, and SEO controls.&lt;/li&gt;
&lt;li&gt;Pro — $85/project/month billed annually, $99 monthly. Adds Stripe payments, single sign-on, multiple frontends on one backend, more collaborators, and higher throughput.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a project shaped like Creator Circle, the binding constraint is the Actionflow count, not storage: four flows is past the Free plan's single Actionflow, so Basic is the realistic entry point. There are no payments in this build, so nothing here requires Pro. Frontend hosting on Vercel is free at this scale. Full details are on &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Momen's pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;James walks through the entire build — the four planning passes, the Stitch handoff, Codex provisioning the Momen backend live in the editor, and each frontend slice — in &lt;a href="https://www.youtube.com/watch?v=iMZUp6E3lJ0" rel="noopener noreferrer"&gt;his video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To try the same split: install the Momen plugin in your coding agent, write the scope and backend plan down before building, let the agent provision the backend first, and only then point it at the frontend.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>codex</category>
      <category>nocode</category>
    </item>
    <item>
      <title>How to Limit Daily Claims in Momen: Unique Constraint vs. State Counter</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:10:54 +0000</pubDate>
      <link>https://dev.to/momen_hq/how-to-limit-daily-claims-in-momen-unique-constraint-vs-state-counter-5lp</link>
      <guid>https://dev.to/momen_hq/how-to-limit-daily-claims-in-momen-unique-constraint-vs-state-counter-5lp</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://momen.app/blogs/meet-your-nocode-ai-copilot-build-apps-by-chatting-in-momen/" rel="noopener noreferrer"&gt;AI Copilot&lt;/a&gt;, and both come with an editor link you can clone directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Method 1: Unique Constraint&lt;/th&gt;
&lt;th&gt;Method 2: State Counter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Design Approach&lt;/td&gt;
&lt;td&gt;Append‑only: each claim creates a new row&lt;/td&gt;
&lt;td&gt;State‑based: each user has a fixed record row, updated on each claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency Defense&lt;/td&gt;
&lt;td&gt;Database unique index&lt;/td&gt;
&lt;td&gt;Update node filter (daily_claim_count &amp;lt; 3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Volume&lt;/td&gt;
&lt;td&gt;Grows linearly with claims over time&lt;/td&gt;
&lt;td&gt;Grows with the number of active users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit Traceability&lt;/td&gt;
&lt;td&gt;Built‑in (each claim is a row)&lt;/td&gt;
&lt;td&gt;Requires additional configuration (database trigger)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best For&lt;/td&gt;
&lt;td&gt;High‑value rewards, strict audit requirements&lt;/td&gt;
&lt;td&gt;Lightweight daily tasks, check‑in counters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Daily Claim Limit (Unique Constraint)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Demo Project
&lt;/h3&gt;

&lt;p&gt;Clone the Unique Constraint project&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Use Cases: Daily check-ins, limited coupon distributions, or daily point systems.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;This tutorial uses pre-styled layout blocks from the "&lt;a href="https://editor.momen.app/tool/PO76RBeB00B/WEB?code=UKpAScIGuBC4c&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Common UI Presets&lt;/a&gt;" 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.&lt;/p&gt;

&lt;p&gt;Data Storage&lt;/p&gt;

&lt;p&gt;To implement a daily claim limit system, we need to establish a dedicated table in the database to store claim records.&lt;/p&gt;

&lt;p&gt;Data Model&lt;/p&gt;

&lt;p&gt;Table: claim_record&lt;/p&gt;

&lt;p&gt;Logs every successful reward claim.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Primary Key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim_date&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;The date the reward was claimed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim_sequence&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;The index of the claim for that day (1, 2, or 3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;account_id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Foreign Key linked to the account table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Field Name&lt;/p&gt;

&lt;p&gt;Type&lt;/p&gt;

&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Primary Key&lt;/p&gt;

&lt;p&gt;claim_date&lt;/p&gt;

&lt;p&gt;Date&lt;/p&gt;

&lt;p&gt;The date the reward was claimed&lt;/p&gt;

&lt;p&gt;claim_sequence&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;The index of the claim for that day (1, 2, or 3)&lt;/p&gt;

&lt;p&gt;account_id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Foreign Key linked to the account table&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff0jnyovqhqwojbwuierv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff0jnyovqhqwojbwuierv.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Database Constraints&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Table: Select the claim_record table.&lt;/li&gt;
&lt;li&gt;New constraint: Click on the table settings and select Edit constraint.&lt;/li&gt;
&lt;li&gt;Composite unique columns: Add a new unique constraint named unique_claim_record_account_date_sequence.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frycn2ra9ukw8m7gl5b6a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frycn2ra9ukw8m7gl5b6a.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Logic &amp;amp; State ConfigurationActionflow: Claim Reward&lt;/p&gt;

&lt;p&gt;This Actionflow validates and executes the reward claim.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2txr6hvmrii637utnhnl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2txr6hvmrii637utnhnl.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actionflow variable: Create a variable named status with the type Boolean to return the result to the frontend.&lt;/li&gt;
&lt;li&gt;Get ID: Add a Get ID node. Its output field is current_account_id.&lt;/li&gt;
&lt;li&gt;Query data: Add a Query data node to fetch the user's claims for the current day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_record.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;claim_date Equal to Current date.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;account_id Equal to current_account_id (from the Get ID node).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limit: Set to 3.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcafpwn4cuzm9ysitiqn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcafpwn4cuzm9ysitiqn.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Condition: Add a Condition node to check the current claim count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case 1 (Less than 3 times): Set the condition to Actionflow data/Fetch today's claim records/Count Less than 3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jl9gro8uwibsietufxc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jl9gro8uwibsietufxc.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insert data: In the "Less than 3 times" branch, add an Insert data node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_record.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;claim_date: Set to Current date.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account_id: Set to current_account_id.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;claim_sequence: Use a formula to calculate the next index: Actionflow data/Fetch today's claim records/Count + 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fele6hzyyd90sq30apbfo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fele6hzyyd90sq30apbfo.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cvs06v41k3wgu3nlpuk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cvs06v41k3wgu3nlpuk.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set variable (Less than 3 times): In the "Less than 3 times" branch, add a Set variable node after the insertion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Condition: Set the condition to Actionflow data/Insert data/id Is not null.&lt;/p&gt;

&lt;p&gt;Value: Set status to True.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set variable (Reached 3 times): In the "3 times reached" branch, add a Set variable node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Value: Set status to False.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02mk5uz10st28p7acuos.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02mk5uz10st28p7acuos.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actionflow output: Configure the output to return the status variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By setting the &lt;strong&gt;Conflict resolution&lt;/strong&gt; 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 &lt;code&gt;status&lt;/code&gt; will return &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;UI Construction &amp;amp; Interaction&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Page Setup&lt;/li&gt;
&lt;li&gt;Create Page: In the Pages tab, click + and add a new page named Page Daily Reward Claim.&lt;/li&gt;
&lt;li&gt;Add Conditional View: Drag a Conditional View component onto the canvas. This will act as the container for the different reward states.&lt;/li&gt;
&lt;li&gt;Configure Cases: Rename the default cases in the Component Tree:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Case 1: Case Less than 3 times&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case 2: Case Reached 3 times&lt;/li&gt;
&lt;li&gt;Case 3: Initializing&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Active Claim State (Less than 3 times)&lt;/li&gt;
&lt;li&gt;Add Button: Inside the Case Less than 3 times case, add a Button component.&lt;/li&gt;
&lt;li&gt;Data Binding (Button Text): Click the Databinding icon next to the Button text field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Combine static text with dynamic data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expression: Claim daily reward ( + Logged in user/claim_record/Count + /3)&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy1yxc7bl75dsg9prg9h.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy1yxc7bl75dsg9prg9h.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interaction (OnClick): Go to the Action tab of the button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trigger: OnClick -&amp;gt; Actionflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Actionflow: Choose Claim Reward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F45axrmoe5j1a2xzebx1g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F45axrmoe5j1a2xzebx1g.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feedback Logic (On Success): Click + under On success and select Condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Claim Success: Set the condition to Action result/Actionflow/status Is true.&lt;/p&gt;

&lt;p&gt;Action: Show toast with the message "Claimed successfully".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case Claim Failed: Set the condition to Action result/Actionflow/status Is false.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action: Show toast with the message "Claim failed".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.momen.app%2Fassets%2Fimages%2Fhowto_daily_claim_constraint_momen_toast_logic_11.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.momen.app%2Fassets%2Fimages%2Fhowto_daily_claim_constraint_momen_toast_logic_11.webp" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Limit Reached &amp;amp; Initializing States&lt;/li&gt;
&lt;li&gt;Disabled Button: In the Case Reached 3 times case, add a Button component.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Button text: Set to Claim daily reward (3/3).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Interaction: Remove all actions to ensure it is non-interactive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Login Prompt: In the Initializing case, add a Text component.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Content: Set to "Please log in first".&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visibility Logic Configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Select the Conditional View and click Config in the right panel to define when each case should be displayed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case Less than 3 times:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Condition: And&lt;/p&gt;

&lt;p&gt;Global/is logged in Is true&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logged in user/claim_record/Count (filtered by today's date – apply the same filter as in the button binding) Less than 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jvqa3pkvirur91t8kc6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jvqa3pkvirur91t8kc6.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case Reached 3 times:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Condition: Global/is logged in Is true. (Since this is the second branch, it will only execute if the "Less than 3" condition fails.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initializing:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Displays when the user is not authenticated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduhfcv38m8fi3pm0jebr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduhfcv38m8fi3pm0jebr.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VerificationStep 1: Authentication Test&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Preview and use the Login simulation at the bottom of the screen.&lt;/li&gt;
&lt;li&gt;Select Restore user to logged out state.&lt;/li&gt;
&lt;li&gt;Expected Result: The page displays "Please log in first".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2: Claiming Rewards&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Login simulation -&amp;gt; Create new to log in as a test user.&lt;/li&gt;
&lt;li&gt;Click the "Claim daily reward (0/3)" button.&lt;/li&gt;
&lt;li&gt;Expected Result: A "Claimed successfully" toast appears, and the button text updates to "(1/3)".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 3: Reaching the Daily Limit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the button two more times.&lt;/li&gt;
&lt;li&gt;Expected Result: After the third claim, the button style changes to the Disabled state and displays "(3/3)".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 4: Database Integrity Check&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Data Source tab and open the claim_record table.&lt;/li&gt;
&lt;li&gt;Expected Result: You should see exactly 3 records for the test user with sequence numbers 1, 2, and 3 under today's date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily Claim Limit (State Counter)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Demo Project
&lt;/h3&gt;

&lt;p&gt;Clone the State Counter project&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Goal: Create a secure daily reward system that limits users to 3 claims per day.&lt;/li&gt;
&lt;li&gt;Use Cases: Daily login rewards, free API rate limiting, daily lucky draws, or high-frequency business action auditing.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Data Storage&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Data Model&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Table: claim_status&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Field Name&lt;/p&gt;

&lt;p&gt;Type&lt;/p&gt;

&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Primary Key (system default)&lt;/p&gt;

&lt;p&gt;last_claim_date&lt;/p&gt;

&lt;p&gt;Date&lt;/p&gt;

&lt;p&gt;Used to verify if the request falls on a "new day"&lt;/p&gt;

&lt;p&gt;daily_claim_count&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Increments sequentially (1‑3)&lt;/p&gt;

&lt;p&gt;account_id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Foreign key to account (1:1 relationship, automatically unique)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Table: claim_log An append‑only audit log table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Field Name&lt;/p&gt;

&lt;p&gt;Type&lt;/p&gt;

&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Primary Key (system default)&lt;/p&gt;

&lt;p&gt;claim_sequence&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Logs the index of the claim (1, 2, or 3)&lt;/p&gt;

&lt;p&gt;account_id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Foreign key to account (1:N relationship)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flh9cugn0a4nti1i9tuje.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flh9cugn0a4nti1i9tuje.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Logic &amp;amp; State Configuration"Daily Claim" Actionflow Construction&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actionflow variable: Add a global variable inside the Actionflow named status with the type Boolean to track the overall success of the request execution.&lt;/li&gt;
&lt;li&gt;Get ID: Add a custom code node named "Get ID" to retrieve the logged-in user's account ID (current_account_id).&lt;/li&gt;
&lt;li&gt;Query data: Add a Query Record node named "Get Claim Status".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_status.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter: account_id Equal to Actionflow data/Get ID/current_account_id.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limit: 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Condition - Root Branching: Add a Branch Separation node named "Condition" to verify if the status record exists in the database.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Existing Status Record: Actionflow data/Get Claim Status/id Is not null. (Proceeds to Step 5)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Case No Records (Else): If the user has never claimed before (the record is null). (Proceeds to Step 9)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Condition - Nested Date Check: Inside the existing record branch, add a nested Branch Separation node named "Condition".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Already Claimed Today: Actionflow data/Get Claim Status/last_claim_date Equal to getCurrentDate. (Proceeds to Step 6)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Case Not Claimed Today (Else): If the last claim date belongs to a previous day. (Proceeds to Step 8)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update data (In Case Already Claimed Today): Add an Update Record node named "Update Daily Claim Count".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_status.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: daily_claim_count -&amp;gt; Arithmetic Operator: increment by 1.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filter: account_id Equal to Actionflow data/Get ID/current_account_id AND daily_claim_count Less than 3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set variable: Add a Set Variable node to determine the value of the status variable based on the execution result of the counter increment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Updated Successfully: Actionflow data/Update Daily Claim Count/id Is not null -&amp;gt; Set to True.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Case Update Failed (Else): Set to False (this happens if the daily_claim_count is already 3 or more, violating the filter constraint).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_status.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: last_claim_date -&amp;gt; getCurrentDate, daily_claim_count -&amp;gt; Set directly to 1.&lt;/li&gt;
&lt;li&gt;Filter: account_id Equal to Actionflow data/Get ID/current_account_id AND last_claim_date Not equal to getCurrentDate.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set variable: Update the Actionflow variable status. If Update Claim Status/id Is not null, set to True, else False.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_status.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: last_claim_date -&amp;gt; getCurrentDate, daily_claim_count -&amp;gt; 1, account_id -&amp;gt; Actionflow data/Get ID/current_account_id.&lt;/li&gt;
&lt;li&gt;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.)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set variable: Update the Actionflow variable status. If Add Claim Status Record/id Is not null, set to True, else False.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Actionflow output: Merge all conditional branches into the Flow End node and configure the output data binding to return Actionflow data/Variable/status.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Claim Log" Actionflow &amp;amp; Trigger&lt;/p&gt;

&lt;p&gt;Automate the audit trail whenever the claim_status table is modified.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actionflow input: Create a new Actionflow named "Claim Log". Add inputs: account_id (Bigint) and sequence (Bigint).&lt;/li&gt;
&lt;li&gt;Configure the Database Trigger: Enable the Database Trigger for this Actionflow to capture automated updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trigger type: DB_TRIGGER.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select DB Operation Type: INSERT_OR_UPDATE.&lt;/li&gt;
&lt;li&gt;Select Table: claim_status.&lt;/li&gt;
&lt;li&gt;Actionflow inputs mapping:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;account_id -&amp;gt; Inserted or updated data/account_id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;sequence -&amp;gt; Inserted or updated data/daily_claim_count.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insert data: Add an Insert Record node named "Insert data" to persist the changes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table: claim_log.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: claim_sequence -&amp;gt; Actionflow data/Input/sequence, account_id -&amp;gt; Actionflow data/Input/account_id.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By using an &lt;code&gt;On database changed&lt;/code&gt; trigger set to &lt;code&gt;INSERT_OR_UPDATE&lt;/code&gt;, you ensure that every change to the &lt;code&gt;claim_status&lt;/code&gt; table is logged.&lt;/p&gt;

&lt;p&gt;UI Construction &amp;amp; Interaction&lt;/p&gt;

&lt;p&gt;Configure the frontend to dynamically display the claim status and handle user interactions based on the backend logic.&lt;/p&gt;

&lt;p&gt;Page: Daily Reward Claim&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conditional View Setup: In the Component Tree, select the Conditional View component. Rename the default cases to reflect the business logic:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Under 3 Times: For users who can still claim rewards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case 3 Times Reached: For users who have hit their daily limit.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initializing: The default state for logged-out users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Active Button: Within Case Under 3 Times, select the Button / Primary component.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Button text: Click the Data Binding icon and select Condition.&lt;/p&gt;

&lt;p&gt;Case No Records: If Logged in user -&amp;gt; claim_status -&amp;gt; daily_claim_count Is null, set the display data to 0.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case Record Exists: Else, bind the data to Logged in user -&amp;gt; claim_status -&amp;gt; daily_claim_count.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final String: Set the static text to Claim Daily Reward ({Condition}/3).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Button Interaction: Select the active button, go to the Interaction tab, and configure its behavior.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action: Add an OnClick event and select the Daily Claim Actionflow. Enable the Loading animation toggle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On success:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Show toast: Add a Show toast node. Click the Data Binding icon for the Message and select Condition.&lt;/p&gt;

&lt;p&gt;Case Claim Successful: If Action result -&amp;gt; Daily Claim -&amp;gt; status Is true, set the message to Claim successful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Case Claim Failed: Else, set the message to Claim failed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh login user data: Add a Refresh login user data node to ensure the frontend counter updates immediately after the database change.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3v4zfqwqyaboometf4k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3v4zfqwqyaboometf4k.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbzoior95nslakj5p6b9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbzoior95nslakj5p6b9.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Disabled Button: Within Case 3 Times Reached, select the Button / Disabled component to configure the UI when the daily limit is hit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Initializing View: Within Initializing, select the Text component to prompt unauthenticated users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Text text: Directly enter the static text string: Please log in first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Conditional View Logic: Select the master Conditional View and click Config in the right panel to define when each case is displayed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case Under 3 Times:&lt;/p&gt;

&lt;p&gt;Add a condition: Global -&amp;gt; Is logged in Is true.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add an And condition: Logged in user -&amp;gt; claim_status -&amp;gt; daily_claim_count Less than 3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Case 3 Times Reached:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a condition: Global -&amp;gt; Is logged in Is true.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add an And condition: Logged in user -&amp;gt; claim_status -&amp;gt; daily_claim_count Greater than or equal 3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initializing:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a condition: Global -&amp;gt; Is logged in Is false.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdltuy8ma4ddqqqa3diy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdltuy8ma4ddqqqa3diy.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha2u7wpd9qg9nyqxoy34.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha2u7wpd9qg9nyqxoy34.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VerificationStep 1: Logged-out State&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Preview icon in the top right.&lt;/li&gt;
&lt;li&gt;Expected Result: The page displays the text "Please log in first."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2: First Claim&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the Login simulation tool in the bottom bar. Click Create new and select the Logged-in user role.&lt;/li&gt;
&lt;li&gt;Click the button labeled Claim Daily Reward (0/3).&lt;/li&gt;
&lt;li&gt;Expected Result: A "Claim successful" toast appears, and the button label updates to Claim Daily Reward (1/3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 3: Reaching the Limit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the button two more times.&lt;/li&gt;
&lt;li&gt;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).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 4: Database Audit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the Data Source tab.&lt;/li&gt;
&lt;li&gt;Check the claim_status table: The daily_claim_count should be 3.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the counter does not update on the frontend, verify that the &lt;code&gt;Refresh login user data&lt;/code&gt; node is correctly placed in the &lt;code&gt;On success&lt;/code&gt; branch of the button's &lt;code&gt;OnClick&lt;/code&gt; interaction.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Primary Key (system default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;last_claim_date&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Used to verify if the request falls on a "new day"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;daily_claim_count&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Increments sequentially (1‑3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;account_id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Foreign key to account (1:1 relationship, automatically unique)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Both projects are ready to clone and explore in the Momen editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://editor.momen.app/tool/z7Bx4APAJrO/WEB?code=LafkRsCNdfdhq&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Unique Constraint — clone the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://editor.momen.app/tool/Vbr0R9B92XZ/WEB?code=neVO5uQxwHMnX&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;State Counter — clone the project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;a href="https://docs.momen.app/docs/data/guide/database_configuration/" rel="noopener noreferrer"&gt;database configuration guide&lt;/a&gt; and the &lt;a href="https://docs.momen.app/docs/actions/reference/trigger_list/" rel="noopener noreferrer"&gt;Actionflow trigger reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>daily</category>
      <category>claim</category>
      <category>limit</category>
      <category>momen</category>
    </item>
    <item>
      <title>How to Save Form Drafts in Momen: Manual vs. Real-Time Approaches</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:10:42 +0000</pubDate>
      <link>https://dev.to/momen_hq/how-to-save-form-drafts-in-momen-manual-vs-real-time-approaches-251n</link>
      <guid>https://dev.to/momen_hq/how-to-save-form-drafts-in-momen-manual-vs-real-time-approaches-251n</guid>
      <description>&lt;p&gt;Long or multi-step forms — job applications, insurance claims, multi-page surveys — put user input at risk. A closed tab, a lost connection, or a long pause before submission can wipe out everything someone just typed. Giving users a way to save a "draft" and come back later fixes that.&lt;/p&gt;

&lt;p&gt;Momen supports two ways to implement draft saving: a manual method where users click a button to save their progress, and a real-time method that saves automatically whenever an input loses focus. Both projects below are built entirely with Momen's in-product &lt;a href="https://momen.app/blogs/meet-your-nocode-ai-copilot-build-apps-by-chatting-in-momen/" rel="noopener noreferrer"&gt;AI Copilot&lt;/a&gt;, and both come with an editor link you can clone directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Saving drafts can prevent data loss when users close a form accidentally, lose network connection, or step away for too long before final submission. Momen offers two main ways to implement this: manual saving triggered by a button click, and automatic saving triggered when an input field loses focus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Method 1: Manual Draft Saving&lt;/th&gt;
&lt;th&gt;Method 2: Real-time Draft Saving&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interaction Core&lt;/td&gt;
&lt;td&gt;User clicks a "Save Draft" button to store progress&lt;/td&gt;
&lt;td&gt;Data updates automatically when input fields lose focus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger Timing&lt;/td&gt;
&lt;td&gt;Explicit, user-controlled&lt;/td&gt;
&lt;td&gt;Implicit, automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation Logic&lt;/td&gt;
&lt;td&gt;Page variable (form_id) toggles between create and edit modes&lt;/td&gt;
&lt;td&gt;Pre-retrieve a blank placeholder record, then update on blur events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource Consumption&lt;/td&gt;
&lt;td&gt;Low — only makes requests when the user clicks save&lt;/td&gt;
&lt;td&gt;Auto-saving triggers more frequent database updates, which may increase server load. Use it where user experience outweighs resource cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Cases&lt;/td&gt;
&lt;td&gt;Short forms, or cases where users prefer explicit confirmation&lt;/td&gt;
&lt;td&gt;Long-form editing, multi-step workflows, high-value inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Manual Draft Saving for Forms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Demo Project
&lt;/h3&gt;

&lt;p&gt;Clone the Manual Draft Saving project&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Goal: Build a form system where users can save incomplete data as drafts and resume editing later before final submission.&lt;/li&gt;
&lt;li&gt;Core Logic: Use a status field (Draft / Submitted / Deleted) to track each record's state, and a page variable form_id to control the UI mode: -1 means creating a new record, any other value means editing an existing draft.&lt;/li&gt;
&lt;li&gt;Use Cases: Job applications, insurance claim forms, multi-page questionnaires, or any workflow where users may need to return later to complete their input.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;This tutorial uses pre-styled layout blocks from the "&lt;a href="https://editor.momen.app/tool/PO76RBeB00B/WEB?code=UKpAScIGuBC4c&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Common UI Presets&lt;/a&gt;" template page. These preset elements provide basic styling and layout only; they contain no conditional logic, data bindings, or Actionflows. You can copy them directly to skip manual styling and focus on the core logic.&lt;/p&gt;

&lt;p&gt;SetupData Model&lt;/p&gt;

&lt;p&gt;Table: form — stores draft and submitted records.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Auto-generated, unique identifier (system column)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;created_at&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;Auto-generated (system column)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;updated_at&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;Auto-generated (system column)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Form state: Draft, Submitted, or Deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field_1&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Custom business field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field_2&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Custom business field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;account_id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Foreign key linking to the account table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Field Name&lt;/p&gt;

&lt;p&gt;Type&lt;/p&gt;

&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Auto-generated, unique identifier (system column)&lt;/p&gt;

&lt;p&gt;created_at&lt;/p&gt;

&lt;p&gt;Timestamp&lt;/p&gt;

&lt;p&gt;Auto-generated (system column)&lt;/p&gt;

&lt;p&gt;updated_at&lt;/p&gt;

&lt;p&gt;Timestamp&lt;/p&gt;

&lt;p&gt;Auto-generated (system column)&lt;/p&gt;

&lt;p&gt;status&lt;/p&gt;

&lt;p&gt;Text&lt;/p&gt;

&lt;p&gt;Form state: Draft, Submitted, or Deleted&lt;/p&gt;

&lt;p&gt;field_1&lt;/p&gt;

&lt;p&gt;Text&lt;/p&gt;

&lt;p&gt;Custom business field&lt;/p&gt;

&lt;p&gt;field_2&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Custom business field&lt;/p&gt;

&lt;p&gt;account_id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Foreign key linking to the account table&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fziuray5ve9z08pd0n61i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fziuray5ve9z08pd0n61i.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page Entry Logic&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the left sidebar, click the Pages tab and add a page named Page Form Drafts.&lt;/li&gt;
&lt;li&gt;Drag a Button onto the canvas. Change the button text to Create and rename the component to Button Create.&lt;/li&gt;
&lt;li&gt;Select Button Create and add an action under OnClick.&lt;/li&gt;
&lt;li&gt;Configure the logic flow:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a Condition node.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case: Guest — If getIsLoggedIn is Is false, add a Show toast action: Please log in first.&lt;/li&gt;
&lt;li&gt;Case: Logged In — Add an Open custom modal node targeting Modal Fill Form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fba4r3y3az58p9zkmq487.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fba4r3y3az58p9zkmq487.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modal UI Builder&lt;/p&gt;

&lt;p&gt;The modal is the workspace for filling out forms. A page variable tracks whether the user is creating a new record or editing an existing draft.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Modal tab in the left sidebar and create a modal named Modal Fill Form.&lt;/li&gt;
&lt;li&gt;Select the root container of Modal Fill Form, go to the Data tab in the right sidebar, and click + next to Variable.&lt;/li&gt;
&lt;li&gt;Set Name to form_id, Type to Bigint, and Default value to -1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;form_id&lt;/code&gt; controls the form mode. &lt;code&gt;-1&lt;/code&gt; means "Create Mode". When an existing draft is loaded, this variable holds the actual record ID, switching the form to "Edit Mode".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppf0zq9w155m27xw66ou.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppf0zq9w155m27xw66ou.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag two Text Input components onto the modal canvas:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rename the first input to Input Field 1 and set its type to Text.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rename the second input to Input Field 2 and set its type to Bigint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8avzxdhzyszgxdrpj4u3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8avzxdhzyszgxdrpj4u3.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the Button Cancel component. Under its OnClick configuration, add a Close modal node and set Scope to Close top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdehxd8o59z51uuwgfozk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdehxd8o59z51uuwgfozk.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Draft List &amp;amp; ActionsDraft List Components&lt;/p&gt;

&lt;p&gt;Now that the modal is configured, return to the main page to build the draft list. The list will show all drafts belonging to the logged-in user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the Text Current number of drafts component. Bind its Content to Logged in user/form/Count and add a local filter: status Equal to 'Draft'.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35bhl1qg7g3aqaltuxq9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35bhl1qg7g3aqaltuxq9.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Logged in user/form/Count&lt;/code&gt; is evaluated when user data loads or refreshes. The draft list uses the &lt;code&gt;Subscription&lt;/code&gt; request type, which automatically keeps the UI in sync with database changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag a List component onto the canvas and rename it to List Drafts. Set its Data source to the form table and toggle Request type to Subscription for real-time syncing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsfkujsj8k51op3hp7lm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsfkujsj8k51op3hp7lm.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Query criteria panel for the list and add two filter rules combined with an And operator:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;account_id Equal to Logged in user/id&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status Equal to 'Draft'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz2x5ve38xvsqfiaw0slm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz2x5ve38xvsqfiaw0slm.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interactive View Toggles&lt;/p&gt;

&lt;p&gt;The draft list is placed inside a conditional view container that can be expanded or collapsed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Button Drafts inside the Case Closed state of the Conditional View Drafts container.&lt;/li&gt;
&lt;li&gt;Add a Switch conditional view action under its OnClick event. Target Conditional View Drafts and set Switch to Case Extended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8o87w989maro2xuqkwr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8o87w989maro2xuqkwr.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch the container to Case Extended, select its internal Button Drafts, and add a Switch conditional view action under its OnClick event, setting the target back to Case Closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17pcm7bjujous29vdz2e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17pcm7bjujous29vdz2e.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save as Draft Actionflow&lt;/p&gt;

&lt;p&gt;The "Save as Draft" logic validates that at least one field is filled, then inserts a new draft record and updates the form_id variable so the user can continue editing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Button Save as Draft.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1louluu86wqpcwjum498.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1louluu86wqpcwjum498.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a Condition node for input validation:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Name the branch Case: Input Empty and use the And operator.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rule: Input Field 1/Value Is null And Input Field 2/Value Is null.&lt;/li&gt;
&lt;li&gt;Add a Show toast node inside this branch: Please fill in at least one field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnsvwaoi1ol6k7x8cc9c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnsvwaoi1ol6k7x8cc9c.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the Case: Input Not Empty branch, add an Insert form node:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Target Table: form.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: Set status to Draft. Bind field_1 to Input Field 1/Value and field_2 to Input Field 2/Value. Bind account_id to Logged in user/id.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3qp0zm8wrpxiwtyospk2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3qp0zm8wrpxiwtyospk2.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After the Insert form node, add a Set variable node:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scope: Page and component&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target Variable: form_id&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Value: Bind to Action result/Insert form/id&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a Show toast node: Draft saved successfully.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwww8aka6vb22w43609re.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwww8aka6vb22w43609re.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Submitting the Form&lt;/p&gt;

&lt;p&gt;The "Apply" button determines whether to insert a new submitted record or update an existing draft, based on the form_id variable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Button Apply and enter its OnClick action flow.&lt;/li&gt;
&lt;li&gt;Add an input validation check using the same logic as the draft phase (at least one field must be filled). Under the Case: Input Not Empty branch, add a nested Condition node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgdcpijaycxzbu1rn2l4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgdcpijaycxzbu1rn2l4.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name the branch Case: New Submission and set the expression: Modal Fill Form/Variable/form_id Equal to -1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7j5a92awsoeaq20msbwd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7j5a92awsoeaq20msbwd.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 1: New Submission (form_id == -1):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add an Insert form node targeting the form table. Set status to Submitted and map the input fields to their corresponding database columns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wcr7x1yf33ck5xx285b.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wcr7x1yf33ck5xx285b.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 2: Update Existing Draft to Submitted (form_id != -1):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add an Update form node targeting the form table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query criteria: id Equal to Modal Fill Form/Variable/form_id.&lt;/li&gt;
&lt;li&gt;Parameters: Set status to Submitted. Sync business fields with current input values (Input Field 1/Value and Input Field 2/Value).&lt;/li&gt;
&lt;li&gt;Append a success toast (Submission successful) and close the modal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wpyfr6843p7f28hktu6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wpyfr6843p7f28hktu6.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Delete Confirmation Modal&lt;/p&gt;

&lt;p&gt;To prevent accidental deletion, a separate confirmation modal is used to pass the user's decision back to the calling Actionflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new modal named Modal Confirm Delete. Go to the Data tab in the right sidebar and create:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An Output parameter named is_deleted (Boolean) — this will be returned to the caller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A local Variable also named is_deleted (Boolean) — this tracks which button the user clicks inside the modal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2a8z91wfnltf31m4i9jm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2a8z91wfnltf31m4i9jm.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the modal root, go to the Outputs panel, and configure a Condition tree:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case 0: If local variable is_deleted Is true, pass True to the Output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case 1: If local variable is_deleted Is false, pass False to the Output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvdsiqshijy48qdyl8qs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvdsiqshijy48qdyl8qs.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Button NO. On its OnClick event, add a Set variable node to set is_deleted to False, followed by a Close modal (Close top) node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fky9m73ha6xj6mf0v00f1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fky9m73ha6xj6mf0v00f1.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Button YES. On its OnClick event, add a Set variable node to set is_deleted to True, followed by a Close modal (Close top) node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3a636mgtoqglbnvfs5e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3a636mgtoqglbnvfs5e.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Record Deletion&lt;/p&gt;

&lt;p&gt;The deletion logic checks whether the draft being deleted is currently loaded in the form, and shows a confirmation modal only if it is.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the Text Delete component inside the list item row and open its OnClick event flow.&lt;/li&gt;
&lt;li&gt;Add a Condition node at the start:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Case: Active Draft — Test if Modal Fill Form/Variable/form_id Equal to List/Data source/Current item/id.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63codaai9rwfs3c7j6xg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63codaai9rwfs3c7j6xg.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 1: Delete the draft currently open in the form (requires confirmation):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add an Open custom modal node targeting Modal Confirm Delete.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the On Modal Closed callback, add a Condition node.&lt;/li&gt;
&lt;li&gt;Case: Confirm Delete — Check if the output variable Modal/Modal Confirm Delete/is_deleted is Is true.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34j27pauh6fzfcuz6atx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34j27pauh6fzfcuz6atx.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If true, add an Update form node. Query criteria: id Equal to List/Data source/Current item/id. Set status to Deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flu69l8zkrh05k4f2ys59.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flu69l8zkrh05k4f2ys59.webp" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a Set variable node. Scope: Page and component. Set form_id back to -1 to reset the form to Create Mode.&lt;/li&gt;
&lt;li&gt;Add a success toast: Draft successfully deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1zs99szi46lycomd47y7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1zs99szi46lycomd47y7.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 2: Delete a draft that is not currently open (no confirmation):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the Case: Inactive Draft branch, add an immediate Update form node. Query criteria: id Equal to List/Data source/Current item/id. Set status to Deleted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a success toast: Draft successfully deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmd8f88fogr9sn3t3r6l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmd8f88fogr9sn3t3r6l.webp" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The draft is soft-deleted (status = &lt;code&gt;Deleted&lt;/code&gt;) and remains in the database. The list uses &lt;code&gt;Subscription&lt;/code&gt;, so the UI updates automatically when the record changes.&lt;/p&gt;

&lt;p&gt;Loading a Draft into the Form&lt;/p&gt;

&lt;p&gt;The "Apply" link loads a draft's data back into the form inputs and sets the form_id variable so the user can continue editing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the Text Apply link inside the list row template.&lt;/li&gt;
&lt;li&gt;Under its OnClick event, add a Set variable node. Target form_id and set its value to List/Data source/Current item/id.&lt;/li&gt;
&lt;li&gt;Add two Set input value nodes to load data back into the UI fields:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Target Input Field 1 and set its value to List/Data source/Current item/field_1.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target Input Field 2 and set its value to List/Data source/Current item/field_2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Step 1: Login Required Test&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Preview icon in the top navigation bar to open the development sandbox.&lt;/li&gt;
&lt;li&gt;Use the simulator toolbar at the base of the screen and click Restore user to logged out state to clear the session.&lt;/li&gt;
&lt;li&gt;Click the main page Create button.&lt;/li&gt;
&lt;li&gt;Expected Result: The form modal stays closed. A warning toast appears: Please log in first.&lt;/li&gt;
&lt;li&gt;Now log in using the Login simulation panel on the lower action bar — select the simulated user role Logged-in User.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2: Create a New Draft&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Create button again.&lt;/li&gt;
&lt;li&gt;Expected Result: Modal Fill Form opens with form_id = -1.&lt;/li&gt;
&lt;li&gt;Enter text in Input Field 1 and a number in Input Field 2.&lt;/li&gt;
&lt;li&gt;Click Save as Draft.&lt;/li&gt;
&lt;li&gt;Expected Result: A success toast appears: Draft saved successfully. The draft list updates and a new record appears. The form_id is now set to the new record's ID (e.g., 1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 3: Save a Second Draft&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the values in the input fields.&lt;/li&gt;
&lt;li&gt;Click Save as Draft again.&lt;/li&gt;
&lt;li&gt;Expand the draft list by clicking the Drafts toggle.&lt;/li&gt;
&lt;li&gt;Expected Result: The draft list now shows two items. The count reads: Current number of drafts: 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 4: Load a Draft, Then Delete It&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Apply link on the first draft row (ID 1).&lt;/li&gt;
&lt;li&gt;Expected Result: The input fields are populated with the values from draft 1.&lt;/li&gt;
&lt;li&gt;Click the Delete link on the same row.&lt;/li&gt;
&lt;li&gt;Expected Result: Because this draft is currently open in the form, the Modal Confirm Delete confirmation dialog appears.&lt;/li&gt;
&lt;li&gt;Click YES.&lt;/li&gt;
&lt;li&gt;Expected Result: The modal closes. The form_id resets to -1. The draft no longer appears in the list. The draft count decreases by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 5: Delete a Draft That Is Not Currently Open (If you have multiple drafts)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new draft (any values) and save it. Note its ID.&lt;/li&gt;
&lt;li&gt;Click Apply on a different draft (not the one you just created) to load it into the form.&lt;/li&gt;
&lt;li&gt;In the draft list, click Delete on the draft you just created (the one that is not currently loaded).&lt;/li&gt;
&lt;li&gt;Expected Result: The draft is deleted immediately (no confirmation dialog). The form_id remains unchanged (still pointing to the draft you loaded). The list updates and the count decreases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 6: Switch Between Drafts (If you have multiple drafts)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create two drafts (Draft A and Draft B).&lt;/li&gt;
&lt;li&gt;Click Apply on Draft A. Verify that form_id becomes A's ID and the inputs are populated with A's values.&lt;/li&gt;
&lt;li&gt;Without saving or deleting, click Apply on Draft B.&lt;/li&gt;
&lt;li&gt;Expected Result: The inputs now show B's values. The form_id has switched to B's ID. Draft A remains in the list unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers the manual approach. Next, here's the same feature built with real-time saving instead — no explicit "Save Draft" button required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time Draft Saving for Forms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Demo Project
&lt;/h3&gt;

&lt;p&gt;Clone the Real-time Draft Saving project&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Goal: Build a form system that prevents data loss by saving user inputs in real time, eliminating the need for manual save actions.&lt;/li&gt;
&lt;li&gt;Use Cases: Long-text editing, multi-step government applications, online examinations, and any scenario where users are at high risk of losing data due to accidental page closure or network interruptions.&lt;/li&gt;
&lt;li&gt;Core Logic: When users click "Create", the system checks for an existing blank draft. If none exists, it inserts a new record; if one exists, it reuses that ID. When input fields lose focus (On blur), the system automatically updates the record and changes its status from Blank Draft to Filled Draft. A dedicated list panel displays all historical drafts for easy recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;This tutorial uses pre-styled layout blocks from the "&lt;a href="https://editor.momen.app/tool/PO76RBeB00B/WEB?code=UKpAScIGuBC4c&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Common UI Presets&lt;/a&gt;" template page. These preset elements provide basic styling and layout only; they contain no conditional logic, data bindings, or Actionflows. You can copy them directly to skip manual styling and focus on the core logic.&lt;/p&gt;

&lt;p&gt;Data Model&lt;/p&gt;

&lt;p&gt;To implement the draft system, create a table in the database to store user progress and track status.&lt;/p&gt;

&lt;p&gt;Table Name: form&lt;/p&gt;

&lt;p&gt;Navigate to the Data tab in the Top Navigation Bar to configure this table.&lt;/p&gt;

&lt;p&gt;Field Name&lt;/p&gt;

&lt;p&gt;Type&lt;/p&gt;

&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Auto-generated, unique identifier&lt;/p&gt;

&lt;p&gt;status&lt;/p&gt;

&lt;p&gt;Text&lt;/p&gt;

&lt;p&gt;Enumerated values: Blank Draft, Filled Draft, Submitted, Deleted&lt;/p&gt;

&lt;p&gt;field_1&lt;/p&gt;

&lt;p&gt;Text&lt;/p&gt;

&lt;p&gt;Business input field 1&lt;/p&gt;

&lt;p&gt;field_2&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Business input field 2&lt;/p&gt;

&lt;p&gt;account_id&lt;/p&gt;

&lt;p&gt;Bigint&lt;/p&gt;

&lt;p&gt;Foreign key referencing id in the account table&lt;/p&gt;

&lt;p&gt;Relationship Mapping: The account table and form table have a One-to-Many relationship (one account owns multiple form/draft records).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status Flow&lt;/strong&gt;:The &lt;code&gt;status&lt;/code&gt; field governs the entire lifecycle of a record: - &lt;code&gt;Blank Draft&lt;/code&gt;: A placeholder created when the user opens the modal for the first time. - &lt;code&gt;Filled Draft&lt;/code&gt;: The user has filled in at least one field and the draft has been auto-saved. - &lt;code&gt;Submitted&lt;/code&gt;: The form has been officially submitted. It no longer appears in the draft list. - &lt;code&gt;Deleted&lt;/code&gt;: The draft has been soft-deleted. It no longer appears in the draft list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ah0llyl252ljqjc6au6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ah0llyl252ljqjc6au6.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page-Level Data Source&lt;/p&gt;

&lt;p&gt;Configure a data source at the Page Form Drafts level to check in real time whether a blank draft already exists for the current logged-in user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Data panel on the left sidebar and click + next to Data Sources.&lt;/li&gt;
&lt;li&gt;Set Name to source_form_empty_draft.&lt;/li&gt;
&lt;li&gt;Target Table: Select form.&lt;/li&gt;
&lt;li&gt;Request Type: Query.&lt;/li&gt;
&lt;li&gt;Limit: 1.&lt;/li&gt;
&lt;li&gt;Query Criteria (combined with And):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;account_id Equal to Logged in user/id&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status Equal to "Blank Draft"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuhwtsdvl4scruwuaku8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuhwtsdvl4scruwuaku8.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusing Blank Drafts&lt;/strong&gt;:This logic ensures that each logged-in user has at most one &lt;code&gt;Blank Draft&lt;/code&gt; record at any given time. When the user clicks Create again, the system simply reuses the existing blank record instead of generating useless empty entries.&lt;/p&gt;

&lt;p&gt;Create Button Actionflow&lt;/p&gt;

&lt;p&gt;Select Button Create on the main page and configure its OnClick event. This actionflow handles login validation and blank draft deduplication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login Check: Add a Condition node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Name the branch Case: Guest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Condition: getIsLoggedIn is false.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Action: Add a Show toast node with the message Please log in first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blank Draft Check: Under the Case: Logged In branch, add another nested Condition node.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path 1 - No Blank Draft: Set the condition to source_form_empty_draft/id is null.&lt;/p&gt;

&lt;p&gt;Insert Data: Target table form. Set status to "Blank Draft" and bind account_id to Logged in user/id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Custom Modal: Select Modal Fill Form. Bind the input parameter empty_draft_form_id to the id returned by the previous Insert action.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh Data Source: Target the page-level data source source_form_empty_draft.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Path 2 - Blank Draft Exists: Set the condition to always (default branch).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open Custom Modal: Select Modal Fill Form. Bind the input parameter empty_draft_form_id directly to source_form_empty_draft/id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh Data Source: Target the page-level data source source_form_empty_draft.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpe7rvbov0h9woksdkxt5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpe7rvbov0h9woksdkxt5.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fca0u6leirvc5gg7tna89.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fca0u6leirvc5gg7tna89.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvztr4y07z9d3axc5s05.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvztr4y07z9d3axc5s05.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82ol6y20l2r3qdn4vu1i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82ol6y20l2r3qdn4vu1i.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fthtkq8hb8wabyfa3d6sn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fthtkq8hb8wabyfa3d6sn.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modal State Management&lt;/p&gt;

&lt;p&gt;The modal needs to receive and store the draft ID passed from the main page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input Parameter: Select the root of Modal Fill Form. Go to the Data tab in the right sidebar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click + next to Input.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set Name to empty_draft_form_id and Type to Bigint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local Variable: Still in the Data tab, click + next to Variable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set Name to form_id and Type to Bigint. (Default value can be left empty.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On Page Load:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a Set variable action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target: form_id.&lt;/li&gt;
&lt;li&gt;Value: Bind to Input/empty_draft_form_id.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj5iv8l2iuevldp8rml3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj5iv8l2iuevldp8rml3.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrtgwd7dy23k3bnrdpqr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrtgwd7dy23k3bnrdpqr.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Input Components and Real-time Saving&lt;/p&gt;

&lt;p&gt;Configure the two input fields inside the modal. When users finish typing and move away (blur event), the data is saved automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input Field 1: Select the text input component. Keep Input value type as Text.&lt;/li&gt;
&lt;li&gt;Input Field 2: Select the text input component. Switch Input value type to Bigint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqivaizsyf8tfd0emsvd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqivaizsyf8tfd0emsvd.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Auto-Save on Blur for Field 1:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select Input Field 1 and add an event under On blur.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a Condition node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path 1 - Input is Empty: Set the condition to: Input Field 1/Value Is null AND Input Field 2/Value Is null.&lt;/p&gt;

&lt;p&gt;Action: Show toast with message Please fill in at least one field.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 2 - Input Not Empty: Set the condition to always.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update Data: Target table form. Query criteria: id Equal to Variable/Modal Fill Form/form_id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: Set status to "Filled Draft". Bind field_1 to Input Field 1/Value.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On Success Action: Show toast with message Draft saved successfully.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Auto-Save on Blur for Field 2:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select Input Field 2 and add an event under On blur.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a Condition node with the same empty-value logic as above.&lt;/li&gt;
&lt;li&gt;Path 1 - Input is Empty: Same intercept logic as above.&lt;/li&gt;
&lt;li&gt;Path 2 - Input Not Empty: Update Data with the same query criteria (id equal to form_id). Set status to "Filled Draft" and bind field_2 to Input Field 2/Value. Show toast Draft saved successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifo9hyosdkvg7xupfp57.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifo9hyosdkvg7xupfp57.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9h2dmzzn6g1rsthb3mkl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9h2dmzzn6g1rsthb3mkl.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual-field Empty Intercept&lt;/strong&gt;:Both input fields must be empty for the interception to trigger. This validation is configured independently on both &lt;code&gt;On blur&lt;/code&gt; events, ensuring users cannot bypass the check by only interacting with one field.&lt;/p&gt;

&lt;p&gt;Historical Drafts Panel&lt;/p&gt;

&lt;p&gt;Create a panel that displays all saved drafts (status = "Filled Draft") belonging to the current user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draft Counter:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select the Text: Current number of drafts component.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bind its Content to Logged in user/form/Count.&lt;/li&gt;
&lt;li&gt;Add a local filter: status Equal to "Filled Draft".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Logged in user/form/Count&lt;/code&gt; updates when user data loads or refreshes. The draft list uses &lt;code&gt;Subscription&lt;/code&gt; (see below), so we add a &lt;code&gt;Refresh current user data&lt;/code&gt; action inside the list's &lt;code&gt;On subscription success&lt;/code&gt; event to keep the counter accurate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5k4363n6wk553dcy8ex.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5k4363n6wk553dcy8ex.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draft List Data Source:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select the List component inside the modal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set Request type to Subscription.&lt;/li&gt;
&lt;li&gt;Target Table: form.&lt;/li&gt;
&lt;li&gt;Query Criteria (combined with And):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;account_id Equal to Logged in user/id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;status Equal to "Filled Draft".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On subscription success: Add a Refresh current user data action to keep the draft counter accurate.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feildrsu69i7bmqcguplj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feildrsu69i7bmqcguplj.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand/Collapse State Control:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select the Conditional View Drafts container. It contains two cases: Case Closed and Case Extended.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand: Inside Case Closed, select Button Drafts. Under its OnClick, add:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch conditional view → Target Conditional View Drafts, switch to Case Extended.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Switch conditional view → Target the secondary conditional view (the one wrapping the list), switch to Case Extended.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collapse: Inside Case Extended, select Button Drafts. Under its OnClick, add:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch conditional view → Target Conditional View Drafts, switch to Case Closed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch conditional view → Target the secondary conditional view, switch to Case Closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Synced Conditional Views&lt;/strong&gt;:This setup uses two conditional view components that must be switched simultaneously. The first controls the button state (Closed/Extended), and the second controls the visibility of the draft list. Using two &lt;code&gt;Switch conditional view&lt;/code&gt; actions in the same event ensures they stay in sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2vxy9u6erh0a4vpddrk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2vxy9u6erh0a4vpddrk.webp" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiaex912qmctkwj15n8yn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiaex912qmctkwj15n8yn.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load Draft (Apply):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside the list row, select Text Apply.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under its OnClick, add these actions in order:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set variable: Target form_id. Set value to Data source/List/Current item/id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set input value: Target Input Field 1. Set value to Data source/List/Current item/field_1.&lt;/li&gt;
&lt;li&gt;Set input value: Target Input Field 2. Set value to Data source/List/Current item/field_2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit438zve90a8heqw6mse.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit438zve90a8heqw6mse.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete Draft (with Editing Lock):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside the list row, select Text Delete.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under its OnClick, add a Condition node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path 1 - Editing Protection: Set condition to Variable/Modal Fill Form/form_id Equal to Data source/List/Current item/id.&lt;/p&gt;

&lt;p&gt;Action: Show toast with message Cannot delete a draft that is being edited.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 2 - Safe to Delete: Set condition to always.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update Data: Target table form. Query criteria: id Equal to Data source/List/Current item/id.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: Set status to "Deleted".&lt;/li&gt;
&lt;li&gt;On Success Action: Show toast with message Draft deleted successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi67rpatsv7g032sl6pbp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi67rpatsv7g032sl6pbp.webp" width="799" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1o2kp7z478sdaq1i57e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1o2kp7z478sdaq1i57e.webp" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Form Submission&lt;/p&gt;

&lt;p&gt;Configure the final submission and exit actions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply Button (Submit):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select Button Apply inside the modal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under its OnClick, add an Update Data action.&lt;/li&gt;
&lt;li&gt;Target Table: form.&lt;/li&gt;
&lt;li&gt;Query Criteria: id Equal to Variable/Modal Fill Form/form_id.&lt;/li&gt;
&lt;li&gt;Parameters: Set status to "Submitted".&lt;/li&gt;
&lt;li&gt;Success Actions:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Show toast: Submission successful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Close modal: Mode CLOSE_ON_TOP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cancel Button (Exit):&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select Button Cancel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under its OnClick, add a Close modal action with Mode CLOSE_ON_TOP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdjgnluavpm62b24c8wen.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdjgnluavpm62b24c8wen.webp" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fif3z8gwn6mv47q2n752o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fif3z8gwn6mv47q2n752o.webp" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Step 1: Verify Blank Draft Reuse Logic&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the Login Simulation toolkit at the bottom bar to select a test account.&lt;/li&gt;
&lt;li&gt;Click the Create button to open the form modal.&lt;/li&gt;
&lt;li&gt;Leave both input fields completely empty and click Cancel.&lt;/li&gt;
&lt;li&gt;Click Create again.&lt;/li&gt;
&lt;li&gt;Expected Result: The system does not create a new record. The Current form ID displayed at the top of the modal is identical to the previous one, confirming the data source successfully executed the reuse logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2: Verify Auto-Save on Blur&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inside the modal, type some text into Input Field 1.&lt;/li&gt;
&lt;li&gt;Click any blank area outside the input to trigger the blur event.&lt;/li&gt;
&lt;li&gt;Expected Result: A toast appears: Draft saved successfully. In the database, the record's status changes from Blank Draft to Filled Draft.&lt;/li&gt;
&lt;li&gt;Clear Input Field 1 so both fields are completely empty, then click outside to trigger blur.&lt;/li&gt;
&lt;li&gt;Expected Result: An intercepting toast appears: Please fill in at least one field., and the data is not saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 3: Verify Draft Recovery and Editing Lock&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand the draft panel and select any historical draft from the list, then click Apply.&lt;/li&gt;
&lt;li&gt;Expected Result: The values in both input fields refresh to match the historical record. The internal form_id variable updates to the selected record's ID.&lt;/li&gt;
&lt;li&gt;In the draft list, click Delete next to the draft currently being edited.&lt;/li&gt;
&lt;li&gt;Expected Result: The system triggers the editing lock protection and shows: Cannot delete a draft that is being edited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 4: Verify Submission Status Change&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Apply (Submit) inside the modal.&lt;/li&gt;
&lt;li&gt;Expected Result: A toast appears: Submission successful. The modal closes. The submitted record no longer appears in the draft list. In the database, the record's status is now Submitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 5: Verify Deletion Status Change&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Create to open a new form modal. Fill in at least one field and let it auto-save to create a new Filled Draft.&lt;/li&gt;
&lt;li&gt;In the draft list, click Delete on this new draft (make sure it is not currently loaded in the editor).&lt;/li&gt;
&lt;li&gt;Expected Result: A toast appears: Draft deleted successfully. The record disappears from the list. In the database, the record's status is now Deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Auto-generated, unique identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Enumerated values: Blank Draft, Filled Draft, Submitted, Deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field_1&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Business input field 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field_2&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Business input field 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;account_id&lt;/td&gt;
&lt;td&gt;Bigint&lt;/td&gt;
&lt;td&gt;Foreign key referencing id in the account table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Both projects are ready to clone and explore in the Momen editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://editor.momen.app/tool/X57jbwZwmMV/WEB?code=bV7jVSBmP7Ko5&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Manual Draft Saving — clone the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://editor.momen.app/tool/QP7kZReRLgM/WEB?code=O66W2KjDbfKyB&amp;amp;ref=0562398" rel="noopener noreferrer"&gt;Real-time Draft Saving — clone the project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have a project open, try swapping in your own table fields, or combine ideas from both methods — for example, auto-saving on blur while still keeping an explicit "Save Draft" button as a fallback. If you're building forms beyond drafts, &lt;a href="https://momen.app/blogs/build-dynamic-forms-in-your-app/" rel="noopener noreferrer"&gt;dynamic multi-choice forms&lt;/a&gt; and the &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows/" rel="noopener noreferrer"&gt;Actionflow guide&lt;/a&gt; are good next reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Draft saving comes down to one design choice: let the user decide when to save, or save automatically as they type. Momen's AI Copilot can scaffold either pattern — the data model, the Actionflow logic, and the UI state — directly inside the editor. Clone one of the projects above and adapt it to your own form.&lt;/p&gt;

</description>
      <category>form</category>
      <category>drafts</category>
      <category>momen</category>
      <category>manual</category>
    </item>
    <item>
      <title>BayHaul: An AI Junk Removal Booking App Built with Momen and Claude Code</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:10:29 +0000</pubDate>
      <link>https://dev.to/momen_hq/bayhaul-an-ai-junk-removal-booking-app-built-with-momen-and-claude-code-32lm</link>
      <guid>https://dev.to/momen_hq/bayhaul-an-ai-junk-removal-booking-app-built-with-momen-and-claude-code-32lm</guid>
      <description>&lt;p&gt;Junk removal is hard to quote remotely — the price depends on what the items actually are and how far the crew has to drive. BayHaul solves both: customers describe their junk and upload photos, and a multimodal AI agent analyzes both together to return a price range with a detected-items breakdown. When they schedule a pickup, the backend calculates real driving distance from the company warehouse before the request ever reaches staff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focg9cl3kow85z11ge5qm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focg9cl3kow85z11ge5qm.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Momen backend handles the AI quoting, the routing calculation, and a two-role permission system — all without server code. The frontend is a React + Vite app built with Claude Code using the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bayhaul-junk-removal.vercel.app/" rel="noopener noreferrer"&gt;Try the live app&lt;/a&gt; · &lt;a href="https://editor.momen.app/tool/k5PBKyGyP5E/WEB?code=MnxxRJDSe73k0&amp;amp;ref=6256176" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Does
&lt;/h2&gt;

&lt;p&gt;A customer signs up with email and password, then creates a removal request: a text description of what they need hauled, plus one or more photos. Submitting the request kicks off an async AI quoting flow in the background. When the estimate is ready, the request shows a price range (low and high in USD) and a structured breakdown of the detected items — what the AI identified from the photos and description, with approximate quantity and volume.&lt;/p&gt;

&lt;p&gt;If the customer is happy with the quote, they proceed to booking: they pick a weekday date, one of nine fixed 1-hour time slots (10 AM through 7 PM Pacific), and their pickup address. The frontend geocodes the address to coordinates client-side using Nominatim, then passes those coordinates to the backend. The backend runs its own geocode call for the company's warehouse address and uses OSRM to calculate the real driving route. The driving distance in miles and duration in minutes are written permanently to the request, and its status changes to scheduled.&lt;/p&gt;

&lt;p&gt;On the staff side, a second login route leads to the BayHaul dashboard. Staff see all requests across all customers, filterable by status. Each request shows the customer's description, photos, AI quote, requested time slot, and — critically — the driving distance and time calculated at booking. From the detail view, staff can accept or decline the request, optionally adding a note. A decline note is visible to the customer. A declined request can be edited (different date, time slot, or address) and resubmitted, which runs the distance calculation again for the new address.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Backend Is Built
&lt;/h2&gt;

&lt;p&gt;Everything server-side — data model, AI agent, routing logic, permissions — lives in Momen. No server code, no deployment configuration. The backend was configured using Momen's in-product AI Copilot, described entirely in natural language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COMPANY INFO (fixed, use exactly this everywhere it's needed):
- Company name: BayHaul Junk Removal
- Company warehouse / dispatch address: 1400 Fairway Drive, San Leandro, CA 94577
- Business hours: Monday to Friday, 10:00 AM to 7:00 PM, Pacific Time. Closed Saturday and Sunday.
- Appointment slots are fixed 1-hour blocks during business hours: 10-11, 11-12, 12-1, 1-2, 2-3, 3-4, 4-5, 5-6, 6-7 (all Pacific Time).

USER ROLES:
There are two kinds of accounts: regular customers, and company staff. A logged-in customer can only create and see their own removal requests, never anyone else's, and has read-only access to the company and appointment-slot data (no write access). Staff can see every request submitted by every customer, including their contact info, address, and photos, and can accept or decline requests, and have full read/write access to the company and appointment-slot data. Set this up using the platform's role/permission system rather than adding a "role" field to the user table. Only staff — never customers — may call the role-assignment logic described below.

Create one staff account directly with email/password login: {STAFF_EMAIL} / {STAFF_PASSWORD}.

Also build a one-off callable action that grants the staff role to a given account id, restricted to staff-only callers, so new staff accounts can be onboarded later.

REQUEST DATA:
Each removal request stores: the customer who submitted it, a text description, one or more uploaded photos, an AI-estimated price range (low and high, in USD), an AI-generated structured breakdown of detected items (item types, approximate quantity, rough volume estimate), the pickup address, the requested pickup date and time slot, a status, a driving distance, a driving duration, and an optional staff note.

Status is a field with exactly four fixed options, hard-coded as the only allowed values: quoted, scheduled, accepted, declined.

AI PRICE ESTIMATE:
Given a request's text description and its uploaded photos as input, generate as output: an estimated price range (low and high USD numbers) and a structured breakdown of detected items (item types, approximate quantity, rough volume estimate), based on analyzing both the description and the photos together. This sets the request's price and detected-items fields. The request's status is already "quoted" from creation and stays "quoted" after this step.

BOOKING:
Given a request already in "quoted" status plus a pickup date (must be a weekday), a time slot (must be one of the fixed slots above), a pickup address, and its coordinates as input: look up the warehouse address's coordinates, calculate the driving distance and time from the warehouse to the pickup coordinates, save the pickup date/time slot/address/coordinates and the calculated distance and duration on the request, and set status to "scheduled".

ACCEPT:
Given a request in "scheduled" status and an optional staff note as input: save the note if provided and set status to "accepted". Do not recalculate distance or duration here — they were already calculated and saved during booking.

DECLINE:
Given a request in "scheduled" status and an optional staff note as input: save the note if provided and set status to "declined".

RESUBMIT:
Given a request in "declined" status plus an updated pickup date, time slot, address, and coordinates as input: look up the warehouse address's coordinates again, recalculate the driving distance and time to the new pickup coordinates, update the pickup date/time slot/address/coordinates and the recalculated distance/duration on the request, and set status back to "scheduled".

DISTANCE AND DRIVING TIME:
Use OSRM (Open Source Routing Machine) for the driving distance/time calculation. Server: http://router.project-osrm.org, "driving" profile. Documentation: http://project-osrm.org/docs/v5.10.0/api/#route-service. Use OpenStreetMap Nominatim (https://nominatim.openstreetmap.org/search) to look up the warehouse address's coordinates, sending a descriptive User-Agent header on every request since Nominatim rejects requests without one. Read distance and duration from the OSRM response as output (convert meters to miles, and seconds to minutes). This calculation happens only during booking and resubmission — never during accept/decline.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;Five tables, each with a clear role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account — Momen's built-in authentication table, extended with phone_number and email. Handles signup and login.&lt;/li&gt;
&lt;li&gt;removal_request — The core record. Stores description, price_low_usd, price_high_usd, detected_items, pickup_address, pickup_date, time_slot, pickup_location (GEO_POINT), status, driving_distance_miles, driving_duration_minutes, and staff_note. Linked to account via customer_id.&lt;/li&gt;
&lt;li&gt;removal_photo — One record per uploaded photo, linked to its removal_request. Stores the image in Momen's IMAGE type.&lt;/li&gt;
&lt;li&gt;company — A single record containing BayHaul's fixed business data: company name, warehouse address, business hours, timezone, opening and closing times, and which days are working days. Pre-populated at project setup.&lt;/li&gt;
&lt;li&gt;appointment_slot — Nine records, one per 1-hour time slot (10:00–19:00 Pacific). Each stores slot_label, start_time, end_time, and an is_active flag. Linked to company. Pre-populated at project setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The status machine captures the full request lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quoted  →  scheduled  →  accepted
                      →  declined  →  scheduled (resubmit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two &lt;a href="https://docs.momen.app/docs/publish_operate/permissions/" rel="noopener noreferrer"&gt;roles&lt;/a&gt; are configured with Momen's RBAC system — Logged-in User for customers, BayHaul Staff for employees. There is no role field on the account table; permission boundaries are enforced at the platform level. Customers can only read their own removal_request records and have no direct write access to that table — all mutations go through Actionflows. Staff see every request with no row-level restriction.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI Quote Agent
&lt;/h3&gt;

&lt;p&gt;One &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration/" rel="noopener noreferrer"&gt;AI Agent&lt;/a&gt;, BayHaul AI Price Estimate, handles the quoting step. It runs on google/gemini-3-flash at temperature 0 and takes two inputs: description (text) and photos (an array of images). The image array is built with an arrayMapping formula that iterates over the removal_photo records linked to the request, passing each photo field to the model as a separate image input.&lt;/p&gt;

&lt;p&gt;The agent's system prompt instructs it to analyze both the written description and the uploaded photos together, then return three structured outputs: price_low_usd, price_high_usd, and detected_items. The detected items field is a structured breakdown — item types, approximate quantities, rough volume estimates — derived from what the model can identify in the photos and description combined.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Actionflows
&lt;/h3&gt;

&lt;p&gt;Seven &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows/" rel="noopener noreferrer"&gt;Actionflows&lt;/a&gt; orchestrate the full lifecycle of a removal request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5y9jp59brqzn78l0y0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5y9jp59brqzn78l0y0i.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create BayHaul Request (sync) — Takes a text description and the authenticated account ID, inserts a new removal_request record with status = quoted, and returns the new record's ID. Photos are uploaded separately before this flow runs; the flow itself only creates the parent request.&lt;/p&gt;

&lt;p&gt;Estimate BayHaul Request (async) — Takes request_id. Queries all removal_photo records linked to that request, builds an image array via arrayMapping, and passes that array alongside the request description to the BayHaul AI Price Estimate agent. Writes the returned price_low_usd, price_high_usd, and detected_items back to the request. Status stays quoted.&lt;/p&gt;

&lt;p&gt;Book BayHaul Pickup (sync) — Takes request_id, pickup_date, time_slot, pickup_address, and pickup_location (the customer's coordinates, resolved client-side). Requires the request to be in quoted status. Calls a Custom Code node that geocodes the warehouse address via Nominatim and calculates the driving route via OSRM, then converts the response to miles and minutes. Writes the pickup details and calculated distance/duration to the request and sets status to scheduled.&lt;/p&gt;

&lt;p&gt;Accept BayHaul Request (sync) — Takes request_id and an optional staff_note. Requires scheduled status. Updates status to accepted and writes the note if provided. Distance and duration are not recalculated — they were written at booking and do not change.&lt;/p&gt;

&lt;p&gt;Decline BayHaul Request (sync) — Takes request_id and an optional staff_note. Requires scheduled status. Updates status to declined and writes the note. The decline note is the only staff-authored field the customer can see.&lt;/p&gt;

&lt;p&gt;Resubmit BayHaul Request (sync) — Takes request_id and new pickup_date, time_slot, pickup_address, and pickup_location. Requires declined status. Runs the same Custom Code distance calculation as booking, overwrites all pickup and distance fields, and sets status back to scheduled.&lt;/p&gt;

&lt;p&gt;Assign BayHaul Staff Role (sync) — An operational utility. Takes an account_id and grants the BayHaul Staff role. Only callable by accounts that already have the BayHaul Staff role.&lt;/p&gt;

&lt;h3&gt;
  
  
  External APIs: Nominatim and OSRM
&lt;/h3&gt;

&lt;p&gt;Both distance-related APIs are free public services with no API keys required. They are configured as Third-Party APIs in Momen and called inside the Custom Code nodes of Book BayHaul Pickup and Resubmit BayHaul Request using context.callThirdPartyApi().&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nominatim.openstreetmap.org/search" rel="noopener noreferrer"&gt;Nominatim (OpenStreetMap)&lt;/a&gt; geocodes the company's warehouse address (1400 Fairway Drive, San Leandro, CA 94577) to coordinates. The endpoint requires a descriptive User-Agent header on every request — requests without one are rejected. The warehouse address is fixed and known in advance, so this geocode call is straightforward: query once, read latitude and longitude from the first result.&lt;/p&gt;

&lt;p&gt;OSRM (Open Source Routing Machine) calculates the driving route between the warehouse coordinates and the customer's pickup coordinates. The public demo server at router.project-osrm.org takes a pair of {longitude},{latitude} coordinate strings and returns a route object with distance (meters) and duration (seconds). The Custom Code node converts those to miles and minutes and writes them to the request record.&lt;/p&gt;

&lt;p&gt;Distance is calculated at booking time and written once. Accept and Decline never touch it. If a customer resubmits with a different address, the calculation runs again for the new coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Frontend with Claude Code
&lt;/h2&gt;

&lt;p&gt;With the Momen backend in place, the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt; passed the project's full context — data model, GraphQL API schema, Actionflow IDs, role configuration — to Claude Code. The frontend was built from natural language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use Momen nocode plugin to build the frontend of this project: {project_url} with React and Vite, calling into the backend already built in this project.

Use light green with soft pastel tones as the primary color palette. Keep the interface simple and clean, avoiding a typical SaaS-style look. Add junk removal-related visual elements throughout so the interface feels engaging and relevant to the subject.

Don't make the homepage just a login screen. Give it a clear value proposition with supporting copy explaining what the app does, along with junk removal-related visual elements, so it feels warm and inviting rather than a bare sign-in form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two Ways to Build Something Like This
&lt;/h2&gt;

&lt;p&gt;This project used Momen's in-product AI Copilot to configure the backend, then the plugin to pass that context to Claude Code for the frontend. Two paths are available:&lt;/p&gt;

&lt;p&gt;Momen AI Copilot — describe your app in natural language directly inside the Momen editor. The in-product Copilot configures your data model, Actionflows, and UI without switching tools. See &lt;a href="https://momen.app/blogs/meet-your-nocode-ai-copilot-build-apps-by-chatting-in-momen/" rel="noopener noreferrer"&gt;Meet Your Nocode AI Copilot — Build Apps by Chatting in Momen&lt;/a&gt; for how this works.&lt;/p&gt;

&lt;p&gt;Momen plugin + Claude Code — build or describe the Momen backend, then install the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt; in your AI coding agent. The plugin passes your project's full context so you can describe the frontend in natural language and have it wired to the correct endpoints automatically. See the &lt;a href="https://momen.app/blogs/momen-claude-code-complete-setup-guide/" rel="noopener noreferrer"&gt;complete setup guide for Momen + Claude Code&lt;/a&gt; to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Long Does This Take
&lt;/h2&gt;

&lt;p&gt;Setting up this Momen backend from scratch — five tables, one AI agent, seven Actionflows, two third-party API configurations, and role-based permissions — takes about 30 minutes to an hour with AI Copilot handling configuration from natural language prompts. Building the frontend with Claude Code, once the project context is loaded, takes around 30–45 minutes for a working version covering both the customer flow and the staff dashboard.&lt;/p&gt;

&lt;p&gt;The minimum plan for this project is &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Basic at $39/month&lt;/a&gt; — it covers unlimited AI agents, Actionflows, and third-party API integrations, plus a custom domain.&lt;/p&gt;

&lt;p&gt;Add-ons layer on top based on actual usage. For this app, the three that grow with volume are AI Points (the multimodal quoting agent spends points on every photo-based estimate), object storage (customer photos accumulate over time), and outbound data transfer (photos served to the staff dashboard). The calculator sizes its estimate around 4,500 cumulative customers and 225 operating days of data — at that scale, AI Points alone account for $70/month (42M points across ongoing usage scenarios), and storage and transfer add another ~$14. The total comes to approximately $123/month. At a smaller early-stage footprint, the add-on costs would be significantly lower. You can plug in your own usage assumptions and see the line-by-line breakdown in &lt;a href="https://momen.app/calculator/cost_to_build/bay_area_junk_removal_124" rel="noopener noreferrer"&gt;Momen's pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both Nominatim and OSRM are free public services with no usage fees. Frontend hosting on Vercel is free for most early-stage projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It and Clone the Project
&lt;/h2&gt;

&lt;p&gt;Submit a request with a description and photo to see the AI quote flow, then book a pickup with a real Bay Area address to see the distance calculation run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bayhaul-junk-removal.vercel.app/" rel="noopener noreferrer"&gt;Try the live app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://editor.momen.app/tool/k5PBKyGyP5E/WEB?code=MnxxRJDSe73k0&amp;amp;ref=6256176" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>ai</category>
      <category>junk</category>
    </item>
    <item>
      <title>AI Meal Planner: Built with Momen and Claude Code</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:09:11 +0000</pubDate>
      <link>https://dev.to/momen_hq/ai-meal-planner-built-with-momen-and-claude-code-4k2k</link>
      <guid>https://dev.to/momen_hq/ai-meal-planner-built-with-momen-and-claude-code-4k2k</guid>
      <description>&lt;p&gt;Most meal planning tools give you AI calorie estimates. AI Meal Planner gives you real ones: users enter body stats and food preferences, the app generates a 3-day plan, and every calorie and macro number comes from a live USDA FoodData Central lookup — not a model guess. The plan uses at most 12 shared ingredients across all nine meals, so the shopping list stays short.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatics.mylandingpages.co%2Fstatic%2Faaai2zwevf3xuzqz%2Fimage%2Feedbbd4fce3940fc9f264a6cf240840a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatics.mylandingpages.co%2Fstatic%2Faaai2zwevf3xuzqz%2Fimage%2Feedbbd4fce3940fc9f264a6cf240840a.webp" width="719" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The backend is built in Momen: two AI agents, one async Actionflow, and a USDA API integration that runs as a deterministic backend step rather than an AI tool call — cutting total generation time from potential minutes to under 90 seconds. The frontend is a React + Vite app built with Claude Code using the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://meal-planner-inky-two-45.vercel.app/" rel="noopener noreferrer"&gt;Try the live app&lt;/a&gt; · &lt;a href="https://editor.momen.app/tool/Y52Xz7J794K/WEB?code=8lKCnuPO8tF8E&amp;amp;ref=6256176" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Does
&lt;/h2&gt;

&lt;p&gt;A user signs up with email and password (email verification required), then fills out a 7-field form: gender, age, height, weight, activity level, goal, and a set of food preference and restriction tags. Tags cover diet styles (High protein, Low carb, Mediterranean, Keto, Vegetarian), restrictions (No dairy, Gluten-free, Low sodium), and allergies (Nut, Shellfish, Egg, Soy). All inputs are fixed choices — no free-text fields.&lt;/p&gt;

&lt;p&gt;Submitting the form triggers an async generation flow in the background. The frontend subscribes over WebSocket and shows a loading state until generation completes, then opens the results automatically.&lt;/p&gt;

&lt;p&gt;The results page shows three days of meals. Each meal has a name, a short description, an ingredient list with gram amounts, and its real calorie and macro totals. A shopping list at the bottom consolidates every ingredient across the plan into a single receipt-style view with total grams needed. Users can generate a new plan at any time, and every plan is saved to their history.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Backend Is Built
&lt;/h2&gt;

&lt;p&gt;The Momen backend covers everything server-side: the data model, the two AI agents, the Actionflow that sequences the full generation pipeline, and the USDA API integration. All of it is configured in the Momen editor — no server code, no deployment step. The backend was set up by describing the full requirements in natural language using the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's my project {project url}. Build only the backend for a web app called "AI Meal Planner" — an app that creates a personalized 3-day meal plan for people who cook their own food at home in the US. This is backend only: set up the data model, the account system, and the logic described below. Do not build any frontend pages or UI — a separate frontend will be built independently and will call into this backend.

Account
Users sign up and log in with email and password. Once logged in, they can save meal plans to their history, look up any past plan, or delete one.

Input data
The backend receives this data for a single request:
- Gender — fixed choices: Male, Female
- Age (number)
- Height in cm (number)
- Weight in kg (number)
- Activity level — fixed choices only: "Rarely active", "Somewhat active", "Very active"
- Goal — fixed choices only: "Lose weight", "Build muscle", "Maintain weight"
- Preferences and restrictions — a list of tags picked from this fixed set (no free text / custom tags):

| Category | Options |
|---|---|
| Taste / diet style | High protein, Low carb, Mediterranean style, Keto, Vegetarian |
| Restrictions | No dairy, Gluten-free, Low sodium |
| Allergies | Nut allergy, Shellfish allergy, Egg allergy, Soy allergy |

Treat "Restrictions" and "Allergies" tags as ingredients to completely avoid, and "Taste / diet style" tags as style preferences to lean into.

What the backend should do with this data
1. Calculate how many calories this person should eat per day, based on their body stats, activity level, and goal, using the standard BMR → TDEE → goal-adjustment method. Also calculate a target range for protein, fat, and carbs in grams per day.
2. Save these targets to the user's profile. If the user already has a saved profile, update it; otherwise create one.
3. Create a full 3-day meal plan, 3 meals a day (breakfast, lunch, dinner) — 9 meals total. Meals must be American/Western home-cooking style. Use at most 12 distinct ingredients across the entire 3-day plan — pick a small core set of proteins, vegetables, grains, and staples, and build all 9 meals by recombining and reseasoning only those ingredients. Each meal needs a name, a short description, and a list of ingredients drawn only from that set, each with an amount in grams. None of the meals may contain anything from the user's selected restriction or allergy tags. Do not estimate or output any calorie or macro numbers at this step — only ingredient names, grams, meal names, and descriptions.
4. Add up the ingredients across all 9 meals into a shopping list: one entry per distinct ingredient with its total grams needed for the 3 days. This list must contain at most 12 items.
5. For each distinct ingredient in the shopping list, look up its real nutrition per 100g using the USDA FoodData Central food search endpoint below. Look up each distinct ingredient exactly once, and reuse that result for every meal that uses it — do not call the API again for an ingredient already looked up in this run. This lookup must run as a fixed backend step, not something a model decides to do during generation.
6. For every meal, add up the real nutrition contributed by each of its ingredients (scale each ingredient's per-100g values by grams ÷ 100), and store the meal's total calories, protein, fat, and carbs.

What the backend should store and expose
- The user's daily calorie target and macro breakdown
- The full 3-day plan, organized by day and by meal, with each meal's name, description, ingredients with grams, and its real calorie/protein/fat/carb totals
- The shopping list: ingredient name + total grams needed for the 3 days
- All of this saved under the user's history, retrievable later, with the ability to trigger a fresh regeneration of a new 3-day plan

Nutrition API to use

USDA FoodData Central — the official US government food nutrition database, free with no paywall.

Docs: https://fdc.nal.usda.gov/api-guide

API key: {YOUR_USDA_API_KEY}

Use one endpoint only — do not call any other USDA endpoint:

`GET https://api.nal.usda.gov/fdc/v1/foods/search?api_key={YOUR_USDA_API_KEY}&amp;amp;query={ingredient name}&amp;amp;pageSize=1&amp;amp;dataType=Foundation,SR Legacy`

Take the first food in the returned `foods` list. Its `foodNutrients` list already contains the nutrition data — read the per-100g values directly from it, matching entries by name: "Energy" with unit "KCAL" is calories, "Protein" is protein, "Total lipid (fat)" is fat, "Carbohydrate, by difference" is carbs. Do not call a second endpoint to get nutrition detail.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;The data model is a hierarchy with the user's profile at the top and individual ingredients at the bottom:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account — Momen's built-in authentication table. Handles email/password signup and login, including verification codes.&lt;/li&gt;
&lt;li&gt;user_profile — One profile per account, storing body stats (gender, age, height, weight, activity level, goal, preference tags) alongside the calculated daily targets (calories, protein, fat, carbs in grams). Updated on each generation.&lt;/li&gt;
&lt;li&gt;meal_plan — One record per generation run, linked to the account. Stores plan status.&lt;/li&gt;
&lt;li&gt;day_plan — Three records per meal plan, one per day (day_number 1–3).&lt;/li&gt;
&lt;li&gt;meal — Nine records per meal plan (breakfast, lunch, dinner across three days). Stores meal name, description, type, and its real calorie and macro totals.&lt;/li&gt;
&lt;li&gt;meal_ingredient — One record per ingredient per meal, storing ingredient name and gram amount.&lt;/li&gt;
&lt;li&gt;shopping_list_item — One record per distinct ingredient, storing the total grams needed across the full 3-day plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Two AI Agents
&lt;/h3&gt;

&lt;p&gt;Two &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration/" rel="noopener noreferrer"&gt;AI Agents&lt;/a&gt; handle the reasoning parts of generation. Each does one job.&lt;/p&gt;

&lt;p&gt;calorie_macro_calculator — Takes gender, age, height, weight, activity level, and goal. Uses the Mifflin-St Jeor formula to calculate BMR, applies an activity multiplier to get TDEE, then adjusts for goal (−20% to lose weight, +12% to build muscle, unchanged to maintain). Calculates protein, fat, and carb targets in grams from those calorie numbers. Returns four values: daily_calories, protein_g, fat_g, carb_g. This agent does no tool calls — it's a single inference step with a deterministic formula baked into the prompt.&lt;/p&gt;

&lt;p&gt;meal_plan_generator — Takes the calorie and macro targets from the first agent plus the user's preference and restriction tags. Returns a full 3-day plan: nine meals with names, descriptions, and per-ingredient gram amounts, plus a shopping list of distinct ingredients and their total grams across the plan. Crucially, this agent outputs no nutrition numbers — it only plans meals and quantities. Real nutrition data comes from the USDA API in the next step, not from model estimates. The agent is constrained to use at most 12 distinct ingredients across the entire plan, keeping the shopping list short and practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Logic: generate_meal_plan
&lt;/h3&gt;

&lt;p&gt;One &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows/" rel="noopener noreferrer"&gt;Actionflow&lt;/a&gt;, generate_meal_plan, sequences the entire pipeline. It runs asynchronously — the frontend subscribes to its result over WebSocket rather than waiting for a synchronous response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvyo52rr31miivnjebdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvyo52rr31miivnjebdh.png" width="618" height="1128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The node sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input — receives all 7 form fields (gender, age, height_cm, weight_kg, activity_level, goal, preference_tags)&lt;/li&gt;
&lt;li&gt;Get current user id — resolves the authenticated account&lt;/li&gt;
&lt;li&gt;Calculate calorie and macro targets — calls calorie_macro_calculator&lt;/li&gt;
&lt;li&gt;Query existing profile — checks whether a user_profile record exists for this account&lt;/li&gt;
&lt;li&gt;Branch: profile exists or not — inserts a new profile or updates the existing one with the latest stats and calculated targets&lt;/li&gt;
&lt;li&gt;Generate 3-day meal plan — calls meal_plan_generator with the calorie targets and preference tags&lt;/li&gt;
&lt;li&gt;Create meal plan record — inserts the parent meal_plan record&lt;/li&gt;
&lt;li&gt;For each shopping list ingredient — iterates over the up-to-12 distinct ingredients in the plan:Calls the USDA FoodData Central search API and caches the resultInserts a shopping_list_item record with ingredient name and total grams&lt;/li&gt;
&lt;li&gt;For each day → for each meal → for each ingredient — nested loops that:Insert day_plan, meal, and meal_ingredient recordsCompute each ingredient's nutrition contribution (grams ÷ 100 × per-100g values from the cache)Accumulate the result into the meal's calorie and macro totals&lt;/li&gt;
&lt;li&gt;Output — signals completion to the WebSocket subscription&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The USDA lookup runs once per distinct ingredient and caches the result. Every meal that uses that ingredient reads from the cache rather than making another API call. This is the key performance decision — see the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  External API: USDA FoodData Central
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://fdc.nal.usda.gov/api-guide" rel="noopener noreferrer"&gt;USDA FoodData Central API&lt;/a&gt; provides the real nutrition data. One endpoint is used: GET /v1/foods/search, queried with the ingredient name, pageSize=1, and dataType=Foundation,SR Legacy to target raw and unprocessed food data rather than branded products.&lt;/p&gt;

&lt;p&gt;The API key is stored in the Momen backend and never exposed to the frontend. The search call happens inside a Custom Code node in the Actionflow loop — not inside the AI agent.&lt;/p&gt;

&lt;p&gt;This placement matters. The alternative would be to give the meal_plan_generator agent a tool that calls USDA during inference. In practice, each AI tool call triggers a full model inference round — measured at roughly 35–40 seconds per call. With up to 12 ingredients, that would make nutrition lookup alone take 6–10 minutes per generation. By running the USDA search as a deterministic backend step with caching, each real HTTP request takes 1–2 seconds, and all 12 ingredients can be resolved in 15–20 seconds total. The full generation pipeline — two AI inference rounds plus all database writes and API calls — completes in about 60–90 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Frontend with Claude Code
&lt;/h2&gt;

&lt;p&gt;With the Momen backend in place, the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt; was used to pass the project's full context — data model, GraphQL API schema, Actionflow IDs — to Claude Code. The frontend was generated from natural language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build the frontend for this app with React and Vite, calling into the backend already built.

Use warm tones with soft pastel tones as the primary color palette. Keep the interface simple and clean, avoiding a typical SaaS-style look. Add food-related visual elements throughout so the interface feels engaging and relevant to the subject.

Don't make the homepage just a login screen. Give it a clear value proposition with supporting copy explaining what the app does, along with food-related visual elements, so it feels warm and inviting rather than a bare sign-in form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code generated the full component structure: the homepage with value proposition, the 7-field form, the WebSocket-powered loading state, the results page with day tabs and meal cards, the receipt-style shopping list, and the history page. The Momen GraphQL endpoint, mutations, and subscription were wired in automatically from the plugin context.&lt;/p&gt;

&lt;p&gt;The design uses Fraunces (serif display), Karla (body), and IBM Plex Mono (nutrition numbers). Each meal card has a calorie badge in the corner. The shopping list renders as a receipt with ingredient names, dot leaders, and gram totals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Ways to Build Something Like This
&lt;/h2&gt;

&lt;p&gt;This project used a Momen backend connected to Claude Code via the plugin. If you're starting from scratch, there are two paths:&lt;/p&gt;

&lt;p&gt;Momen AI Copilot — describe your app in natural language directly inside the Momen editor. The in-product Copilot configures your data model, Actionflows, and UI without switching tools. See &lt;a href="https://momen.app/blogs/meet-your-nocode-ai-copilot-build-apps-by-chatting-in-momen/" rel="noopener noreferrer"&gt;Meet Your Nocode AI Copilot — Build Apps by Chatting in Momen&lt;/a&gt; for how this works.&lt;/p&gt;

&lt;p&gt;Momen plugin + Claude Code / Codex / Cursor — build the Momen backend in the editor, then install the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt; in your AI coding agent. The plugin passes your project's full context to the agent so you can describe the frontend in natural language and have it wired to the right endpoints automatically. See the &lt;a href="https://momen.app/blogs/momen-claude-code-complete-setup-guide/" rel="noopener noreferrer"&gt;complete setup guide for Momen + Claude Code&lt;/a&gt; to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Long Does This Take
&lt;/h2&gt;

&lt;p&gt;Setting up this Momen backend from scratch — data model, two AI agents, one Actionflow with 29 nodes, USDA API configuration, and permissions — takes about 1–2 hours with the plugin handling configuration from natural language prompts. Building the frontend with Claude Code, once the backend context is loaded, takes around 30–45 minutes for a working version.&lt;/p&gt;

&lt;p&gt;The minimum plan for this project is &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Basic at $39/month&lt;/a&gt;. At the scale the calculator assumes — 500 registered home cooks growing gradually over time — all usage falls within Basic's included allowances: the database stays around 20 MB (against a 200 MB limit), no photos means no object storage, no outbound data transfer, and AI Points for plan generation fit within the included 1 million per month. No add-ons are needed. You can plug in your own usage assumptions in &lt;a href="https://momen.app/calculator/cost_to_build/ai_meal_planner_125" rel="noopener noreferrer"&gt;Momen's pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The USDA FoodData Central API is free with no monthly fee. Frontend hosting on Vercel is free for most early-stage projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It and Clone the Project
&lt;/h2&gt;

&lt;p&gt;New users get access on signup. Generate a plan with your own stats to see the full pipeline — form, loading state, real nutrition data per meal, and the consolidated shopping list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://meal-planner-inky-two-45.vercel.app/" rel="noopener noreferrer"&gt;Try the live app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://editor.momen.app/tool/Y52Xz7J794K/WEB?code=8lKCnuPO8tF8E&amp;amp;ref=6256176" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>ai</category>
      <category>meal</category>
    </item>
    <item>
      <title>Mikey Gave His Design Studio Site a Way to Quote and Get Paid Automatically</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:08:59 +0000</pubDate>
      <link>https://dev.to/momen_hq/mikey-gave-his-design-studio-site-a-way-to-quote-and-get-paid-automatically-2h4f</link>
      <guid>https://dev.to/momen_hq/mikey-gave-his-design-studio-site-a-way-to-quote-and-get-paid-automatically-2h4f</guid>
      <description>&lt;p&gt;Mikey, who builds tools for freelancers and service businesses, added a quote widget to his design studio's website: a visitor picks a service, a scope, and a timeline, describes the project, and gets back an AI-calculated estimate and a friendly written summary, with a Stripe deposit collected on the spot. The problem it replaces is the back-and-forth of quoting by email and phone that anyone running a freelance or agency business already knows.&lt;/p&gt;

&lt;p&gt;Rather than rebuilding his site, he used Momen purely as a backend-as-a-service behind the site he already had. The &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;, run from Claude Code's CLI inside his existing project folder, builds the backend directly from a prompt while the studio site's own frontend stays exactly as it was, just with a new widget added to call it.&lt;/p&gt;

&lt;p&gt;The finished backend is public: &lt;a href="https://editor.momen.app/tool/rmLyJ0Z0LY8/WEB?code=RuokuHsPhHPFb&amp;amp;ref=4031205" rel="noopener noreferrer"&gt;open it in the Momen editor / clone the project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/3fEuITfx3n8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What the widget does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pick a service, scope, and timeline — service type, a Small/Medium/Large scope tier, and a Standard/Rush timeline&lt;/li&gt;
&lt;li&gt;Describe the project in free text, alongside a name and email&lt;/li&gt;
&lt;li&gt;Get an AI-calculated estimate and a friendly quote message — a price anchored to the studio's actual pricing rules, written up in plain client-facing language&lt;/li&gt;
&lt;li&gt;Pay a deposit through Stripe on the same screen&lt;/li&gt;
&lt;li&gt;An admin dashboard listing every lead, its scope and timeline, and payment status&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The data model behind the pricing logic
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;service — the studio's offered services, name and description&lt;/li&gt;
&lt;li&gt;pricing_rule — the studio's actual price list: a scope and timeline combination mapped to a price, per service&lt;/li&gt;
&lt;li&gt;lead — one row per quote request: name, email, scope, timeline, project_details, the AI's estimated_price and deposit_amount, the friendly_summary text, and a status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Payments run through Momen's built-in Stripe integration, bound to lead as the order table — the project also carries fz_payment_record, fz_recurring_payment, and fz_refund, which Momen manages once Stripe's keys are saved. See &lt;a href="https://docs.momen.app/docs/actions/guide/payment/payment_stripe" rel="noopener noreferrer"&gt;Momen's Stripe payment guide&lt;/a&gt; for how binding a table as the order table works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two AI agents, chained: one prices, one writes
&lt;/h2&gt;

&lt;p&gt;PricingCalculator (GPT-5.4) takes the service name, scope tier, timeline tier, the authoritative base_price pulled from pricing_rule, and the client's free-text project description, and returns an estimate anchored tightly to that base price — its system prompt tells it to only deviate from the studio's actual pricing rule within a defined range, not invent a number from the description alone.&lt;/p&gt;

&lt;p&gt;FriendlyQuoteWriter (GPT-5.4-mini) takes that output and turns it into the message the client actually reads: a short, warm, plain-prose note that greets the client by name, names the service, states the estimated total and deposit, and reads like an account manager wrote it rather than a script. Splitting the two apart means the pricing logic stays strict and auditable while the tone of the client-facing copy can be iterated on its own. See &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration" rel="noopener noreferrer"&gt;Build AI Agents&lt;/a&gt; for how an agent's inputs and prompt are configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Actionflow ties the quote together, four more handle Stripe
&lt;/h2&gt;

&lt;p&gt;CalculateQuote is the flow behind the "get my estimate" button: it looks up the matching service and pricing_rule rows, runs the pricing agent, feeds that result into the friendly-writer agent, and only then inserts the lead row with both the numeric estimate and the written summary attached. Four more flows — StripePayment, StripeRecurringPaymentManagement, StripeRecurringPaymentDeduction, and StripeRefund — are Momen's standard payment flows, created automatically once the payment module is activated and bound to lead.&lt;/p&gt;

&lt;p&gt;Permissions reflect that the widget is meant for anonymous visitors, not logged-in accounts: the anonymous role can only select from service and lead, with no direct insert — a lead only gets created by CalculateQuote after the pricing agent has actually run, not by a visitor writing to the table directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into an existing site instead of a new one
&lt;/h2&gt;

&lt;p&gt;The build prompt Mikey used was specific about what already existed: the tech stack (his existing frontend), the two agents and what each one does, that Stripe needed to be connected, and — importantly — what not to build, since the frontend wasn't something Claude Code needed to touch. He dropped his existing project folder into Claude Code's working directory and pointed it at the Momen project's editor URL, so the plugin knew which backend to build into without touching the site's existing code.&lt;/p&gt;

&lt;p&gt;Watching the build, the data tables, the two agents, and the Actionflows all appeared in the Momen editor as Claude Code created them — pricing rules, lead records, and the service list were all visible and editable there afterward, which is what let him hand the studio's admin dashboard to a non-technical setup: the leads and their statuses render straight out of the same tables, no separate reporting tool required.&lt;/p&gt;

&lt;p&gt;Building this project on Momen comes to approximately $99/month on the &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Pro plan&lt;/a&gt;, monthly billing. Pro is the tier this project needs specifically because it uses Momen's built-in Stripe Payment module — resource-wise it's a light project, using well under its included database storage and AI points. You can estimate the cost of your own project using &lt;a href="https://momen.app/calculator/cost_to_build/agency_project_quote_calculator_129" rel="noopener noreferrer"&gt;Momen's pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mikey walks through the whole build — the prompt, watching Claude Code create the schema and agents live in the Momen editor, activating Stripe, and a full end-to-end test with a dummy card — in &lt;a href="https://www.youtube.com/watch?v=3fEuITfx3n8" rel="noopener noreferrer"&gt;his video&lt;/a&gt;. &lt;a href="https://editor.momen.app/tool/rmLyJ0Z0LY8/WEB?code=RuokuHsPhHPFb&amp;amp;ref=4031205" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>baas</category>
      <category>claude</category>
    </item>
    <item>
      <title>Rajeevdaz Built an App That Scores Your Resume Against Any Job Before You Apply</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:08:48 +0000</pubDate>
      <link>https://dev.to/momen_hq/rajeevdaz-built-an-app-that-scores-your-resume-against-any-job-before-you-apply-3c43</link>
      <guid>https://dev.to/momen_hq/rajeevdaz-built-an-app-that-scores-your-resume-against-any-job-before-you-apply-3c43</guid>
      <description>&lt;p&gt;Rajeevdaz, who covers AI tools for non-technical builders, built ResumeFit: upload a resume and a job description, and an AI agent scores the match, lists missing keywords and skills, and emails the report. The app has real accounts, a free-to-paid upgrade path through Stripe, and outbound email — the pieces that usually stall a non-technical builder once the frontend looks done.&lt;/p&gt;

&lt;p&gt;His split: Momen holds the database, the AI agent, the Stripe and email integrations, and the logic that ties them together. Claude Code, connected to that backend through the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;, writes the React and Vite frontend against it. He didn't touch the backend by hand — he wrote a structured prompt describing the app and the plugin let Claude Code create the tables, the agent, and the Actionflows directly inside the Momen project.&lt;/p&gt;

&lt;p&gt;The finished backend is public: &lt;a href="https://editor.momen.app/tool/NyndxPeP5O8/WEB?code=oNKXchLRsR3AG&amp;amp;ref=3804078" rel="noopener noreferrer"&gt;open it in the Momen editor / clone the project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the app does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upload a resume and a job description — a resume file plus a job title and job description text&lt;/li&gt;
&lt;li&gt;Get an ATS match score — an AI agent compares the two and returns a score out of 100&lt;/li&gt;
&lt;li&gt;See strengths, weaknesses, and missing keywords — the same agent returns a structured breakdown, not just a number&lt;/li&gt;
&lt;li&gt;Get the report by email — each completed analysis is also sent to the account's inbox&lt;/li&gt;
&lt;li&gt;Free tier with a paid upgrade — a fixed number of free checks, then a Stripe subscription for unlimited checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The data model: five app tables plus the built-in account
&lt;/h2&gt;

&lt;p&gt;Momen's built-in account table was extended with free_checks_used, is_pro, and stripe_customer_id — the three fields the free-to-paid gate runs on. Claude Code added four more tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resume_upload — file_name, the uploaded file, and extracted_text pulled from it&lt;/li&gt;
&lt;li&gt;job_description — title and content, the job posting being matched against&lt;/li&gt;
&lt;li&gt;resume_analysis — ats_score, plus strengths, weaknesses, missing_keywords, missing_skills, and suggestions as JSON, a summary, a recommendation, and an email_sent flag, with foreign keys to the resume, the job description, and the account&lt;/li&gt;
&lt;li&gt;subscription — status, plan, stripe_subscription_id, stripe_customer_id, and current_period_end&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An AI agent that scores the match, not just summarizes it
&lt;/h2&gt;

&lt;p&gt;The agent, resume_analyzer, runs on GPT-5.4 and takes two typed inputs: resume_text and job_description_text. Its system prompt instructs it to act as an ATS analyst and senior technical recruiter, scoring the match from 0–100 based on keyword overlap, required-skills coverage, and relevant experience, then returning the score alongside strengths, weaknesses, missing keywords, missing skills, and concrete suggestions — the same fields that land in the resume_analysis table. See &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration" rel="noopener noreferrer"&gt;Build AI Agents&lt;/a&gt; for how an agent like this is configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Actionflows: gate, checkout, and confirm
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;analyze_resume — checks the account's free-check status first. If checks remain, it runs the AI agent, increments the account's free_checks_used, saves the full analysis, and marks it emailed. If the free allowance is used up and the account isn't on the paid plan, the flow branches to a blocked path instead of calling the agent.&lt;/li&gt;
&lt;li&gt;create_checkout — starts a Stripe subscription checkout session and returns the session URL the frontend redirects to.&lt;/li&gt;
&lt;li&gt;confirm_subscription — looks up the account's completed Stripe session after checkout and activates the subscription, flipping is_pro and recording the Stripe subscription and customer IDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Momen's own &lt;a href="https://docs.momen.app/docs/actions/guide/payment/payment_stripe" rel="noopener noreferrer"&gt;Payment guide&lt;/a&gt; covers how a Stripe integration like this is wired into an Actionflow. Outbound email runs through a Resend integration configured as a &lt;a href="https://docs.momen.app/docs/actions/guide/api_integration" rel="noopener noreferrer"&gt;third-party API&lt;/a&gt; that the analyze_resume flow calls after saving the report.&lt;/p&gt;

&lt;p&gt;Authentication is Momen's built-in account system, with username, phone, and email sign-in all enabled — the video walks through creating an account with just an email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the frontend with Claude Code
&lt;/h2&gt;

&lt;p&gt;Rajeevdaz's advice here is specific: don't prompt with "build me an app." He wrote a structured prompt — the app's purpose, that Momen is the backend and a custom React and Vite app is the frontend, what the backend needs to handle (auth, AI resume analysis, free-usage tracking, Stripe subscriptions, email), and the user flow from login to running an analysis — and pasted the Momen project's editor link into it so Claude Code knew which backend to build against.&lt;/p&gt;

&lt;p&gt;With the &lt;a href="https://momen.app/blogs/momen-plugin-is-here-let-ai-build-your-backend-and-vibe-code-your-frontend/" rel="noopener noreferrer"&gt;Momen plugin&lt;/a&gt; installed in Claude Code, that one prompt was enough for it to read the backend context, ask for authentication with the Momen account, and then build out the database, the AI agent, and the three Actionflows in a single pass — followed by the frontend against the live API. The &lt;a href="https://momen.app/blogs/momen-claude-code-complete-setup-guide/" rel="noopener noreferrer"&gt;step-by-step Claude Code setup guide&lt;/a&gt; covers the same install-and-connect flow.&lt;/p&gt;

&lt;p&gt;The first working build used a generic default theme; Rajeevdaz then pulled a reference layout from a UI design site and had Claude Code restyle the frontend to match, in the same session that also wired up the Stripe upgrade button that hadn't been connected yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs to run
&lt;/h2&gt;

&lt;p&gt;Rajeevdaz picked Basic when he first created the project, before the subscription flow existed. Once the app has real subscription payments in it, though — which ResumeFit does — Momen's &lt;a href="https://momen.app/calculator/cost_to_build/ai_resume_job_matcher_130" rel="noopener noreferrer"&gt;pricing calculator&lt;/a&gt; puts the minimum viable plan at &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Pro&lt;/a&gt;: subscription payments are gated to that tier. Sized against a small early-stage launch (1,000 registered users, the free-to-paid usage this app actually has), that comes out to about $111.92/month — the $99 Pro base plus a small object storage add-on and an AI points add-on, both driven by usage rather than by a feature unlock. You can estimate your own project's cost the same way with &lt;a href="https://momen.app/calculator/cost_to_build/ai_resume_job_matcher_130" rel="noopener noreferrer"&gt;Momen's pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rajeevdaz walks through the whole build — the structured prompt, Claude Code provisioning the Momen backend, the UI restyle, and testing the free-to-paid upgrade end to end — in &lt;a href="https://www.youtube.com/watch?v=PWK_CKgVkYs" rel="noopener noreferrer"&gt;his video&lt;/a&gt;. &lt;a href="https://editor.momen.app/tool/NyndxPeP5O8/WEB?code=oNKXchLRsR3AG&amp;amp;ref=3804078" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>resume</category>
      <category>score</category>
    </item>
    <item>
      <title>MakerThrive Built a Tool That Tells Founders Exactly What's Wrong With Their Landing Page</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:08:36 +0000</pubDate>
      <link>https://dev.to/momen_hq/makerthrive-built-a-tool-that-tells-founders-exactly-whats-wrong-with-their-landing-page-299i</link>
      <guid>https://dev.to/momen_hq/makerthrive-built-a-tool-that-tells-founders-exactly-whats-wrong-with-their-landing-page-299i</guid>
      <description>&lt;p&gt;MakerThrive, who builds and reviews AI tools, built a landing page audit tool: upload a screenshot, pick a goal like "get signups" or "sell a product," and an AI agent scores the page on value proposition clarity, CTA visibility, and trust signals, then emails back the top three fixes. She wrote none of the backend by hand.&lt;/p&gt;

&lt;p&gt;The split she used is Momen for the backend and Claude Code for both the backend logic and the frontend. The &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt; runs inside Claude Code, Codex, or Cursor, and lets the agent build directly inside a Momen project — tables, AI, Actionflows, permissions — instead of the builder configuring any of it by hand in the editor. She started from a completely empty Momen project and described what she wanted in a single prompt that included that project's editor URL, so Claude Code knew exactly which backend to build into.&lt;/p&gt;

&lt;p&gt;The finished backend is public: &lt;a href="https://editor.momen.app/tool/8bKnRgegozr/WEB?code=nWq63oJeauL0j&amp;amp;ref=5713300" rel="noopener noreferrer"&gt;open it in the Momen editor / clone the project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/z9DoWhHip6w"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upload a screenshot and pick a goal — "get signups," "sell a product," or similar&lt;/li&gt;
&lt;li&gt;Get an AI score across three dimensions — value proposition clarity, CTA visibility, and trust signals, each with a numeric score and written reasoning&lt;/li&gt;
&lt;li&gt;Get a prioritized fix list by email — the top issues and a ranked action list land in the requester's inbox&lt;/li&gt;
&lt;li&gt;A free first audit, then paid credits — one audit per account before a credit purchase is required&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The data model Claude Code proposed and built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;audits — the core record: the uploaded image, the goal, an email to send results to, a status, and the AI's output: overall_score, plus a score and written reasoning for value_prop, cta, and trust, a top_issues list, and an action_list&lt;/li&gt;
&lt;li&gt;account — Momen's built-in user table, extended with credit_balance and free_audit_used&lt;/li&gt;
&lt;li&gt;credit_purchase — amount, status, and credits_granted per order&lt;/li&gt;
&lt;li&gt;promo_code — code, discount_percent, and active, checked when someone applies a discount at checkout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Payments run through Momen's own built-in Stripe integration rather than a custom API call — the project also has fz_payment_record, fz_recurring_payment, and fz_refund tables, which Momen manages automatically once Stripe is connected. See &lt;a href="https://docs.momen.app/docs/actions/guide/payment/payment_stripe" rel="noopener noreferrer"&gt;Momen's Stripe payment guide&lt;/a&gt; for how that connection is configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  An AI agent that scores, not just describes
&lt;/h2&gt;

&lt;p&gt;The agent, LandingPageAuditor, runs on GPT-5.4 and takes the screenshot and the stated goal as input. Its system prompt casts it as a blunt conversion-rate-optimization consultant: look at the image directly — layout, visual hierarchy, headline and copy, imagery, button placement and color, forms — judge it the way a first-time visitor would in the first few seconds, and score honestly on a 0–100 scale per dimension rather than defaulting to safe, inflated numbers. That's the difference between an agent returning a paragraph of feedback and one returning the structured value_prop_score / cta_score / trust_score fields the audits table actually stores. See &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration" rel="noopener noreferrer"&gt;Build AI Agents&lt;/a&gt; for how an agent's input, prompt, and structured output are configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the logic actually runs
&lt;/h2&gt;

&lt;p&gt;A database trigger on audits insert fires RunAudit, which calls the AI agent, saves the results back to that row, and sends the email through a Resend &lt;a href="https://docs.momen.app/docs/actions/guide/api_integration" rel="noopener noreferrer"&gt;third-party API&lt;/a&gt; integration. A separate SubmitAudit flow handles eligibility first — checking whether the account still has its free audit or has a credit to spend, decrementing the balance only if a credit was actually available, and inserting the audit row only after that check passes. CreateCreditOrder resolves any promo code before creating a paid or free credit order, and four more flows (StripePayment, StripeRecurringPaymentManagement, StripeRecurringPaymentDeduction, StripeRefund) handle the Stripe side automatically as part of Momen's built-in payment integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permission pass Claude Code made on its own
&lt;/h2&gt;

&lt;p&gt;This is the part MakerThrive slows down on in the video, and it holds up against the project's actual configuration: neither the logged-in role nor the anonymous role has insert, update, or delete permission on audits — only select. The account table has no direct read or write access for either role at all. In practice, that means a credit balance can't be edited from the browser and an audit record can't be created except by the server-side flow that already checked eligibility — the paywall can't be skipped by calling the database directly. &lt;a href="https://docs.momen.app/docs/publish_operate/permissions" rel="noopener noreferrer"&gt;Momen's permissions guide&lt;/a&gt; covers how role, table, and column-level rules like these are structured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the frontend as a separate, ordinary codebase
&lt;/h2&gt;

&lt;p&gt;For the frontend, MakerThrive was specific in her prompt: build it in React and Vite, and connect it to the existing Momen backend through the connector — not Momen's own UI builder. That instruction keeps the two sides genuinely separate: the frontend is regular code she can read, edit, and deploy anywhere, while the backend stays visual and inspectable in the Momen editor. Watching a submission live, she shows both sides at once — a new row landing in the audits table, the Actionflow firing, and the row updating with a score a few seconds later, followed by the email.&lt;/p&gt;

&lt;p&gt;Building this project on Momen comes to approximately $131.92/month on the &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Pro plan&lt;/a&gt; — Pro is the tier this project needs because it collects payment for credits, not because of resource usage (database storage sits at a fraction of the Pro allowance; the AI points and object storage add-ons that push the total past $99 are usage-driven, not feature-gated). You can estimate the cost of your own project using &lt;a href="https://momen.app/calculator/cost_to_build/landing_page_audit_tool_131" rel="noopener noreferrer"&gt;Momen's pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;MakerThrive walks through the entire build — the empty project, the single prompt, watching the database and Actionflow appear live in the editor, and the permission fix Claude Code made unprompted — in &lt;a href="https://www.youtube.com/watch?v=z9DoWhHip6w" rel="noopener noreferrer"&gt;her video&lt;/a&gt;. &lt;a href="https://editor.momen.app/tool/8bKnRgegozr/WEB?code=nWq63oJeauL0j&amp;amp;ref=5713300" rel="noopener noreferrer"&gt;Open in Momen editor / Clone project&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>landing</category>
      <category>page</category>
    </item>
    <item>
      <title>Nano Banana 2 Lite Is Live in Momen: 1K Image Generation at a Quarter of the Cost</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:08:24 +0000</pubDate>
      <link>https://dev.to/momen_hq/nano-banana-2-lite-is-live-in-momen-1k-image-generation-at-a-quarter-of-the-cost-3i97</link>
      <guid>https://dev.to/momen_hq/nano-banana-2-lite-is-live-in-momen-1k-image-generation-at-a-quarter-of-the-cost-3i97</guid>
      <description>&lt;p&gt;Nano Banana 2 Lite — listed in Momen as Gemini-3.1-flash-lite-image — is now in Momen's model list. You select it inside an AI agent and wire that agent into your app's backend logic. No API key, no provider account.&lt;/p&gt;

&lt;p&gt;The reason to care is cost per image. A 1K image on this model costs exactly half of Gemini-3.1-flash-image-preview and exactly a quarter of Gemini-3-pro-image-preview — roughly five cents at top-up rates, worked out in full below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Gemini-3.1-flash-lite-image
&lt;/h2&gt;

&lt;p&gt;This is the fast, cheap member of the Nano Banana family — the same generation and editing behavior as its siblings, tuned for volume instead of maximum fidelity. It generates an image in &lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/nano-banana-2-lite-and-gemini-omni-flash-available" rel="noopener noreferrer"&gt;as little as four seconds&lt;/a&gt;, with edits running slightly slower than fresh generations. That speed is the point: it makes generated images viable in places you would not have put them before, like a user waiting on a screen rather than a background job.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gemini-3.1-flash-lite-image&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it does&lt;/td&gt;
&lt;td&gt;Text-to-image and image editing, with reference images plus a text prompt in one call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output resolution&lt;/td&gt;
&lt;td&gt;1K (1024×1024) only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aspect ratios&lt;/td&gt;
&lt;td&gt;14 supported ratios, including 1:1, 4:3, 16:9, 9:16, 21:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per output image&lt;/td&gt;
&lt;td&gt;1,120 at 1K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per input image&lt;/td&gt;
&lt;td&gt;1,120 per reference image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token limits&lt;/td&gt;
&lt;td&gt;65,536 input / 4,096 output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured output&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool calling&lt;/td&gt;
&lt;td&gt;Not available in Momen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three limits shape what you build with it.&lt;/p&gt;

&lt;p&gt;1K is the ceiling. &lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite-image" rel="noopener noreferrer"&gt;1024×1024 is the only size it produces&lt;/a&gt; — there is no 2K or 4K variant to switch to. For higher resolution, use Gemini-3.1-flash-image-preview (available at 0.5K, 2K and 4K) or Gemini-3-pro-image-preview (2K and 4K).&lt;/p&gt;

&lt;p&gt;Output is plain text and images, not JSON. Structured output is unavailable, so an agent on this model cannot return a schema you parse downstream. Streaming stays available.&lt;/p&gt;

&lt;p&gt;The agent cannot call tools. It will not reach for an Actionflow, an API, or another agent on its own. Give it a prompt, take back an image.&lt;/p&gt;

&lt;p&gt;Together those make it a generation step rather than a reasoning agent. When you need JSON or tool calls around the image, run a text model in a second agent and chain the two in an Actionflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Gemini-3.1-flash-lite-image in Momen
&lt;/h2&gt;

&lt;p&gt;Momen connects models two ways: built-in models that run on Momen's provider accounts and bill in AI Points, and BYOM, which runs calls on a provider account you own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in model
&lt;/h3&gt;

&lt;p&gt;Gemini-3.1-flash-lite-image is built in, on every plan including Free. Nothing to connect, no key to store, usage billed in AI Points.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the AI tab in the header of your Momen project.&lt;/li&gt;
&lt;li&gt;On the Agent tab, click Add agent (or open an existing one).&lt;/li&gt;
&lt;li&gt;Click the model name at the top of the agent editor to open its settings.&lt;/li&gt;
&lt;li&gt;Click the Model: row and pick Gemini-3.1-flash-lite-image from the list.&lt;/li&gt;
&lt;li&gt;Configure the rest of the agent: temperature, max number of rounds, maximum output tokens, and image processing (Simple or Detailed).&lt;/li&gt;
&lt;li&gt;Drop the agent into an Actionflow to connect its output to a database write, a file upload, or an API call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0w1t5xdorwlx5eqepru3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0w1t5xdorwlx5eqepru3.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  BYOM (Bring Your Own Model)
&lt;/h3&gt;

&lt;p&gt;BYOM runs the same model through your own provider account, so calls bill to that account rather than to your AI Points. It requires Basic or above.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the AI tab, then the Model tab.&lt;/li&gt;
&lt;li&gt;Click Add model and connect the provider account you want the calls to run on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For how agents connect to the rest of your backend, see the &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration/" rel="noopener noreferrer"&gt;AI integration guide&lt;/a&gt;. For a worked image-generation build, see &lt;a href="https://docs.momen.app/tutorial/ai_applications/ai_product_image_generation/" rel="noopener noreferrer"&gt;Build a Product Image Generator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Point conversion
&lt;/h3&gt;

&lt;p&gt;Momen bills AI usage in AI Points, converted from tokens at a rate that differs per model. Every model's rate is listed in the Point conversion table on the AI page, next to your AI Point balance.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3.1-flash-lite-image&lt;/td&gt;
&lt;td&gt;1 token ≈ 0.4300 points&lt;/td&gt;
&lt;td&gt;1 token ≈ 25.7100 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3.1-flash-image-preview&lt;/td&gt;
&lt;td&gt;1 token ≈ 0.2100 points&lt;/td&gt;
&lt;td&gt;1 token ≈ 51.4300 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3-pro-image-preview&lt;/td&gt;
&lt;td&gt;1 token ≈ 1.7100 points&lt;/td&gt;
&lt;td&gt;1 token ≈ 102.8600 points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What one image costs
&lt;/h3&gt;

&lt;p&gt;The formula is the same for every model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;points = (input tokens × input rate) + (output tokens × output rate)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 1K image is billed as 1,120 output tokens. So:&lt;/p&gt;

&lt;p&gt;1,120 × 25.71 ≈ 28,795 points per generated image.&lt;/p&gt;

&lt;p&gt;The prompt is rounding error next to that. A 30-token text prompt adds 30 × 0.43 ≈ 13 points. What does matter is reference images: each one you pass in is billed as 1,120 input tokens, so 1,120 × 0.43 ≈ 482 points per reference image. A text-to-image call costs ~28,800 points; an edit with two reference images costs ~29,760.&lt;/p&gt;

&lt;p&gt;Against the other two Gemini image models, at the same 1K size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Output tokens (1K)&lt;/th&gt;
&lt;th&gt;Arithmetic&lt;/th&gt;
&lt;th&gt;Points per image&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3.1-flash-lite-image&lt;/td&gt;
&lt;td&gt;1,120&lt;/td&gt;
&lt;td&gt;1,120 × 25.71&lt;/td&gt;
&lt;td&gt;≈ 28,795&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3.1-flash-image-preview&lt;/td&gt;
&lt;td&gt;1,120&lt;/td&gt;
&lt;td&gt;1,120 × 51.43&lt;/td&gt;
&lt;td&gt;≈ 57,602&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini-3-pro-image-preview&lt;/td&gt;
&lt;td&gt;1,120&lt;/td&gt;
&lt;td&gt;1,120 × 102.86&lt;/td&gt;
&lt;td&gt;≈ 115,203&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Half the cost of the flash preview model, a quarter of the pro one — the token count is identical at 1K, and the output rates are exactly 2× and 4×. Push those two models to 2K or 4K and they bill more tokens per image, widening the gap further.&lt;/p&gt;

&lt;h3&gt;
  
  
  How far the Free plan goes
&lt;/h3&gt;

&lt;p&gt;The Free plan includes 100,000 AI Points per month.&lt;/p&gt;

&lt;p&gt;100,000 ÷ 28,795 ≈ 3.5 — so three images per month, and the fourth one runs you out.&lt;/p&gt;

&lt;p&gt;That is a plan for trying the model, not for running on it. The same arithmetic for the paid plans:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;AI Points / month&lt;/th&gt;
&lt;th&gt;1K images / month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;≈ 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;≈ 34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;5,000,000&lt;/td&gt;
&lt;td&gt;≈ 173&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Anything with real volume runs on the AI Points add-on rather than on the included allotment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plans and top-ups
&lt;/h3&gt;

&lt;p&gt;AI Points top up at 6,000,000 points for $10/month. That makes one point worth $10 ÷ 6,000,000, so:&lt;/p&gt;

&lt;p&gt;28,795 × ($10 ÷ 6,000,000) ≈ $0.048 per image.&lt;/p&gt;

&lt;p&gt;Roughly five cents an image, or about 208 images per $10 block. On the same basis Gemini-3.1-flash-image-preview is ≈$0.096 and Gemini-3-pro-image-preview is ≈$0.192.&lt;/p&gt;

&lt;p&gt;Plans start at Free, with &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Basic at $39/project/month and Pro at $99/project/month&lt;/a&gt; (less on annual billing). Basic is the first plan with unlimited AI agents; Free is capped at one. To size your own numbers against storage, traffic and Actionflow runs as well as points, use the &lt;a href="https://calculator.momen.app/" rel="noopener noreferrer"&gt;pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Showcases
&lt;/h2&gt;

&lt;p&gt;Apps already built on Momen that run on image generation — the same slot Gemini-3.1-flash-lite-image drops into.&lt;/p&gt;

&lt;p&gt;Building a Production-Ready AI Image Generator with Nano Banana and No-Code A full image generator running on an earlier Nano Banana model — the same build, one model swap away. &lt;a href="https://momen.app/blogs/build-an-ai-image-generator-with-nano-banana-in-no-code/" rel="noopener noreferrer"&gt;Read more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A model on its own is not a product. What makes image generation shippable is everything around the call — a database for what gets generated, an Actionflow that fires on submit, file storage, accounts, payments — and that is what Momen gives you. Start from the app you want to build, whether that is a paid generator or avatars in a product you already run, and let this agent be one step in the flow.&lt;/p&gt;

</description>
      <category>nano</category>
      <category>banana</category>
      <category>2</category>
      <category>lite</category>
    </item>
    <item>
      <title>Wander: An AI Trip Planner Built with Momen and Claude Code</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:14:28 +0000</pubDate>
      <link>https://dev.to/momen_hq/wander-an-ai-trip-planner-built-with-momen-and-claude-code-2j4o</link>
      <guid>https://dev.to/momen_hq/wander-an-ai-trip-planner-built-with-momen-and-claude-code-2j4o</guid>
      <description>&lt;p&gt;Planning a trip usually means open tabs, contradictory forum posts, and a rough itinerary that still leaves the hard decisions to you. Wander replaces that with a form: destination, dates, budget, travel vibe — and an AI agent returns a complete day-by-day plan with mapped stops, meals, and accommodation in minutes.&lt;/p&gt;

&lt;p&gt;This project started as a manually configured Momen backend — a working template with the data model, AI agent, &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows/" rel="noopener noreferrer"&gt;Actionflows&lt;/a&gt;, and integrations all set up inside the Momen editor. The frontend was then built by connecting that backend to Claude Code via the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;: the plugin passes the full Momen project context to Claude Code, and the frontend was described in natural language from there — no code written manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3lkhvk1pvscut3jmgnn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3lkhvk1pvscut3jmgnn.webp" width="560" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Does
&lt;/h2&gt;

&lt;p&gt;A user picks a destination, sets trip length (1 to 14 days), adjusts a budget slider, and selects a vibe from eight options: Foodie, Hidden gems, Museums &amp;amp; art, Nature &amp;amp; hikes, Nightlife, Family friendly, Slow travel, or Photography. Submitting the form kicks off AI generation in the background.&lt;/p&gt;

&lt;p&gt;A progress overlay shows two stages — "Reserving itinerary" and "Planning &amp;amp; illustrating days" — in real time. When the plan is ready, the app opens the trip detail page automatically.&lt;/p&gt;

&lt;p&gt;Each trip has a cover image, a short summary, and a day-by-day breakdown with tabs. Switching days shows that day's activities, meals, and accommodation alongside an interactive map with every location pinned. Selecting a place on the list highlights it on the map, and vice versa. Users can save any trip with a single tap.&lt;/p&gt;

&lt;p&gt;Other features in the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discover page: Browse itineraries generated by other users&lt;/li&gt;
&lt;li&gt;User accounts: Sign up with username and password; new accounts receive a free credit to generate a first trip&lt;/li&gt;
&lt;li&gt;Credit system: Each generation costs one credit; users can top up with Stripe ($5 for 3 credits)&lt;/li&gt;
&lt;li&gt;Personal center: View generated trips, credit balance, and order history&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Backend Is Built
&lt;/h2&gt;

&lt;p&gt;The Momen backend covers the full server-side of the app: data model, AI agent, Actionflows, and external API integrations — all configured visually inside the Momen editor, with no server code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88m2211ssdf76rzoevph.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88m2211ssdf76rzoevph.webp" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;The data is structured as a hierarchy: each account has many trips; each trip has itinerary days; each day has activities, meals, and an accommodation entry. Every location stores geographic coordinates so the map can place each pin exactly. Two additional tables handle credit orders and trip saves (likes).&lt;/p&gt;

&lt;p&gt;This structure is configured visually in Momen — defining fields, types, and relationships between tables without writing any database code. For more on how data modeling works in Momen, see &lt;a href="https://momen.app/blogs/beginner-guide-data-modeling-momen-no-code-web-app/" rel="noopener noreferrer"&gt;How to Create Data Models for Your App in Momen&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration/" rel="noopener noreferrer"&gt;AI Agent&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The itinerary generation is handled by an AI Agent configured in Momen. The agent takes the trip inputs — destination, duration, budget, and vibe — and returns a structured plan: themed days, geographic focus, specific activities, meals, and accommodation, all with names and coordinates. It also generates a cover image for the trip.&lt;/p&gt;

&lt;p&gt;The agent prompt, model selection, and structured output format are configured in the Momen editor. No separate AI service or API key is needed on the backend — the agent runs inside Momen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Logic
&lt;/h3&gt;

&lt;p&gt;Four Actionflows handle the app's business logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate trip: An async flow that deducts a credit, calls the AI agent, saves the full itinerary to the database, and returns a trip ID. The frontend listens for completion over a WebSocket subscription and shows the progress overlay until the flow finishes.&lt;/li&gt;
&lt;li&gt;Apply default credit: A sync flow triggered at signup that adds one starter credit to the new account.&lt;/li&gt;
&lt;li&gt;Create order: Initiates a Stripe payment session when a user requests a top-up.&lt;/li&gt;
&lt;li&gt;Stripe webhook: Receives Stripe's confirmation after payment and adds three credits to the user's account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each flow is configured in the Momen editor — chaining steps, setting conditions, and connecting to the database or external services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrations
&lt;/h3&gt;

&lt;p&gt;Stripe handles credit purchases. When a user tops up, the frontend asks Momen to create an order. Momen calls Stripe using the secret key stored securely in the Momen backend, then returns only a payment token to the frontend. The Stripe secret key never appears in the frontend. After payment, Stripe's webhook notifies Momen to settle the order and add credits. See &lt;a href="https://docs.momen.app/docs/actions/guide/payment/" rel="noopener noreferrer"&gt;Momen's payment documentation&lt;/a&gt; for how payment integration is configured.&lt;/p&gt;

&lt;p&gt;Geocoding API converts place names and addresses returned by the AI agent into precise lat/lng coordinates. This runs inside the trip generation Actionflow, so every activity, meal, and accommodation entry is geocoded before it reaches the frontend.&lt;/p&gt;

&lt;p&gt;Nearby Search API enriches each day's itinerary by finding relevant points of interest around the day's geographic focus — called within the same generation flow so the final itinerary has real, locatable places rather than generic suggestions.&lt;/p&gt;

&lt;p&gt;MapLibre GL renders the interactive map on the trip detail page using a free CARTO basemap — no Google Maps or Mapbox API key required on the frontend. Coordinates come from the geocoded backend data, so the map only renders what the backend already provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Frontend with Claude Code
&lt;/h2&gt;

&lt;p&gt;With the Momen backend in place, the Momen no-code plugin was used to pass the project's full context — data model, API schema, Actionflow IDs — to Claude Code. The frontend was then generated from natural language:&lt;/p&gt;

&lt;p&gt;"Based on this Momen project, build a React + Vite frontend. There should be a home page with a trip generation form and a discover grid, a trip detail page with day tabs and an interactive map, a login/register page, and a personal center with my trips and order history."&lt;/p&gt;

&lt;p&gt;Claude Code generated the component structure, wired every screen to Momen's GraphQL API, built the Stripe checkout flow, and implemented the real-time generation overlay — all from descriptions like this, without the user writing any code manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Ways to Build Something Like This
&lt;/h2&gt;

&lt;p&gt;This project used a manually configured Momen backend connected to Claude Code via the plugin. If you're starting from scratch, there are two paths:&lt;/p&gt;

&lt;p&gt;Momen AI Copilot — build the entire app inside the Momen editor. The in-product AI Copilot lets you describe your data model, backend logic, and UI in natural language, and it configures everything directly in your project. No external coding agent needed. See &lt;a href="https://momen.app/blogs/meet-your-nocode-ai-copilot-build-apps-by-chatting-in-momen" rel="noopener noreferrer"&gt;Meet Your Nocode AI Copilot&lt;/a&gt; for how this works.&lt;/p&gt;

&lt;p&gt;Momen plugin + Claude Code / Codex / Cursor — build the Momen backend in the editor (or with the Copilot), then install the Momen no-code plugin in your AI coding agent of choice. The plugin passes your Momen project's full context — data model, API schema, Actionflow IDs — to the agent, so you can describe the frontend you want in natural language and have it wired to the right endpoints automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Long Does This Take?
&lt;/h2&gt;

&lt;p&gt;Configuring this Momen backend manually — data model, AI agent, four Actionflows, Stripe integration, permissions — would take around 20 hours of focused work in the Momen editor. With the plugin handling configuration from natural language, the same backend takes 1 to 2 hours.&lt;/p&gt;

&lt;p&gt;Building the frontend from scratch with Claude Code, once the Momen backend context is loaded into the plugin, takes around 30 minutes for the initial working version.&lt;/p&gt;

&lt;p&gt;This project uses Stripe payment integration, which requires Momen's &lt;a href="https://momen.app/pricing" rel="noopener noreferrer"&gt;Pro plan at $85/project/month&lt;/a&gt; (billed annually). Stripe itself is pay-as-you-go with no monthly fee. Frontend hosting on Vercel is free for most early-stage projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It and Clone the Project
&lt;/h2&gt;

&lt;p&gt;New users get a free credit on signup — enough to generate one trip and see the full app in action. If you want to build your own version with more credits or a different use case, clone the Momen backend into your own workspace and start from there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ai-trip-planner-nu-woad.vercel.app/" rel="noopener noreferrer"&gt;Try the live app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open in Momen editor / Clone project&lt;/p&gt;

&lt;p&gt;For a broader look at why combining a no-code backend with an AI coding agent works well for building production apps, see &lt;a href="https://momen.app/blogs/how-to-build-mvp-without-engineers-no-code/" rel="noopener noreferrer"&gt;How to Build an MVP Without Engineers&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>ai</category>
      <category>trip</category>
    </item>
    <item>
      <title>How the Momen Team Built a Multi-Round AI Judge in Momen</title>
      <dc:creator>Cici Yu</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:14:17 +0000</pubDate>
      <link>https://dev.to/momen_hq/how-the-momen-team-built-a-multi-round-ai-judge-in-momen-581b</link>
      <guid>https://dev.to/momen_hq/how-the-momen-team-built-a-multi-round-ai-judge-in-momen-581b</guid>
      <description>&lt;p&gt;Most AI integrations are stateless: send a prompt, get a response, done. A multi-round dialogue system is a fundamentally different problem. Each exchange has to know what came before — the AI needs context, and that context grows with every turn. Managing that state without a custom server is the architectural challenge this project solves.&lt;/p&gt;

&lt;p&gt;Demo Judge is an internal tool that runs a simulated multi-round conversation between a presenter and a fake AI audience, then scores the session against a Momen knowledge base. The business scenario is internal. The architecture is the point.&lt;/p&gt;

&lt;p&gt;This showcase explains how a stateful conversational AI system with nine agents and seven &lt;a href="https://docs.momen.app/docs/actions/guide/building_action_flows/" rel="noopener noreferrer"&gt;Actionflows&lt;/a&gt; is built entirely inside Momen — no custom server, no session middleware, no external orchestration layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwze8n276fulwmqc9v6z1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwze8n276fulwmqc9v6z1.webp" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the System Does
&lt;/h2&gt;

&lt;p&gt;A session moves through three phases: setup, conversation loop, and evaluation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsc8ij3kl1r4lknvnusw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsc8ij3kl1r4lknvnusw1.png" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core of every round is the DB query in the loop: before each AI call, continue_session fetches the full session_details history for this session — that query is what makes the conversation stateful across turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Design Problem: Where Does Conversation State Live?
&lt;/h2&gt;

&lt;p&gt;An AI agent has no memory between calls. Send it a question without context and it answers in a vacuum. To make a multi-round conversation work, every call needs the full history of what came before.&lt;/p&gt;

&lt;p&gt;The design decision in Demo Judge is to use the Momen database as conversation memory. Each round is a session_details record. Before every AI call, the continue_session Actionflow queries all session_details records for the current session, formats them into a conversation thread, and passes them to the agent as context. The database is the state machine.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No in-memory session state that disappears on refresh&lt;/li&gt;
&lt;li&gt;No custom session management layer&lt;/li&gt;
&lt;li&gt;The conversation is persistent, queryable, and inspectable by default&lt;/li&gt;
&lt;li&gt;The system can resume a session after a browser close or network drop&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Data Is Structured
&lt;/h2&gt;

&lt;p&gt;The data model is built around the conversation at its center and supporting context around it.&lt;/p&gt;

&lt;p&gt;session — One complete evaluation run. Stores completion status, final score, score reason, session summary, full transcript, and whether it ended by timeout.&lt;/p&gt;

&lt;p&gt;session_details — Every single round of the conversation. Stores the presenter's input (initiator_text), the audience response (response_text), an audio version of the response (response_audio), and a timestamped version. This is the table the continue_session agent reads from on every round.&lt;/p&gt;

&lt;p&gt;audience — The fake audience member. Stores name, profile, additional info, and a user_simulator field that configures how this persona behaves in the dialogue.&lt;/p&gt;

&lt;p&gt;demo_project — The Momen project being demonstrated. Stores name, description, editor URL, publish URL, and crucially: project_schema and project_schema_analysis — a machine-readable snapshot and a human-readable interpretation of the project's architecture, used to give agents accurate context about what's being demoed.&lt;/p&gt;

&lt;p&gt;tool and tool_used — A catalog of Momen features (with complexity and intro fields) and a record of which ones were actually demonstrated in a session.&lt;/p&gt;

&lt;p&gt;session_mode — Whether the session runs in conversation mode (back-and-forth Q&amp;amp;A) or presentation mode (more monologue-like delivery).&lt;/p&gt;

&lt;p&gt;Knowledge base tables — momen_docs, blog_rss, product_philosophy, talks_material, demo_example — Momen product documentation and content used to ground agent responses and scoring in accurate product knowledge.&lt;/p&gt;

&lt;p&gt;This structure is configured visually in Momen with relational links between tables. See &lt;a href="https://momen.app/blogs/beginner-guide-data-modeling-momen-no-code-web-app/" rel="noopener noreferrer"&gt;How to Create Data Models for Your App in Momen&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine Agents, Each with One Job
&lt;/h2&gt;

&lt;p&gt;Demo Judge uses nine &lt;a href="https://docs.momen.app/docs/actions/guide/ai_integration/" rel="noopener noreferrer"&gt;AI Agents&lt;/a&gt; configured separately in Momen. Separating them means each can be prompted and tuned independently.&lt;/p&gt;

&lt;p&gt;audience_profile_gen — Generates the fake audience persona from a role and additional context. Called during setup, not during the live session.&lt;/p&gt;

&lt;p&gt;question_planner — Pre-plans the questions and challenges the audience member is likely to raise during the session, based on the audience profile and the project being demoed. This runs at setup time so the audience has a coherent strategy going into the conversation.&lt;/p&gt;

&lt;p&gt;start_session — Generates the opening context when a session begins. Receives the audience profile and project details, and produces the initial framing for the conversation.&lt;/p&gt;

&lt;p&gt;audio_to_transcript — Converts voice input from the presenter into text. Called only when the session receives audio, before the transcript is passed into the conversation flow.&lt;/p&gt;

&lt;p&gt;continue_session — The central agent of the multi-round loop. On every round, it receives the full session_details history for the current session plus the presenter's latest input, and returns the audience's next response. This is what makes the conversation feel stateful and contextual rather than isolated.&lt;/p&gt;

&lt;p&gt;summarize_session — Called at end of session. Reads the complete conversation history and generates a human-readable summary of what was covered and how the session went.&lt;/p&gt;

&lt;p&gt;score_session — Evaluates the full session against the knowledge base and scoring criteria. Returns a numeric score and a written reason, which are saved back to the session record.&lt;/p&gt;

&lt;p&gt;tool_classifier — After the session ends, identifies which Momen product features were mentioned or demonstrated during the conversation, and writes those to the tool_used table.&lt;/p&gt;

&lt;p&gt;project_schema_analysis — Analyzes the project_schema of a demo_project record and generates a natural-language interpretation of its architecture. This analysis is stored in the project record and used as context for the session agents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7dhcx3iwmm62tolj5jh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7dhcx3iwmm62tolj5jh.webp" width="560" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actionflows That Wire It Together
&lt;/h2&gt;

&lt;p&gt;Seven Actionflows handle all orchestration. Each one chains database queries, branching logic, and AI agent calls into a single server-side operation.&lt;/p&gt;

&lt;p&gt;audience_profile_gen (async) — Setup flow. Generates a unique ID for the audience record, inserts it, then runs five sub-flows to classify which tools this audience persona is familiar with. Calls audience_profile_gen and question_planner agents in sequence. Updates the audience record with the generated profile.&lt;/p&gt;

&lt;p&gt;start_session (async) — Called when a presenter begins. Queries the audience record and the demo_project record to build full context. Calls the start_session agent. Inserts the session record and the first session_details record. Updates the audience's current session reference.&lt;/p&gt;

&lt;p&gt;continue_session (async) — The multi-round loop engine. Called on every presenter input. Queries the audience profile and the full session_details history for this session. Branches first on input type: if audio, calls audio_to_transcript before proceeding. Then branches on session mode (conversation vs presentation) and calls continue_session agent with the full history as context. Inserts the new session_details record for this round.&lt;/p&gt;

&lt;p&gt;end_session (async) — Called when the session closes. Handles the timeout edge case by querying and deleting the last incomplete session_details record if the session timed out mid-round. Calls summarize_session and updates the session record with the summary. Then calls score_session and updates the session with the final score and reason.&lt;/p&gt;

&lt;p&gt;tool_classifier (async) — Runs after session end. Calls the tool_classifier agent to identify which Momen tools appeared in the session transcript, and writes the results to tool_used.&lt;/p&gt;

&lt;p&gt;project_schema_analysis (async) — Utility flow for setup. Queries a demo_project record, runs a custom code node to pre-process the schema, calls the project_schema_analysis agent, and writes the human-readable analysis back to the project record.&lt;/p&gt;

&lt;p&gt;insert_tool_use_if_good — A helper sub-flow called inside audience_profile_gen. Conditionally inserts a tool_used record based on branching conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Database Makes This Work
&lt;/h2&gt;

&lt;p&gt;The continue_session Actionflow is the architectural core. Before every AI call, it runs a database query that fetches every session_details record for the current session, ordered by creation time. That complete history is what the continue_session agent receives as context.&lt;/p&gt;

&lt;p&gt;Without this query, each round would be a disconnected single-turn exchange. With it, the agent knows everything the presenter has said and everything the audience has responded with. The conversation is coherent across any number of rounds.&lt;/p&gt;

&lt;p&gt;This pattern — database as memory, Actionflow as orchestrator, agent as stateless processor — applies to any conversational AI system: customer support that remembers previous exchanges in a session, an AI interviewer that adapts based on earlier answers, a tutoring system that tracks what a student has already covered. In each case the pattern is the same: every turn is a record, every AI call includes the full history, and the database is the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frontend
&lt;/h2&gt;

&lt;p&gt;The frontend was built with Claude Code using the &lt;a href="https://github.com/momen-tech-org/momen-nocode-plugin" rel="noopener noreferrer"&gt;Momen no-code plugin&lt;/a&gt;. The plugin passed the project context — data model, Actionflow IDs, GraphQL API schema — to Claude Code, and the UI was described in natural language. The frontend connects to Momen's GraphQL API and subscribes to async Actionflow results over WebSocket to show generation progress in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A stateful multi-round dialogue with scoring — the kind of system that usually needs custom session infrastructure — built entirely inside Momen. Nine agents, seven Actionflows, the database as memory.&lt;/p&gt;

</description>
      <category>momen</category>
      <category>showcase</category>
      <category>multiround</category>
      <category>dialogue</category>
    </item>
  </channel>
</rss>
