<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thoughtbot="https://thoughtbot.com/feeds/">
  <title>Giant Robots Smashing Into Other Giant Robots</title>
  <subtitle>Written by thoughtbot, your expert partner for design and development.
</subtitle>
  <id>https://robots.thoughtbot.com/</id>
  <link href="https://thoughtbot.com/blog"/>
  <link href="https://feed.thoughtbot.com" rel="self"/>
  <updated>2026-06-09T00:00:00+00:00</updated>
  <author>
    <name>thoughtbot</name>
  </author>
<entry>
  <title>How I Built a Chrome Extension Wrapper (and Everything That Tried to Stop Me)</title>
  <link rel="alternate" href="https://thoughtbot.com/blog/how-i-built-a-chrome-extension"/>
  <author>
    <name>Valeria Graffeo</name>
  </author>
  <id>https://thoughtbot.com/blog/how-i-built-a-chrome-extension</id>
  <published>2026-06-09T00:00:00+00:00</published>
  <updated>2026-06-08T15:31:10Z</updated>
  <content type="html">&lt;p&gt;So one day a client came to me and said: “we want a Chrome extension.”
Simple enough, right?&lt;/p&gt;

&lt;p&gt;First thing to know: the client didn’t want to build a &lt;em&gt;real&lt;/em&gt; extension from
scratch with its own codebase and logic. They wanted a &lt;em&gt;wrapper&lt;/em&gt;,
essentially their existing Rails app, dressed up and living inside a
Chrome extension panel.&lt;/p&gt;

&lt;p&gt;Same features, same views, same assets.
Just… accessible from the browser bar.&lt;/p&gt;

&lt;p&gt;That sounds simpler than building from scratch. But, was it?&lt;/p&gt;
&lt;h2 id="exploring-options"&gt;
  
    Exploring options
  
&lt;/h2&gt;

&lt;p&gt;Now, when it comes to Chrome extensions, there are a few main UI patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Side panel: slides in like a drawer on the right side of the screen&lt;/li&gt;
&lt;li&gt;Popup: the little window that appears when you click the extension
icon in Chrome’s toolbar&lt;/li&gt;
&lt;li&gt;Iframe overlay: inject an iframe into the current page,
absolutely positioned wherever you want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trigger can be a button injected into the page (via &lt;code&gt;content_scripts&lt;/code&gt;,
positioned with CSS), or the native Chrome extension icon in the browser bar.
We explored a few options before eventually landing on a reference to model
it after.&lt;/p&gt;

&lt;p&gt;We ended up going with an iframe approach: a wrapper around the main
Rails app, rendering its views and assets inside the extension panel.&lt;/p&gt;
&lt;h2 id="the-stack-problem-rails-didn39t-like-this"&gt;
  
    The Stack Problem: Rails Didn’t Like This
  
&lt;/h2&gt;

&lt;p&gt;Here’s where it gets fun. The code for the extension, the page with the
trigger, the button, the iframe, all of it, lived inside the same Rails app
that hosted the main product. Why? Because it needed to render views and serve
assets from that app, and stay in sync if the main content changed.&lt;/p&gt;

&lt;p&gt;But running two things from one Rails app (the main UI + the extension wrapper)
is… not a great time. Locally, we ran into endless &lt;code&gt;Rack-CORS&lt;/code&gt; errors.
Trial and error sessions that felt like a personal war.&lt;/p&gt;

&lt;p&gt;In hindsight, the much cleaner solution would have been to spin up a
separate lightweight app just for the extension code, pointed at the main
app as an API. Staying in one repo is a common and totally understandable
constraint: less duplication, easier to keep things in sync. The tradeoff
is exactly what we ran into: CORS complexity and two contexts fighting each
other locally. Worth knowing before you commit to that architecture.&lt;/p&gt;
&lt;h2 id="auth-the-real-boss-fight"&gt;
  
    Auth: The Real Boss Fight
  
&lt;/h2&gt;

&lt;p&gt;Okay, if CORS was annoying, auth was brutal.&lt;/p&gt;

&lt;p&gt;The main app used Devise with cookie-based sessions. Totally normal for a
Rails app only. Totally terrible when you’re trying to embed it inside a
Chrome extension where there’s now &lt;strong&gt;another&lt;/strong&gt; session in a different context.&lt;/p&gt;

