Bootcamp
Build drag-and-drop deal stages (a kanban board) in Bubble
Photo by Nathaniel Yeo on Unsplash
A pipeline you can only look at is half a CRM. The moment sales tools clicked for most of us was dragging a deal card from “doing” to “done”, and that interaction is entirely buildable in Bubble with a free plugin, one workflow, and a data model that was set up properly to begin with. This tutorial builds it: first drag-and-drop between stages, then the genuinely hard part, resorting cards within a column.
A kanban board is a set of columns representing the stages of a process, with cards that move between them. In Bubble, the columns come from a repeating group over a stage option set and the cards from a nested repeating group searching for records in each stage — so “moving” a card is just updating one field on one row.
The setup: what this board is built on
If you’re starting cold, here’s the structure this tutorial assumes (built earlier in module 9 of the bootcamp):
- A Deal data type with fields:
name(text),contact(type Contact),value(number),renewal dateandexpected close date(dates),contract(file),status(a DealStatusOS option set: open, won, lost) andstage(a DealStageOS option set: to do, doing, done). The option sets keep their displays lowercase and URL-friendly, with aLabelattribute for what users see. - A deals page where the columns are programmed dynamically rather than hard-coded: a repeating group (
RepeatingGroup Deal Stages) with type of content set to the Deal Stage option set, one row and three columns, so the data defines the board. Each cell holds a column group with a header showingParent group's Deal Stage's Label. - Inside each column, a nested repeating group (
RepeatingGroup Deal) with data sourceDo a search for Dealsconstrained tostage = Parent group's Deal Stage. Each cell contains aGroup Deal Cardshowing the deal’s contact, value, dates and status. - A popup (type of content Deal) that creates a deal when its deal is empty and edits when it isn’t, via Create Deal and Update Deal custom events, plus a confirm-delete popup.
The board works, but it’s static. Deals only change stage through the edit popup. Let’s fix that.
Part 1: drag deals between stages
Step 1: install the plugin. Go to Plugins and install Draggable Elements, by Bubble. It gives you three things: a Drag/Drop Group (the element you pick up), a Drop Area (the target), and a workflow event for when one lands on the other.
Step 2: add a Drop Area to each column. Drop a Drop Area into the cell of the outer repeating group, set its type of content to Deal Stage, and its data source to Current cell's Deal Stage. Then pull your existing column group inside the Drop Area so it sits below it in the element hierarchy, and check the chain of Deal Stage references still passes down to the inner repeating group. Each column is now a live target that knows which stage it represents.
Step 3: make the cards draggable. Add a Drag/Drop Group inside each cell of the inner repeating group, set its type of content to Deal with data source Current cell's Deal, and move the card group inside it. The card now represents a deal you can pick up.
Step 4: tune the drag behaviour. Preview and you’ll find the card drags but stays constrained within its cell. On the Drag/Drop Group, enable the option to make the element droppable anywhere, and give the group a background (surface white) so the card stays visible while dragging. Set the drop behaviour to move back, otherwise the card vanishes when released. If the drop group carries more spacing than the card, remove it and fit height to content so the draggable region hugs the card.
Step 5: wire the workflow. Add the plugin’s event When a Drop Area has a group dropped on it, and set the type to Deal. Add one action:
Make changes to a thing...with thing to changeCurrent Workflow's Dealand field changestage = This Drop Area's Deal Stage.
Step 6: test. Drag a card from to do into doing. The drop area reports its stage, the workflow writes it onto the deal, and the nested repeating groups re-run their searches: the card leaves one column and appears in the other. One action, and the board is live.
Part 2: resorting deals within a list
Dragging between stages is the easy half. Reordering cards within a column is a genuinely hard problem, hard enough that the honest way to solve it is to step away from the board entirely and isolate the logic on a scratch page, which is exactly what we’ll do. (Working in a separate branch of your app is a sensible precaution for experiments like this.)
Step 1: build a minimal test rig. Create a page called list sort, functional rather than pretty. Create a data type Reorder Test with two fields: name (text) and sort by (number). In App Data, create five rows: apple = 1, banana = 2, cranberry = 3, dinner = 4, eggs = 5. Five rows, not three: you need enough items to test ranges, not just swaps.
Step 2: display the list. Add a repeating group, type Reorder Test, data source Do a search for Reorder Tests sorted by sort by, ascending. In each cell put the Current cell's Reorder Test's name, the Current cell's index, and an input (integer) with initial content Current cell's Reorder Test's sort by. Typing a new number into that input will be our stand-in for dragging.
Step 3: think on paper before you build. This is the step that actually solves the problem. Map the scenarios: an item swaps with its neighbour above; swaps below; jumps up past several items; jumps down past several. Sketch them out and a pattern appears. The neighbour swap is just a range of one, so there are really only two scenarios: moving up and moving down. In both, three things must happen:
- Identify the range to alter: every item between the new position and the old position, excluding the moved item itself.
- Set the moved record’s
sort byto the new position. - Shift the range by one: down one (+1) when the item moved up; up one (-1) when it moved down.
Fifteen minutes of drawing beats hours of trial and error inside the workflow editor.
Step 4: build the “moving up” workflow. Event: When Input sort by’s value is changed, with the condition Parent group's Reorder Test's sort by > This Input's value (old position greater than new position means the item moved up). Steps:
Make changes to a list of things...onRepeatingGroup Reorder Test's List of Reorder Tests:items from # This Input's value, with no field changes. We’re using the action purely to retrieve the tail of the list from the new position onward.- The same again, this time
:items until #. Here Bubble won’t let you do maths inline, so use the trick:Arbitrary textcontainingParent group's Reorder Test's sort by - 1, with:converted to numberon the end. Arbitrary text plus converted-to-number is the standard way to nest a little maths inside an expression. Make changes to a list of things...onResult of step 1 intersect with Result of step 2 minus item Parent group's Reorder Test. The intersect catches exactly the middle of the list, which is the range to alter, andminus itemexcludes the record being moved. Field change:sort by = This Reorder Test's sort by + 1.Make changes to a thing...onParent group's Reorder Test:sort by = This Input's value. Add the conditionResult of step 3:first item is not empty. It isn’t strictly necessary, but because this step references step 3, it guarantees the range update runs first and avoids any timing issues with the repeating group re-sorting mid-flight.
Extra steps? Deliberately. Breaking the retrieval into two slices and an intersect keeps each expression simple enough to reason about; the single-expression version is where this problem eats hours.
Step 5: test moving up. Type 1 into cranberry’s input (position 3 to 1). Expected: cranberry, apple, banana. Then banana to 2, then eggs to 1. Each time, the displaced items should bump down exactly one place and the numbers stay a clean 1-to-5 sequence. This writes straight to the database, and it’s snappy.
Step 6: build the mirror workflow for moving down. Copy the event, reverse the condition (sort by < This Input's value), and mirror the slices: step 1 becomes :items from # Arbitrary text Parent group's Reorder Test's sort by + 1 :converted to number; step 2 becomes :items until # This Input's value; step 3 is the same intersect-minus-item with sort by = This Reorder Test's sort by - 1; step 4 is unchanged. Test the reverse moves: cranberry to 5, then back to 1, dinner to 5, eggs to 4. Provided you enter a number within the list’s range, the sequence stays intact.
Step 7: connect it back to the board. On the kanban there’s no input to type into: the new position comes from where the card lands. The approach is the same workflow with the drop area’s index standing in for the input’s value. That final wiring is left as a challenge, with a fair warning from experience: dragging between columns while preserving a perfect sequence in each is not guaranteed by this logic alone. If column one runs 1-to-5 and column two runs 1-to-2, a card dropped across columns needs its sequence reconciled. The core reordering logic above is solid and definitively tested; treat the fully draggable version as an optional stretch rather than a requirement. It’s a lovely UX improvement, not a foundation.
Try it yourself
Build the rig on the scratch page before touching your real screen. You’ll exercise half the list toolkit in one exercise (:items from and :items until, intersect with, minus item, arbitrary text with :converted to number, and forcing workflow order with only-when conditions) on five rows of data you can fully hold in your head.
This skill in the AI era
What survives from this tutorial isn’t the plugin settings. It’s step 3: reducing a fuzzy interaction to two scenarios and three operations before building, which is precisely how you get good results from AI tools. Describe the kanban data model and the reordering rules to Claude Code and it will produce a working board in whatever stack you like, but only as correctly as you specified the edge cases. The builders who sketched ranges on paper in Bubble are the ones writing sharp specifications now. That’s the through-line of our move from Bubble to AI coding, and it’s how we approach every AI-assisted build: logic first, tooling second.
If this sparked something, let's talk.
No pitch, no pressure — just a conversation about what you're working on.
Let's talkRelated posts
Bootcamp
A simple approach to data modelling
A four-step method for designing a database schema: identify entities, map relationships, choose fields. Taught through the CRM data model built in Bubble.
Bootcamp
Module 5 — Application architecture: pages, states and reusable pieces
Single-page vs multi-page apps, URL parameters, custom states, reusable elements — and a big practical building a main template with an expanding navigation drawer.
Bootcamp
API calls as actions vs data (and the planning checklist)
When to use Bubble API calls as actions versus data sources, a one-to-many auth pattern, and the planning checklist that keeps integrations from failing silently.