How I Got a Dear ImGui App Approved on the Mac App Store

A technical deep-dive into shipping a C++ / ImGui desktop app through Apple's review process. Code signing, static linking, sandboxing, and everything that broke along the way.

Last week, Apple approved ColumnLens for the Mac App Store. It's a data explorer I built in C++ with Dear ImGui. I'm pretty sure it's one of the first ImGui apps to actually make it through Apple's review. Omar Cornut, the creator of Dear ImGui, was kind enough to feature it in the official gallery, which was a great moment.

I want to share what the process looked like, because when I started I couldn't find anyone who had done this before. If you're building something with ImGui and wondering whether the App Store is even possible: it is. Here's what I ran into.

What ColumnLens is Built With

The whole thing is C++17. Dear ImGui (docking branch) for the UI, GLFW and OpenGL 3.3 for the window, DuckDB as the SQL engine, ImPlot for charts, and Lua/sol2 for scripting. Everything compiled into a single binary. No Swift, no Objective-C (well, except what GLFW needs internally for macOS windows).

None of this is what Apple has in mind when they think about Mac apps. Their world is Xcode, SwiftUI, AppKit. But here's the thing: nothing in the guidelines actually requires you to use their frameworks. You just have to meet the technical bar.

The DuckDB Problem

Ironically, the hardest part had nothing to do with ImGui.

DuckDB loads extensions at runtime through dlopen. The App Store doesn't allow that. Any dynamic library either needs to be inside your bundle and signed, or it gets rejected. So I had to rebuild DuckDB from source with all extensions baked in. Nine of them: JSON, Parquet, ICU, HTTPFS, FTS, autocomplete, Excel, inet, sqlite_scanner. One big static library, no runtime loading.

DuckDB static build flags
$ cmake -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_OSX_ARCHITECTURES=arm64 \
  -DDISABLE_EXTENSION_LOAD=ON \
  -DBUILD_EXTENSIONS="json;parquet;icu;httpfs;fts;autocomplete;excel;inet;sqlite_scanner" \
  -DENABLE_EXTENSION_AUTOLOADING=OFF \
  ..

GLFW bit me too. I initially used the Homebrew version, which gives you a dylib signed with an ad-hoc signature. When you try to codesign your app bundle, it fails because the Team IDs don't match. Took me a while to figure that one out. The solution was simple once I knew: vendor the GLFW source and build it as a static lib.

Code Signing

Since everything is statically linked into one binary, signing is actually pretty simple:

Code signing
$ codesign --deep --force --options runtime \
  --entitlements ColumnLens.entitlements \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  ColumnLens.app

The --options runtime flag turns on the hardened runtime. Apple requires this for notarization. It means no unsigned code execution, no DYLD tricks, no debugging other processes. Sounds scary, but for a normal ImGui app it changes nothing.

After signing, you submit the binary to Apple for notarization. They scan it, you get a ticket, you staple it:

Notarization
$ xcrun notarytool submit ColumnLens.zip \
  --keychain-profile "columnlens-notary" --wait
$ xcrun stapler staple ColumnLens.app

Entitlements

I was nervous about this part. Turns out ColumnLens only needs a single entitlement: com.apple.security.network.client for S3 access and DuckDB's HTTPFS. That's it. No file system access needed beyond what the native open/save dialogs give you automatically. No microphone, no camera, nothing.

My advice: request as few entitlements as possible. Each one is something Apple might question. If your app doesn't need the network, you can ship with zero.

The Bundle

macOS expects a specific directory layout:

Bundle structure
ColumnLens.app/
  Contents/
    MacOS/
      columnlens ← your binary
    Resources/
      icon.icns
      lua/ ← bundled scripts
    Info.plist

CMake's MACOSX_BUNDLE does most of the work here. You still need a proper Info.plist with bundle identifier, version, minimum macOS version, and the Retina flag. I also copy Lua scripts and font files into Contents/Resources/ at build time.

What Apple's Review Actually Looks Like

I expected a purely technical check. It's not. Real humans open your app and poke around. They check whether it launches, whether the screenshots match what they see, whether there's a privacy policy (yes, even if you collect literally nothing). They also seem to judge whether the app looks like a real product vs. a thrown-together prototype.

I think this is where a lot of ImGui apps would struggle. If you ship with the default gray theme, it looks like a debug tool. ColumnLens has a custom dark theme, which I think helped. It looks intentional.

My first submission got rejected. Not because of ImGui, but because of lzma, a compression library that DuckDB pulled in. Something about how it was linked didn't pass their automated checks. I upgraded DuckDB to v1.5.0, rebuilt the static lib, and the second submission went through.

Things I Worried About for Nothing

Give the Reviewer Something to Do

One thing I'm glad I did: I wrote detailed test instructions in the reviewer notes field. Apple's reviewers are busy. If they open your app and don't immediately know how to use it, that's a problem. Especially for a data tool where the obvious first step is "open a file" but you're running in a sandbox and the reviewer might not have a 500MB CSV lying around.

I included a step-by-step walkthrough that uses ColumnLens's built-in Lua scripting to fetch live earthquake data from the USGS API. No files needed. The reviewer could go from launch to 3D visualization in under a minute:

Reviewer notes
1. Launch the app (welcome screen appears)
2. Cmd+5 → Automation workspace
3. Click "Load Example" → select "fetch_earthquakes.lua"
4. Click "Run" → fetches live USGS earthquake data
5. Cmd+1 → Table View (sort columns, filter rows)
6. Cmd+3 → Chart View (select X/Y axes)
7. Cmd+4 → 3D City View (interactive cityscape)
8. File → Export Filtered (Cmd+E) → exports CSV

I think this made a real difference. The reviewer could see every major feature working without having to guess. If your app needs data to be useful, give them a way to get data instantly.

If You Want to Do This

Here's what I'd tell someone starting out:

  1. Vendor everything and static link. No Homebrew, no dlopen, no dynamic libraries. Build all your deps from source. This is the #1 thing that will save you headaches.
  2. Make it look good. Spend a few hours on a custom ImGui theme. The default look will make reviewers skeptical.
  3. Keep entitlements minimal. The less you ask for, the less Apple questions.
  4. Test on a fresh user account. Your dev machine is lying to you. It has libraries and tools installed that your users won't have.
  5. Write a privacy policy. Apple requires one even if your app is fully offline. I know, I know.
  6. Write a clear App Store description. Reviewers actually read it to understand what your app is supposed to do.

Was It Worth the Effort?

Absolutely. Before the App Store, I distributed through Gumroad. Users had to deal with Gatekeeper warnings, the "right-click to open" dance, manual zip extraction. Now they click "Get" and it installs. That alone was worth every hour I spent on this.

There's also something else: on the App Store, most of the competition in the data tools space is Electron apps. An app that opens instantly, uses 50MB of RAM instead of 500, and doesn't spin up the fans? People notice that.

If you're building something with Dear ImGui and wondering whether the App Store is realistic: it is. The technical requirements are clear, the review process was fair, and the result is a much better experience for users. Go for it.

See It in Action

ColumnLens is on the Mac App Store. It opens CSV, JSONL, Parquet, Excel, and SQLite files. Built-in SQL, charts, 3D visualization, and Lua scripting. $12.99, no subscription.