&lt;p&gt;Cookies + iframes + different origins = &lt;strong&gt;CHAOS&lt;/strong&gt;. The sessions would conflict,
auth state got confused between the main UI and the extension view, and
debugging it felt like a nightmare.&lt;/p&gt;

&lt;p&gt;The core issue: the extension iframe was loading the app from the same
origin as the main app, so cookies were being shared, but in ways that
weren’t predictable or clean. Having the extension live in a separate
app/repo with its own auth flow would have made this much more manageable.&lt;/p&gt;
&lt;h2 id="assets-because-why-not-add-one-more-thing"&gt;
  
    Assets: Because Why Not Add One More Thing
  
&lt;/h2&gt;

&lt;p&gt;Once auth was (mostly) sorted, the assets decided to have a turn.&lt;/p&gt;

&lt;p&gt;Icons and images weren’t rendering inside the iframe. The Rails asset
pipeline had opinions about how assets were served, and those opinions
didn’t align with being loaded in a sandboxed Chrome extension context.
Some path adjustments and pipeline config tweaks later, it was working,
but it was one of those things where you fix it and never want to look at
it again.&lt;/p&gt;
&lt;h2 id="the-chrome-web-store-setup"&gt;
  
    The Chrome Web Store Setup
  
&lt;/h2&gt;

&lt;p&gt;Here’s the practical stuff that I wish was better documented:&lt;/p&gt;

&lt;p&gt;You need a Chrome Developer account: a Google account registered as a
Chrome Extension developer.&lt;/p&gt;

&lt;p&gt;The client owned this account, which made sense since they’d be the publisher.
But the Chrome Extension developer account didn’t support collaborators.
So deployments of staging versions had to go through the client’s access.
That’s a workflow you want to sort out early, when you are a developer on a
client project: nothing worse than needing to push a fix
and waiting for someone else to log in or test changes for you. And make
debugging an extra hard task.&lt;/p&gt;

&lt;p&gt;Anyhow, in practice, this is the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a placeholder app in the Chrome Web Store to get a unique extension ID&lt;/li&gt;
&lt;li&gt;Hardcode that ID into your Rails config (initializers/environment config).
It’s used to toggle the extension open and controls how the iframe is allowed
to load&lt;/li&gt;
&lt;li&gt;Develop locally using &lt;code&gt;sandbox/unpublished mode&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rotate to the production extension package when publishing&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-i39d-do-differently"&gt;
  
    What I’d Do Differently
  
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Separate repo for the extension code from day one.
Fewer CORS headaches, cleaner auth, easier to reason about.&lt;/li&gt;
&lt;li&gt;Get a reference from the client early. When they finally showed me the
competitor’s extension they wanted to replicate, I’d already gone down
two other paths. Ask “is there an example you want to match?” in the first
meeting.&lt;/li&gt;
&lt;li&gt;Plan for auth early. If your main app uses cookie sessions, figure out the
extension auth strategy before you write a line of extension code.&lt;/li&gt;
&lt;li&gt;Test assets in the extension context early. Don’t assume the pipeline will
just work cause it probably won’t.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tldr"&gt;
  
    TL;DR
  
&lt;/h2&gt;

&lt;p&gt;Chrome extensions are powerful and actually not that hard in isolation.
The complexity explodes when you’re wrapping an existing app with existing
auth and trying to share code. Clean separation of concerns is your friend.
CORS is your nemesis. And always ask the client if they have a reference
they want to take inspiration from.&lt;/p&gt;

&lt;p&gt;Building a Chrome extension and not sure where to start?
Now we know exactly how to. Let’s chat.&lt;/p&gt;

&lt;aside class="related-articles"&gt;&lt;h2&gt;If you enjoyed this post, you might also like:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/how-to-use-chatgpt-to-find-custom-software-consultants"&gt;How to Use ChatGPT to Find Custom Software Consultants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/2009-rubyists-guide-to-a-mac-os-x-development"&gt;2009 Rubyist’s Guide To A Mac OS X Development Environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/five-ridiculously-awesome-cucumber-and-webrat"&gt;Five Ridiculously Awesome Cucumber (and Webrat) Features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/aside&gt;
</content>
  <summary>Sharing my experience building a Chrome extension wrapper around a Rails app: the hard-won expertise, the rabbit holes, and everything I'd do differently.</summary>
  <thoughtbot:auto_social_share>true</thoughtbot:auto_social_share>
</entry>
</feed>
