지나가던 개발(zigae)

My Chrome extension was rejected three times: the name, the permissions, and a login that failed silently

2026년 8월 27일 • ☕️ 5 min read

I built a tool that moves web pages into Figma and put it on the stores. Getting through review took longer than writing the code.

Three rejections. One for the name, two for permissions. I was wrong all three times and the reviewers were right, which is exactly why it seemed worth writing down. You rarely get to read someone else’s rejection notices.

1. You can’t put “figma” in the name

The original name was html2figma. I submitted the plugin to Figma Community and got this back.

Website url does not align with our branding guidelines

The brand guidelines say plainly that product names, domains, and handles can’t contain “Figma” or “Fig”. The only accepted form is “X for Figma”. Nothing to argue with.

The problem is that renaming isn’t just renaming. I had to move the domain, 301 the old one, and hunt down the old name across package names, docs, and store assets. I missed places. The app name on the Google OAuth consent screen stayed html2figma for a long while after, so anyone signing in was looking at a different name than the one they installed.

It’s called html2design now.

2. Don’t request permissions you don’t use (twice)

The Chrome Web Store’s automated review caught me twice, both for the same violation class.

The first was scripting.

Requests the following permissions but does not use them (scripting)

I searched the code and there were exactly zero calls to chrome.scripting. I was running scripts in the page, but through chrome.debugger and CDP’s Runtime.evaluate. The permission had been sitting in the manifest since the beginning without ever being needed. I removed it and resubmitted.

The second was tabs, and this one had something to teach me.

You do not need to request the following permissions for the methods/properties implemented by your item (tabs)

I was actually calling chrome.tabs.query, and it was still telling me the permission was unnecessary. It was right. chrome.tabs.query works without the permission; what the permission gates is four fields: url, title, favIconUrl, and pendingUrl. My code only read tab.id. The page’s URL and title come from parsing the DOMSnapshot, not from the tab object, so it never mattered.

chrome.tabs.create doesn’t require the permission either. Opening a new tab felt like something that obviously would, and it doesn’t.

A call site doesn’t imply a permission. That’s what two rejections bought me. Check the docs for whether an API actually requires the permission before you write it into the manifest.

I applied the same reasoning to activeTab and dropped it too. I don’t use executeScript or insertCSS, and I already have host permissions, so it wasn’t widening or narrowing anything. No reason to wait for a third rejection.

The count went from five permissions to three: debugger, storage, identity. The install warning got shorter as a result, so it worked out.

What’s left is one host permission, <all_urls>. It’s genuinely required, because images on a captured page can come from any origin. The cost is that having it puts the extension into “in-depth review” and makes every submission slower. Not much to do about that one.

3. The bug that took longer than all three rejections

Google sign-in didn’t work at all.

The consent screen was fine, and clicking “Continue” closed the window, and then nothing happened. No error either. With no log, there was nothing to tell me where it stopped.

Here’s what was happening. When chrome.identity.launchWebAuthFlow opens the auth window, focus moves, and at that moment Chrome closes the extension popup. Closing the popup destroys its JS context, so nothing after the await runs. The code exchange, the session write, the error display: all of it was after that line. That’s also why there was no error. The thing that would have rendered it was already gone.

The worst part was that it worked whenever devtools was attached. With devtools open the popup doesn’t close, so the flow runs to the end. I spent a long time in “works on my machine” over it.

The fix was moving the whole auth flow into the service worker. The worker survives the popup closing, and it writes the result to storage, so reopening the popup shows the updated state. I moved opening the checkout page there too, so the tab still opens even if the popup dies partway.

If I compress it to one line: don’t start an async flow from an extension popup if that flow opens a window. A popup is a context that disappears the moment it loses focus.

Things I removed

Some of what I built, I deleted.

There was a feature on the Figma side that generated Local Styles from the captured colors and text. It’s gone. Delivery got simplified too. It used to be three paths (save a .h2f file and drop it on the plugin, copy through the clipboard, or send directly with a 6-digit code), and only the last one is left.

The problem was that the removed features were still being advertised in several places. The landing page, the Figma Community description, the store screenshots, the blog post. I was promoting things that no longer existed. Deleting a feature isn’t done when the code is gone. Text baked into store screenshots is especially easy to miss.

I also lowered the price, from $9/month to $5. That one had nothing to do with the reviews.

Where it stands

Free is five conversions a month, with no account and no sign-in. Unlimited is $5/month.

You need both the Chrome extension and the Figma plugin. The extension captures and the plugin renders, so neither half does anything on its own. I know that’s the biggest place people drop off and I don’t have an answer for it yet.

The things it can’t do, stated plainly: <canvas> pixel content doesn’t come through, you only get the element box. Gradient angles are approximated, and fonts you don’t have installed fall back to Inter, which is noticeable when the captured site uses a custom typeface.

I haven’t started promoting it and about twenty people are using it. Sending me the URL of a page that breaks is the single most useful thing right now.