# TulipLot > TulipLot is a browser dashboard that shows up to six live websites side by side in a fixed 3×2 grid, in one browser tab. Full text of every guide and blog post. llms.txt lists the same pages with one line each. # Getting started with TulipLot Source: https://tuliplot.com/guides/getting-started/ Published: 2026-08-01 ## What you'll have in five minutes By the end of this guide you'll have a browser dashboard: one tab holding a fixed 3×2 grid, with your five most-used apps within reach at once. Some load right there in the grid, live. Others — Gmail and Google Calendar are the common ones — are one click from opening in their own tab instead, and [Gmail and Google Calendar side by side](/blog/gmail-and-calendar-side-by-side) covers exactly how that pairing works day to day. Either way, you stop digging through tabs to find the thing you were just looking at — which is [the whole point](/blog/why-we-built-tuliplot). Set up takes about five minutes. Here's the whole path, start to finish. ## 1. Create your account Sign up with email and a password, or use Sign in with Google — either takes under a minute. The free plan needs no credit card, so there's nothing to cancel later if TulipLot isn't for you. Once you're in, you'll land on an empty grid: six cells, all blank, waiting for you to add apps. That's the next step. ## 2. Add your first apps Click the + on any empty cell. You'll get two ways to fill it: pick from the built-in catalog, or paste any HTTPS URL and TulipLot will load it directly. The catalog is grouped by category — email, productivity, news, media — and covers the apps most people reach for first: Gmail and Outlook, Trello and Notion, Hacker News, and more, each already addressed so you don't have to hunt for the right URL. Paste-a-URL covers everything else: your team wiki, a status page, a site the catalog doesn't have yet. One rule either way: the URL has to be HTTPS. Plain http is rejected, because a dashboard that mixes secure and insecure pages in the same tab isn't one you can trust. Not every app behaves the same once it lands in a cell, and TulipLot is upfront about which is which. Some sites — Trello is one — load live in the grid with nothing extra to do. Some, like Notion and Hacker News, need the TulipLot Companion extension before they'll embed; the cell walks you through it — a button to install the Companion from the Chrome Web Store, then a second click back in the cell, "Enable for this site," once it's installed. And sites that insist on their own login window, like Gmail and Google Calendar, open in a new tab instead of framing in the grid — still one click away, just not embedded. All three are useful; you'll just meet them a little differently. If you're not sure where to start, a solid first five looks like this: Gmail for mail, Google Calendar, Trello for boards or tasks, Hacker News for a news source, and Notion for notes and docs. That mix touches all three behaviors above, so you'll see how each one works from day one. It fills five cells and leaves the sixth for whatever you add next — or for Premium later. ## 3. Arrange the grid Once you've got a few apps in, drag any two cells onto each other to swap their positions. There's no free-floating or resizing — the grid stays fixed at six cells, which is the point. A fixed layout means your eyes learn where things are and stop having to check. Open the dashboard on Monday and mail is still top-left, calendar is still next to it — no relearning, no misclicks. That's spatial memory doing the work instead of your attention. Since the layout is fixed, it's worth being deliberate about where things go. Put whatever you glance at most — mail, chat, whatever pulls at you all day — in the top-left cell, since that's where your eyes land first. Everything else can follow. ## 4. Every cell control, explained A cell that's loading live in the grid gets the full toolbar. A cell that opens in its own tab instead — Gmail and Calendar, from the starter five above — shows a simpler three: open in tab, edit, remove. A cell still waiting on the Companion shows that same simpler three until you enable it, then picks up the rest. Either way, there's no in-grid frame yet to reload, expand, or put to sleep, so those controls just don't apply. Here's what each one does, and where you'll see it. **Reload** refreshes just that cell, the same as hitting refresh on a single tab — handy when a site's gone stale but you don't want to touch anything else on the grid. Live cells only. **Expand** puts one cell into focus mode, filling the browser window so you can work in it without the other five competing for space. Press Esc to exit and return to the full grid. Live cells only. **Open in tab** launches that cell's site in a normal browser tab, with its full URL bar and browser chrome. Every cell has this one — it's how the tab-only apps like Gmail and Calendar get used day to day. **Edit** swaps the site loaded in a cell without disturbing its position. Change your mind about which calendar app you want? Edit the cell and the new site takes over the same slot — no rebuilding the grid. Works on every cell. **Sleep** pauses a live cell — it stops refreshing and its background noise goes quiet. **Wake** brings it back exactly where you left off. Useful for a cell you need in view but don't need active every minute. **Remove** clears a cell back to empty, ready for something new. Works on every cell. ## 5. When a site won't load Some sites send headers that tell browsers not to embed them elsewhere — a security setting many banks and login pages use. When TulipLot detects one of these, it doesn't show you a broken frame. Instead it offers the TulipLot Companion, an optional Chrome extension that can unlock the site, or an open-in-tab option so you can use the site normally in its own window. Read the full guide: [Adding sites that refuse to be framed](/guides/add-any-site). ## Try, free, and Premium in one minute Not ready to create an account? [Try TulipLot](/try) first: two live cells, no signup, saved to this browser only. The free plan gives you five usable cells plus one ad in the sixth, and it costs nothing — no credit card required. Premium is $4 a month, removes the ad, and unlocks all six cells for your own apps. Everything else — the catalog, custom URLs, drag-to-swap, the Companion — works the same across all three. See the full breakdown: [Premium vs Free](/guides/premium-vs-free). ## Common first-day questions ### Do I need the extension? No — most sites load straight into your grid with no extension at all. The TulipLot Companion only matters for stubborn sites that actively block embedding; install it if and when you hit one. ### Does it work outside Chrome? TulipLot is built Chrome-first and works best there. The Companion extension is Chrome-only, so if you're on a different browser, sites that need it will open in a tab instead of unlocking in the grid. ### Can I change apps later? Any time. Use edit to swap the site in a cell without losing its position, or remove it and add something new. Nothing about your layout is permanent except the six-cell shape. Your dashboard is ready whenever you are. [Get started free](/register) and have your five-minute setup done before your coffee's cold. --- # Adding sites that refuse to be framed Source: https://tuliplot.com/guides/add-any-site/ Published: 2026-08-01 Updated: 2026-08-16 Paste almost any HTTPS URL into a cell and it loads. But every so often you'll add a site and the cell says the site refuses to connect, or that it won't load here. That's not a TulipLot bug — it's the site itself, and there's a fix for most of them. ## Why some sites refuse to load Browsers let any site send an instruction that means "don't let other pages frame me." It comes through as a response header — [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options) or a `Content-Security-Policy` with [`frame-ancestors`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-ancestors) — for a real reason: without it, someone could load your bank's login page inside a hidden frame on their own site and trick you into typing your password into what looks like the real thing. Blocking embedding stops that cold; [the full explanation of why sites refuse to be embedded](/guides/why-sites-wont-load) covers the headers involved and what does and doesn't fix it. The same header also blocks harmless embedding, like a cell in your grid. The browser can't tell a malicious overlay from a dashboard you built on purpose — it just sees "don't embed me" and obeys, and TulipLot can't override that at the page level. So instead of pretending it can, it tells you what happened and gives you a real option, rather than a broken pane. ## What TulipLot shows you instead When a site blocks embedding, the cell shows you one of three states, depending on what that particular site allows: - **Needs the Companion.** The site can be unlocked, but only with the Companion extension installed and turned on for it — Notion and Hacker News are common examples. The cell shows three buttons: **Install TulipLot Companion**, **Enable for this site**, and **Open in a tab instead**. - **Opens in its own tab.** Some sites — Gmail, Outlook, and Google Calendar are common examples — never embed, Companion or not. Their protections run deeper than a header. The cell just offers to open the site in a normal tab. - **Load-failed with Retry.** A site tried to load and didn't. Often it's a Companion-eligible site that isn't granted yet; sometimes it's just a slow server or a timed-out connection. Either way, the cell shows a Retry button, and there's more on sorting out which is which below. None of these is a dead end — each gives you the fastest working path for that particular site. ## Install the TulipLot Companion The TulipLot Companion is a free, optional Chrome extension, and it's the reason most stubborn sites end up embedding anyway. What it does is narrow: it [adjusts frame-blocking headers](https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest) only for sites you've explicitly enabled, one at a time. It doesn't touch any other site, and it collects nothing about you or your browsing. To install it, click **Install TulipLot Companion** on any cell that needs it. That opens the Chrome Web Store listing, where installation happens the normal Chrome way. Once it's installed, come back to your dashboard — the cell is still waiting for the second step. The Companion is Chrome-only, since it relies on Chrome's extension APIs. On a different browser, sites that need it fall back to opening in a tab. ## Enable a stubborn site Installing the Companion doesn't unlock anything by itself — it just makes unlocking possible, site by site. A browser won't let a page grant itself permission to relax another site's security headers, so the enabling step happens deliberately, one click at a time, back in the cell: 1. **Install TulipLot Companion**, if you haven't already — opens the Chrome Web Store listing, where installation happens. 2. Back in the cell, click **Enable for this site** — the required second step, specific to that one site. 3. The cell retries and, for most sites, loads live in the grid from then on. The cell checks each site's grant, so the button is never lost: install the Companion, reload your dashboard, and every stubborn cell shows its own **Enable for this site** button — one click each. Add another stubborn site later and its cell asks the same way. Prefer Chrome's settings? Open `chrome://extensions`, click **TulipLot Companion**, go to **Site access**, and add the site there — same permission, same one-at-a-time scoping, just requested from the other side. ## What still can't be embedded Not every site gives in, even with the Companion enabled — worth saying plainly, rather than letting you assume you did something wrong. Many banks and some login flows, including Gmail, Outlook, and Google Calendar, refuse to embed no matter what. Their protection runs deeper than the header the Companion adjusts, often tied to how login and session cookies work in a top-level window. The Companion genuinely can't unlock them, and it's honest to say so instead of promising a fix that doesn't exist. Those cells aren't wasted, though. They get a reduced toolbar of three controls — open in tab, edit, remove — and **open in tab** does the real work: one click and the site opens in its own tab, fully itself. Sites like Trello need none of this — they embed live in the grid the moment you add them. ## Troubleshooting ### Reload the cell If a site that normally loads fine shows a blank cell, try reload first. A stale connection or a slow response is the most common cause, and reloading clears it without touching the rest of the grid. ### Re-grant the site If a stubborn site stops embedding — say its permission was removed — its cell falls back to **Enable for this site**. Click it, approve Chrome's prompt, and the cell loads again. The manual path works too: open `chrome://extensions`, click **TulipLot Companion**, open **Site access**, and re-add the site there. Neither touches your other sites. ### Load-failed → Retry A load-failed cell means the site had its permission and started to load, but didn't finish — usually a slow server or a timeout, and a second Retry clears it. Still stuck, check the site in a regular tab; it may just be down. And if the cell shows **Enable for this site** instead, that's not a failure — the site just hasn't been granted yet, and the button is the fix. ### When in doubt, open in a tab If a cell isn't behaving as expected and nothing above clears it, **open in tab** always works as a fallback. It's on every cell, live or not, and it gets you to the actual site with zero troubleshooting. ## Safety questions, answered ### Is loosening those headers safe? Yes, because of how narrow the change is. The Companion adjusts embedding headers only for the specific sites you chose, only inside your own browser, and only while it's installed. It never touches sites you haven't enabled, and never changes how those sites behave for anyone else. ### Why is it a separate extension? Because a web page isn't allowed to grant itself that power — it's a browser rule, not a TulipLot limitation. Only an installed extension, one you explicitly add and that asks Chrome for permission, can adjust how a site's headers get handled for you. Splitting it out keeps the unlock opt-in and visible, not hidden in the page. ### Does it read my browsing? No. The Companion collects nothing — no browsing history, no page contents, no analytics. Its only job is adjusting embedding headers for sites you've enabled; there's nothing else it does. Most sites need none of this — they just work. For the ones that don't, now you know what "refused to connect" means and what to click next. Haven't set up your grid yet? [Get started](/guides/getting-started) or [create a free account](/register) and add your first stubborn site today. --- # Premium vs Free — what you get Source: https://tuliplot.com/guides/premium-vs-free/ Published: 2026-08-01 Every TulipLot account starts on the free plan, and for plenty of people that's the whole story — five live cells and the full toolbar cover a normal day just fine. Premium exists for one reason: you want that sixth cell for something of your own, and you'd rather not look at an ad to get it. Here's exactly what changes, what stays the same, and what happens to your grid if you switch either direction. ## At a glance | | Try (no account) | Free | Premium | |---|---|---|---| | Usable cells | 2 | 5 | 6 | | Ad cell | 1 | 1, bottom-right | None | | Price | $0, no signup | $0, forever | $4/month | | Saved to | This browser only | Your account | Your account | | Catalog + custom URLs | Yes | Yes | Yes | | Drag-to-arrange, focus, sleep | Yes | Yes | Yes | | Companion support | Yes | Yes | Yes | ## Try it before you sign up Before you commit to either plan, there's a third option: [try TulipLot](/try) with no account at all. Two cells load live immediately — pick from the catalog or paste any HTTPS URL, and they render in the grid exactly like a Free or Premium cell would. Three more cells sit locked behind a Create free account prompt, and the sixth carries an ad, so the 3×2 grid looks complete from the first click, even though only two cells are yours to fill. Everything about those two cells works the same as on Free: drag to rearrange, expand into focus mode, sleep, edit, remove, the Companion for stubborn sites, all of it. The only catch is where they live. Your cells stay in that browser's local storage, not synced to any account, so a different device or a cleared browser starts over. Sign up, though, and whatever you built carries straight into your new account's grid, no rebuilding required. ## What free actually includes Free isn't a trial with a countdown — it's a complete, permanent version of TulipLot. Five cells load live apps from the moment you sign up, and every one of them gets the full catalog: email, productivity, news, media, and anything else the built-in list covers. If the catalog doesn't have what you want, paste any HTTPS URL into a cell yourself and it loads the same way. Every cell control works identically to Premium, too. Drag any two cells to swap positions, reload a single cell without touching the rest, expand one into focus mode, edit a cell to swap in a different site, sleep a cell you're not using right now, remove one you're done with. None of that is gated behind payment — it's the same toolbar either way. The one difference sits in a single spot: the bottom-right cell carries an ad. It's fixed there, it doesn't move around the grid, and it doesn't shrink your other five cells to make room. Free needs no credit card to start and has no time limit — you can run five cells and an ad indefinitely and never see a prompt to upgrade. ## What Premium adds Premium adds exactly one thing: the sixth cell, with zero ads anywhere in the grid. That's the entire difference. The catalog is the same, custom URLs work the same, every cell control behaves the same, and Companion support for stubborn sites is identical on both plans. Premium doesn't unlock a hidden feature — it unlocks space. Who reaches for that space varies, but a common pattern is one more thing that doesn't quite fit into the first five: a music player running quietly in the corner, a group chat you want visible without alt-tabbing, or a second calendar sitting next to your main one. If your first five cells already cover the essentials and you keep eyeing a sixth thing you'd add if you could, that's the plan Premium is built for. It's $4 a month, and that's the only cost — no setup fee, no per-cell pricing, no tiers above it. ## How billing works Billing runs entirely through Freemius. To upgrade, go to Settings and click **Go Premium** — that takes you to an upgrade page where you enter payment details directly with Freemius. TulipLot never sees your card number; there's no card form built into the app itself, because the app never needs to touch it. Managing or canceling works the same way, any time you want: Settings → **Manage billing** opens the Freemius customer portal. That's where your payment method, invoices, and cancellation all live — TulipLot hands the whole job to Freemius rather than building a second version of it. Before you confirm anything, the portal shows you exactly when your subscription ends, so there's no guessing about timing. Premium bills monthly, with no annual contract. ## What happens when you cancel Cancellation itself happens in the Freemius customer portal, and it tells you exactly when your subscription ends before you confirm. When that date arrives, your account reverts to the free plan automatically — no extra step required from you. Reverting means the bottom-right cell becomes the ad slot again. What happens to whatever was in your sixth cell depends on the rest of your grid at that moment: - **Sixth cell was empty.** There's nothing to move — it just becomes the ad cell, and your other five cells stay exactly where they are. - **Sixth cell had an app, and at least one of your other five cells was empty.** That app moves automatically into the first empty slot it finds. Your other cells don't shift; only the displaced app relocates. - **All six cells were full.** There's no empty slot to move it into, so TulipLot doesn't just delete it. Instead, it's parked, and you get a prompt to place it into a different slot — replacing whatever's there — or discard it if you'd rather not keep it. Either way, it's your call to make, not something that happens silently in the background. ## Questions before you decide ### Can I switch back and forth? Yes, any time, in either direction. Upgrade, downgrade, upgrade again later — there's no limit on how often you change plans, and no penalty for switching back after canceling. ### Do I lose my layout when upgrading? No. Upgrading leaves your existing five cells exactly where they are. The ad cell simply turns into a normal, usable sixth cell — nothing gets rearranged, and nothing you've already set up moves. ### Is there a trial? Two, really. [The try page](/try) needs no account at all: two live cells, no time limit. Free is the longer trial, and it doesn't expire either. Five full cells, the entire catalog, custom URLs, every cell control, no credit card required — you can run that setup for as long as you like before deciding whether the sixth cell is worth $4 a month to you. If you haven't built your grid yet, start with [Getting started with TulipLot](/guides/getting-started) — five minutes gets your first five cells filled, and you can decide about the sixth whenever you're ready. [Create a free account](/register) — no card, no deadline. --- # Why some sites won't load in a dashboard Source: https://tuliplot.com/guides/why-sites-wont-load/ Published: 2026-08-02 Updated: 2026-08-16 ## What "refused to connect" actually means Most tools that show one website inside another, including dashboards and widgets, do it through something called an iframe: a small window that displays one webpage inside another page, the same way a picture-in-picture video sits inside a bigger screen. If you've ever pasted a URL into one of these tools, you may have run into a page that won't load in an iframe at all: a message like "refused to connect" or "refused to display in a frame" instead of the site you expected. Here's the short version: that's not the tool breaking. It's the site you tried to load, saying no. Plenty of sites are perfectly happy to be shown this way. A specific minority send back an instruction first, one that tells the browser: do not display me inside anyone else's page. When that instruction arrives, the browser follows it and blocks the frame, and "refused to connect" is simply what that block looks like on your screen. Nothing you did caused this. The tool tried to load the site the normal way, the site's own instruction refused that request, and the browser respected the refusal instead of overriding it, because browsers aren't built to ignore it. The failure you're looking at is the site working exactly as its owner designed it, not a bug in whatever you were using to load it. The rest of this guide covers why sites that block embedding do it, what actually fixes it, and what stays out of reach no matter which tool you're using. ## The two headers behind it That refusal travels as a header: a small piece of information a website sends back alongside its page, invisible on the page itself but read by your browser before anything is displayed. Two different headers can carry this particular instruction, and knowing which one you're dealing with helps make sense of what you're seeing. [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options) is the older of the two, and it's blunt on purpose. A site sets it to one of two values: `DENY`, which blocks every attempt to frame it with no exceptions, or `SAMEORIGIN`, which allows framing only by pages on that exact same site. There's no middle ground and no list of trusted outsiders, just those two settings. [`Content-Security-Policy: frame-ancestors`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-ancestors) is the newer replacement, and it works more like a guest list. Instead of an on-or-off switch, a site names the exact sources allowed to frame it, anything from nobody at all to a short list of specific approved sites. A site that wants to block embedding entirely can still use it; it just leaves that list empty. ## Why sites do this — and why it's mostly reasonable These headers exist because of an attack called clickjacking. An attacker builds an ordinary-looking page, maybe one promising a prize or a video, and secretly loads a real site underneath it in an invisible frame, positioned exactly over a fake button. You think you're clicking "claim your prize." What you're actually clicking is the real site's own button, hidden right below the decoy: confirm a purchase, change a setting, approve a login. Your click is genuine. You just have no idea what it actually landed on. Blocking embedding shuts that down at the source. If a site refuses to be framed at all, there's no invisible layer for an attacker to hide underneath, and the trick has nothing left to work with. That's why the sites most likely to refuse are the ones with the most to lose from a stolen click: banks, anything tied to a login, anything that moves money or changes an account setting. Framing rules are a genuine defense against a real attack, aimed at attackers, not at dashboards. Tools that embed other sites just happen to sit on the other side of the same rule. ## The three things you'll see instead Strip away any particular tool, and every embedding attempt lands in one of three places. A site loads live, framed normally, and behaves like the real thing shrunk into a smaller window. A site needs a helper before it will embed: a browser extension you install once and then turn on for that specific site, after which it behaves like the first case. Or a site refuses no matter what, and the tool's only honest option is to hand you a one-click way to open it in its own tab instead. TulipLot mirrors those three outcomes exactly, and it's upfront about which sites land where. Trello is a catalog example of the first case: add it and it loads live in the grid with nothing extra to do. Notion and Hacker News are catalog examples of the second: they need the free TulipLot Companion extension installed once and enabled for that specific site before they'll embed. Gmail, Outlook, and Google Calendar are catalog examples of the third: they never embed, Companion or not, and each opens in its own tab as a one-click launcher instead. None of the three is a failure state. A cell that opens a launcher tab is doing exactly what that particular site allows, and it still gets you to the real app in one click. ## What actually fixes it (and what doesn't) For the second category, sites that need a helper, the fix is real and it works: a browser extension that adjusts those two headers, but only for the specific sites you choose. TulipLot's Companion is one example. It's free, Chrome-only, installed once from the Chrome Web Store, then switched on per site rather than granted blanket access to everything you visit. It changes the headers only on frames your own dashboard is loading, so an enabled site still behaves completely normally if you open it in an ordinary tab on its own, and it collects nothing about you or your browsing while it does it. For the third category, sites that refuse no matter what, nothing fixes it, and it helps to know why. Those sites layer protections well below the header level: login flows that check whether they're running as the top-level page, session cookies that only behave correctly outside a frame, scripts that detect framing and break themselves on purpose. An extension that edits response headers has nothing left to act on there. It's worth naming the workaround people reach for anyway: routing the site through a proxy, a middleman server that fetches the page for you and passes it along, stripping the blocking header before it arrives. That can work for a static page with nothing to log into. For a logged-in app, it doesn't. Your session, your cookies, and any two-factor check are tied to talking to the real site directly, so a proxy either leaves you logged out or asks you to hand your credentials to a server that isn't the app you're trying to use. Neither one is a fix. A launcher tab that opens the real, unmodified site is the honest answer for that category, not a proxy. ## The honest limits Some sites will never embed anywhere, in any tool, no matter which extension you install. Banks are the obvious case. Most Google properties are too, and Microsoft's Outlook behaves the same way, which is why Gmail, Outlook, and Google Calendar all open in their own tab rather than a frame. Some single sign-on flows, the shared login systems that let one account cover several separate apps, behave the same way, because the same deep protections that keep your session safe also keep it from working inside a frame. A launcher cell that opens a site in its own tab isn't a downgrade from the "real" experience. It is the real experience, one click away, with the site's own login and security fully intact instead of routed through something else. TulipLot would rather say that plainly than suggest a future update might unlock Gmail, because it won't: the limits described here aren't a gap waiting to be closed, they're the same protections keeping your accounts safe everywhere else you already use them. ## Common questions ### Is bypassing X-Frame-Options safe? Adjusting it the way a scoped browser extension does, one site at a time, inside your own browser only, is safe because of how narrow the change is. It doesn't touch the site for anyone else, it doesn't change how the site behaves for you outside that one dashboard, and it stops the moment you disable the site or remove the extension. What's genuinely risky is a general "turn off security headers everywhere" setting with no scoping at all, which is a much broader thing than what an extension like the Companion does. ### Why can't a website just fix this itself? In a real sense, it already has: refusing to be framed is the fix, not a bug waiting to be patched. A site could switch from a blanket `DENY` to a specific guest list under `frame-ancestors`, but maintaining that list is ongoing work, and one mistake in it reopens the exact clickjacking risk the header exists to close. Plenty of sites decide the simpler, blunter setting is worth the occasional dashboard inconvenience. ### Does this break the site's own security? No. A scoped extension changes headers only on frames that your own dashboard is loading, for sites you specifically enabled, inside your own browser. Anyone visiting that site directly, or using it on a different device, sees the exact same protections as before. Nothing about the site's actual defenses changes for anyone but you, and only inside the one tool you enabled it for. ### Will Gmail ever load in a dashboard? Not through TulipLot, and not through any tool that plays by the same rules, because Gmail's protection isn't only the header. It's built into how its login and session behave when it isn't the top-level page in your browser. That's not a limitation TulipLot is waiting to lift; it's why Gmail is a permanent one-click launcher instead, opening in its own tab with its real login fully intact. Most sites you add don't need any of this: they load live, on the first try, no extension and no explanation required. For the ones that do, [adding sites that refuse to be framed](/guides/add-any-site) walks through installing and enabling the TulipLot Companion, cell by cell. Haven't built a grid yet, or still sorting out how a dashboard differs from [what a browser start page is](/blog/what-is-a-browser-start-page)? [Getting started](/guides/getting-started) covers your first five minutes. And if you'd rather just see which of the three outcomes your own favorite sites land in, [create a free account](/register) and add them yourself. --- # What a Chrome extension can and cannot rewrite in a response Source: https://tuliplot.com/blog/chrome-declarativenetrequest-headers/ Published: 2026-09-04 Checked on 2026-08-24. A framing header is one line in a response. The browser reads it, refuses to display the page, and blanks the frame. That isn't permanent: the header lands in your own browser, and software running in your own browser can delete the line before the decision happens. That's what a header-rewriting extension is for, and on Chrome there's now exactly one supported way to build one. It's narrower than it looks, and when we measured one of its rule conditions it matched nothing at all in the case we cared about. ## What Manifest V3 took away Under Manifest V2 you registered a listener, marked it blocking, and Chrome handed you every request to read and rewrite. Chrome's summary of Manifest V3 gives the reason that stopped: "We are deprecating the blocking version of the webRequest API. This required extensions to proxy all network traffic to provide filtering capabilities, which came at a performance and privacy cost." The migration guide is specific about the performance half. "In Manifest V2, blocking web requests could significantly degrade both the performance of extensions and the performance of pages they work with," it says, because "The webRequest namespace supports nine potentially blocking events, each of which takes an unlimited number of event handlers." The guide stops there; our own reading is that each listener ran in the extension's own process, with a page waiting on several extensions at once. "Manifest V3 guards against this problem by replacing callbacks with declarative rules." The webRequest reference states the outcome for `webRequestBlocking` flatly: "As of Manifest V3, the ... permission is no longer available for most extensions." Observation survives; interception doesn't. One exception remains, and it isn't for the public — "Policy installed extensions can continue to use" the blocking permission. `declarativeNetRequest` is the replacement, and the trade sits in its opening paragraph. It "lets extensions modify network requests without intercepting them and viewing their content, thus providing more privacy." You describe conditions and actions in JSON. Chrome does the matching. You don't see the response. ## The rule that deletes a framing header A framing refusal is a response header, so you want `modifyHeaders` with a `responseHeaders` array. Three operations exist. `remove` "Removes all entries for the specified header." `set` "Sets a new value for the specified header, removing any existing headers with the same name." `append` "Adds a new entry for the specified header," and on the request side it's restricted — Chrome's reference says "The append operation is only supported for the following request headers" and then lists twenty-one of them, `cookie` and `user-agent` among them. Nothing similar constrains what you remove from a response. So the whole trick is small: ```json { "id": 1, "priority": 1, "action": { "type": "modifyHeaders", "responseHeaders": [ { "header": "x-frame-options", "operation": "remove" }, { "header": "content-security-policy", "operation": "remove" } ] }, "condition": { "resourceTypes": ["sub_frame"], "initiatorDomains": ["example.com"] } } ``` Two details deserve a second look, and the last line is a trap. Take the blunt one first. You can't remove a directive. You remove a header. [`frame-ancestors`](/blog/csp-frame-ancestors/) travels inside `Content-Security-Policy` alongside whatever else that policy says, so deleting the header to win the frame also deletes the site's script and style restrictions for that document. There's no operation that drops one directive and keeps the rest. You also can't delete just one of them and be finished. Of the 60 popular apps we measured, 53 refused to be framed: 42 sent [`X-Frame-Options`](/blog/x-frame-options-explained/), 24 sent a `frame-ancestors` directive, and 13 sent both. Remove one header from a site in that overlap and the other still refuses. ## Static, dynamic, and session rules Chrome offers three ruleset types. Lifetime separates them first. Static rules are "Packaged, installed, and updated when an extension is installed or upgraded" — they ship in JSON files named in the manifest. Dynamic rules "Persist across browser sessions and extension upgrades..." Session rules are "Cleared when the browser shuts down and when a new version of the extension is installed." The quotas are generous, and worth knowing early. An extension may declare 100 static rulesets, enable 50 at once, and rely on at least 30,000 static rules. Session rules cap at 5,000, safe and unsafe alike. Dynamic rules cap at 30,000 — but only for what Chrome calls safe rules, which are "rules with an action of block, allow, allowAllRequests or upgradeScheme." A `modifyHeaders` rule isn't one of those, so it counts against the unsafe limit of 5,000 instead. Regular-expression rules cap at 1,000 per type, and each rule must compile to under 2KB. One asymmetry matters more. The `tabIds` and `excludedTabIds` conditions are "Only supported for session-scoped rules." A static rule can't be scoped to a tab. Remember that; it becomes the fix. ## The condition that matched nothing `initiatorDomains` looks like the right way to keep your rule off everybody else's browsing. The reference describes it plainly: "The rule will only match network requests originating from the list of initiatorDomains. If the list is omitted, the rule is applied to requests from all domains." A note adds that "This matches against the request initiator and not the request url." Your page creates the frame, so your page is the initiator: name your domain and the rule won't touch anyone else. In August 2026 we measured otherwise. The finding is ours, not Chrome's, and it's worth stating exactly. On 2026-08-14, debugging an unpacked build of the TulipLot Companion extension in Microsoft Edge 151, a rule conditioned on `initiatorDomains` never fired for a frame that the page itself created. A main-world control frame, with `src` set before the element went into the document, was still refused. The same frame rendered the moment the initiator condition came out of the rule and nothing else changed. Frames injected from the extension's own isolated world *did* match, which is what had made every earlier experiment look successful. Chrome doesn't document this. We read the current reference again while writing, and it doesn't carry an exception for frames the page creates — the two sentences quoted above are the whole specification of the condition, and read plainly they promise a match. So treat this as one measurement, not a rule of the platform: one Chromium browser, one version, one afternoon. We haven't reproduced it on Google Chrome itself, and we haven't confirmed it as a Chromium defect. Two documented facts sit next to it without explaining it. MDN says "The initiator of a request is usually the document or worker that triggered the request" — *usually* is MDN's word, not ours. And Chrome's webRequest reference makes the initiator optional on every event, noting that "If this is an opaque origin, the string 'null' will be used." A request whose initiator isn't what you assumed is a shape the platform already allows for. Don't expect the debugging tools to settle it either. `testMatchOutcome` "Checks if any of the extension's declarativeNetRequest rules would match a hypothetical request," but you supply the initiator yourself, so it answers the question you already believe. It's also "Only available for unpacked extensions..." Its own field documentation hedges in the same direction: the initiator is "The initiator URL (if any) for the hypothetical request." Two conditions express what people usually mean by *only on my site*. `tabIds` scopes a session rule to specific tabs, which is what shipped here: one session rule, `resourceTypes` of `sub_frame`, and the tab IDs of open dashboard tabs. Since Chrome 145 there's also `topDomains`, which matches "when the associated top-level frame's domain matches one from the list of topDomains" — the top-level frame, not the initiator. It isn't initiator-free everywhere, though: "For requests with no associated top-level frame ... the request initiator's domain is considered instead." We haven't tested `topDomains` against this case, so treat it as the condition that names what you meant, not a verified fix. ## Why it asks about every site separately Permission is the other constraint, and it's the one users notice. Chrome ships two permissions with, in its words, "the same capabilities." Declaring `declarativeNetRequest` "Triggers a permission warning at install time but provides implicit access to allow, allowAllRequests and block rules." Declaring `declarativeNetRequestWithHostAccess` means "A permission warning is not shown at install time, but you must request host permissions before you can perform any action on a host." MDN draws the line where it actually falls. The first permission "allows extensions to block and upgrade requests without any host permissions," but "Host permissions are required if the extension wants to redirect requests or modify headers on requests..." Header rewriting sits on the side that costs a host permission. MDN also notes an exemption worth knowing: "For all requests, except for navigation requests (i.e., resource type `main_frame` and `sub_frame`), host permissions are also required for the request's initiator." So a header-rewriting extension has two honest options: ask for every site up front, or ask per site. Per site means listing broad patterns under `optional_host_permissions` and calling `permissions.request()` from a user gesture. Chrome "prompts the user if adding the permissions results in different warning messages than the user has already seen and accepted," which in practice is one dialog per new host. That friction is the feature. ## What the deletion still can't fix **A logged-out page.** Removing the framing header buys you a frame that paints. It buys nothing about session state, which is governed by `SameSite`, third-party cookie blocking, and partitioning — a separate set of rules, described in [why a site logs you out inside an iframe](/blog/cookies-inside-an-iframe/). We haven't tested whether a rule that rewrites `Set-Cookie` changes any of that, and we're not going to guess. **A refusal on the request.** If the server reads [`Sec-Fetch-Dest`](/blog/fetch-metadata-headers/) and answers 403, there's no page and therefore no response header to delete. That article carries the asymmetry in full: a proxy sits on the request side and can rewrite what the server is told; an extension editing a response arrives after the decision. Chrome's own note makes the timing concrete: rules with a response-header condition run after delivery, and the reference warns that "the request has already been sent to the server and the server has received data like the request body." ## Questions ### Can an extension unblock any site? No. It handles sites that refuse by header, and 53 of the 60 we measured do — but it won't touch logins that redirect to another host, or pages that compare `window.top` with `window.self`. [Can I embed this site in an iframe?](/can-i-embed/) has the per-site results. ### Static rule or session rule? Static rules are simpler and survive restarts. Choose a session rule when you can't fix the scope until runtime — a tab ID is the clear case, since `tabIds` works nowhere else. ### My rule works in a test page but not in production. Why? Check what creates the frame. Our own bug looked exactly like this in Edge 151: a frame injected by extension code matched the rule, and the identical frame created by the page didn't. --- # Clickjacking, the attack that framing headers exist to stop Source: https://tuliplot.com/blog/clickjacking-explained/ Published: 2026-09-04 Checked on 2026-08-24. Most of the web apps people actually want to embed refuse to be put inside an iframe. Of the 60 we measured on 2026-08-24 for [can I embed this site in an iframe?](/can-i-embed/), 53 blocked it and 7 allowed it. That ratio looks like paranoia until you know what the refusal is for. It's for one attack — old, simple, and still working wherever the defenses are missing. ## The attack in one sentence RFC 7034, the document that wrote down `X-Frame-Options`, defines it precisely: clickjacking "occurs when an attacker uses multiple transparent or opaque layers in the user interface to trick a user into clicking on a button or link on another page from server B when they were intending to click on the same place of the overlaying page from server A." OWASP, which catalogs it under the alternate name "UI redress attack," puts the same thing in the language of consequences: the attacker is "'hijacking' clicks meant for their page and routing them to another page, most likely owned by another application, domain, or both." The important word in the RFC's version is *intending*. The click is real. Your finger moved, and the browser dispatched a genuine, user-initiated event to a real page. Nothing was forged. You were just looking at the wrong thing. ## What it looks like, built up piece by piece We'll describe this in prose rather than ship a working page. There's no reason to hand out a template. Start with the attacker's page. It's ordinary: some copy, an image, and a big button promising something worth a click — a prize, a captcha step, a "click to continue" gate. Now add an iframe pointing at a real application, one the visitor's probably signed into. Give it absolute positioning so it can go anywhere on the page, and set its opacity to zero. It's invisible but not unrendered; `display: none` would do that, and a page nobody renders receives no clicks. It's fully laid out, fully live, and fully transparent. Next comes the alignment, the only fiddly part. The attacker loads the real application in a normal tab and notes where the control worth stealing sits. They can't scroll the framed copy — a cross-origin `contentWindow` doesn't expose that — so they wrap the iframe in a small `overflow: hidden` box and shift it inside by negative offsets until the wanted control is the part showing. A fragment in the `src` does a coarser version of the same job. Finally, the stacking. The invisible frame goes *above* the decoy in the paint order, so it is the frame — not the attacker's own button — under the cursor. The visitor sees the decoy, because the layer covering it is transparent. The browser sees the frame, because it is on top. The visitor clicks. The event goes to the real application, in a real session, and the application does what it was asked. The attacker's page never reads a byte of the response, and doesn't need to. ## Why the same-origin policy does not save you This surprises people who assume cross-origin isolation covers it. The same-origin policy stops the attacker's script *reading* the framed page: it can't see the DOM, the text, the cookies, or the response. All of that holds. What it doesn't stop is the framed page being *positioned*, *sized*, *clipped*, or *made transparent* — those are properties of the container, and the container belongs to the attacker. So the attacker works blind and still wins. They don't need a view of the page, only the victim's pointer landing on a known coordinate. ## Variants worth recognizing **Keystroke theft.** OWASP records the same trick applied to typing: "With a carefully crafted combination of stylesheets, iframes, and text boxes, a user can be led to believe they are typing in the password to their email or bank account, but are instead typing into an invisible frame controlled by the attacker." **Likejacking.** OWASP documents real campaigns against social features — attacks "abusing Facebook's 'Like' functionality" that could "trick logged-in Facebook users to arbitrarily like fan pages, links, groups," and a Twitter worm where the stolen click "caused them to re-tweet the location of the malicious page, and propagated massively." The informal name for that family is likejacking: the same attack with a cheap payload and a viral loop attached. **Cursorjacking.** This attacks the pointer rather than the page: hide the real cursor and draw a fake one at an offset, so what the visitor aims at and what the browser clicks are different places. We describe the mechanism rather than any incident, because we could not verify the usual attributions from a primary source, and browser handling of custom cursors has changed since. Underneath all three is one shape: the victim's intent is captured somewhere other than where they're looking. ## Three defenses, strongest first OWASP's Clickjacking Defense Cheat Sheet counts the two headers as one mechanism, then `SameSite` cookies, then a frame-buster. The headers aren't equally strong, so each gets an entry here, and `SameSite` has a section of its own below. **1. `frame-ancestors`.** A Content Security Policy directive naming who may frame you, with `'none'` meaning nobody. OWASP is blunt: `frame-ancestors 'none'` "is recommended unless a specific need has been identified for framing." It's the strongest because the browser refuses the load before the page exists, and because it takes a guest list rather than a yes-or-no switch. [The frame-ancestors directive](/blog/csp-frame-ancestors/) covers the syntax and the four ways a source list quietly matches nothing. **2. `X-Frame-Options`.** The legacy header, two working values. It's weaker for one reason: it fails open. A browser meeting a value it doesn't recognize behaves as though the header were never sent, so a stale `ALLOW-FROM` line offers no protection while looking like it does. Send it alongside the directive for old clients, and know that current browsers ignore it whenever an enforced `frame-ancestors` is present. [X-Frame-Options explained](/blog/x-frame-options-explained/) has the history. **3. A frame-buster script.** JavaScript that checks whether it's the top window and, if not, breaks out. OWASP still publishes one for legacy browsers, and its shape shows how carefully it must be written: a `