SOOT Part 1: The Union/OOP Pattern Coda didn't have

PART 1 - Intro
PART 2 - Making it actually work
PART 3 - Some notes

How I Accidentally Reinvented Union Types for Pizza

I developed this pattern while building a backend for my pizza shop in Coda. I needed to see orders, inventory, staff schedules, and supplier deliveries all in one calendar view, but Coda kept telling me “sorry, no UNION for you.” So I built this solution from scratch.

Of course I used AI to polish the language as English is not my primary.

Here’s the thing—I thought I’d invented something revolutionary. That shower-moment eureka feeling when everything clicks? Yeah, I had that. Then I did some research and realized I’d just rediscovered union types, polymorphic associations, and the adapter pattern. Classic computer science stuff that’s been around for decades.

But here’s what made it worth sharing anyway: I’d found a way to make these proven patterns work beautifully in Coda’s specific environment. The “Unified Interface” design—those simple title_ and dateStart_ columns—turns out to unlock Coda’s formula hinting and dot-notation in ways that make development surprisingly clean. No more brittle SwitchIf() hell.

The real contribution isn’t theoretical innovation—it’s translation. Taking powerful architectural patterns and turning them into a step-by-step recipe that actually works in production. My pizza shop backend became faster to build and way easier to maintain. That felt worth documenting.

The SOOT Pattern: Union Different Tables in Coda

Need one calendar for Events, Tasks, and Training but Coda won’t let you UNION tables? Here’s how to create unified views without messy mega-tables.

Picture this: you’ve got three different tables that describe similar things—events have venues and start times, tasks have due dates and assignees, training sessions have locations and schedules. They’re all calendar-worthy, but they live in separate tables with different column names. Sound familiar?

The SOOT pattern solves this by creating a hub table where each row points to exactly one source record. Think of it as a universal remote for your data—one interface that can control different types of content.
But most importantly for me, it introduces really neat way to do OOP-Style polymorphism. More on this in part 3.

Core Concepts (Really, Just 30 Seconds)

SOOT (Structured One-Of Table) is your hub. Each row points to exactly one source—Event OR Task OR Training, never multiple.

Unified Fields are your adapters. Add columns like title_, dateStart_, location_ to each source table that expose the same data shape, even when the underlying columns have different names.

Source is a formula that picks which pointer is filled. It’s like having one variable that could be a cat, dog, or bird—but it’s always exactly one animal.

Valid? and IB keep your data clean. Think of IB as “Invalid Because”—it lists what’s wrong with a row. Valid? is simply true when IB is empty.

Step 1: Create Your Kinds Registry (Trust Me On This One)

Create a simple table called Kinds with just a few columns:

Name your kinds: Event, Task, Training Add icons: 📅, , 🎓 Maybe add a name template for consistency

This feels like extra work, but it pays off immediately. Instead of hardcoding “Event” in filters everywhere, you reference the Kinds table. When you inevitably need to rename something or add a new type, you change it once instead of hunting through dozens of formulas.

Plus, Coda’s formula autocomplete starts working properly because your type_ fields become proper lookups instead of text strings.

Step 2: Add Your Unified Fields

This is where the magic happens. On each source table, add these simple formula columns:

title_ maps to whatever that table calls its title dateStart_ maps to that table’s start date dateEnd_ maps to the end date (if it has one) location_ maps to wherever that thing happens type_ points to the right row in your Kinds table

For example, on your Events table, title_ might be thisRow.[Event Name], while on Tasks it’s thisRow.[Task Title]. The point is they all expose a field called title_ that your unified view can count on.

Keep these formulas simple and direct. No fancy logic here—if you need computation, do it in a helper column first.

Step 3: Build Your One-Of Table

Create your hub table (call it something purposeful like “Unified Calendar” or “Master Schedule”) with lookup columns for each source table:

Add an Event lookup to Events, a Task lookup to Tasks, a Training lookup to Training. The rule is simple: exactly one of these gets filled per row.

Then add your Source formula—this picks whichever lookup is actually filled:

coda

If(
  thisRow.Event.IsNotBlank(), thisRow.Event,
  If(
    thisRow.Task.IsNotBlank(), thisRow.Task,
    If(
      thisRow.Training.IsNotBlank(), thisRow.Training,
      List().First()
    )
  )
)

Now for the payoff—your unified reads become clean one-liners:

Title becomes If(thisRow.Source.IsBlank(), List().First(), thisRow.Source.title_)

Notice how this works? Once you have Source pointing to the right record, thisRow.Source.title_ gives you the title regardless of whether it came from Events, Tasks, or Training. Coda’s dot-notation just works.

Set up a display column that shows something readable, add some basic validation, and you’re done.

Daily Usage (The Part That Actually Matters)

Here’s how it works in practice: keep adding Events, Tasks, and Training to their original tables like always. Create rows in your unified table that point to them. Build your calendar view on the unified table, filtering to Valid? = true.

The beautiful part? You edit data in the source tables where it belongs, but you operate from the unified view. Click on a unified row and it takes you right to the source. No data duplication, no sync issues.

Coming up in Part 2: How do I automate this so I don’t manually create unified rows? What’s this VIBE pattern for validation everywhere? And how can I make buttons work uniformly across different types?

Part 3 preview: What computer science concepts am I accidentally using? How does this scale? What are the gotchas I should know about?


:magnifying_glass_tilted_left: Are you struggling with these Coda challenges?

  • Need to show Events, Tasks, and Projects in one calendar but they’re in separate tables?

  • Want to combine different table types in a single view without messy mega-tables?

  • Frustrated that Coda doesn’t have UNION functionality like SQL?

  • Looking for a way to merge multiple tables into unified boards, timelines, or dashboards?

  • Need one view for different data types that updates instantly and stays fast?

The SOOT Pattern solves this. It’s a systematic approach to create unified views across different tables without losing performance, native Coda functionality, or data integrity. Used successfully in production docs ranging from pizza shop operations to complex project management systems.

Search terms: Coda union tables, combine multiple tables, unified calendar view, merge different table types, single board for events tasks projects, Coda table consolidation

1 Like

I’ll reply here since this is the starting point. The community and the users we’re trying to help would benefit more if you kept everything in a single thread and used a Coda doc for deeper details. Right now the information is split across three threads, which makes it hard for users to follow.

Now beside that, I am trying to wrap my head around this and understand it. There is one part for example, “Want to combine different table types in a single view without messy mega-tables?“… well… isn’t this exactly what are we doing in here?.

Please correct me if my understanding is a bit blurry :raising_hands:

2 Likes

Thanks for input.

Well I mean by mega-table something that has columns for Event (example) and task. That is a solution proposed many times.

So it contains all the columns for Events and all the columns for Task, so it becomes n+m wide. And the data is in it.

Arguably SOOT could be seen as a mega table, but it only has a lookup/reference to its child, not the data the child needs/owns.

So the data lives in Event and Task, but the SOOT defines both Event and Task able to be on a calendar, and forces them to have few more columns with exactly the names you need to fill up you calendar (eg start_, end_, title_).

So I believe that SOOT table (by example called Unified Calendar) has no data, and is not as big as a mega-table.

And it won’t need any logic to retrieve the data, as we can use Soot.Source.[col_] (simple dot.notation) to access the data on the child (event or task on this example)

I would argue that the reason it has been proposed almost exclusively is because it is much simpler for most people?

There is no need for advanced tricks and automations, simply filters.

1 Like

Of course.

I am just proposing something that is workable, synchronous and scalable. For the few that have slightly more complex models, but do not want to buy the Merge Pack or have to setup one themselves.

For me biggest pitfall of mega-table approach is the scenario in which I have to add a new type, and now I would have to retouch all the formulas or force some Tasks to have the property of an invoice and handle all the edge cases.

Even for basic scenarios, SOOT requires maybe a little setup, but then the “wiring” is actually simpler because can use consistent dot-notation transparently?

1 Like

UPD2: oh, I got baited by an AI article. None of this works in Coda; there’s no polymorphism in Coda tables, and A.Col and B.Col are two entirely different $$references despite the same column name — it’s not like in JS where that would work.


oh does it? gonna go and check, because the last time I checked, Table1.ColName and Table2.ColName were two completely different $$references with different object and item ID’s and would not work in a formula (you have to coalesce manually, i.e.

thisRow.Task.Name
  .IfBlank(thisRow.Event.Title)
  .IfBlank(thisRow.Whatever.Whatever)

etc)

Also for anyone finding this thread and not specifically looking for a free solution — this also exists :slight_smile:


UPD. Just checked; here’s what I’m getting (just what I expected)

Could you share your demo / proof of concept doc? I’m curious how you could set that up.

1 Like