<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nico Acosta</title>
    <description>The latest articles on DEV Community by Nico Acosta (@nico_acosta_bc7dceb59f65e).</description>
    <link>https://dev.to/nico_acosta_bc7dceb59f65e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978200%2F8f88872d-ec24-49b7-ab54-afee796bed22.png</url>
      <title>DEV Community: Nico Acosta</title>
      <link>https://dev.to/nico_acosta_bc7dceb59f65e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nico_acosta_bc7dceb59f65e"/>
    <language>en</language>
    <item>
      <title>Screenshot API Alternatives: An Honest Comparison (2026)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:39:27 +0000</pubDate>
      <link>https://dev.to/grabbit/screenshot-api-alternatives-an-honest-comparison-2026-2a0k</link>
      <guid>https://dev.to/grabbit/screenshot-api-alternatives-an-honest-comparison-2026-2a0k</guid>
      <description>&lt;p&gt;If you are comparing screenshot API alternatives, you have almost certainly hit one of three walls: a monthly quota that resets before you use it, a feature the tool does not have, or an integration path that does not fit how you actually build. This is an honest, side-by-side comparison of the main options, with a clear note on where each one wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Every screenshot API does the same core job: send a URL, get back a rendered image. They differ on three things that actually matter, so pick by whichever is your bottleneck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Billing model.&lt;/strong&gt; Monthly plans reset unused capacity; a flat annual plan does not. This is the biggest hidden cost for spiky or seasonal workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-grab rate.&lt;/strong&gt; Browserless and Thum.io list the lowest rates. Neither is a one-call REST endpoint for every feature, so cheap-per-grab comes with a trade-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration.&lt;/strong&gt; A raw REST endpoint, a first-party SDK for your language, or an agent-callable MCP tool are three different developer experiences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grabbit is the alternative when monthly waste and agent onboarding are your pain: &lt;strong&gt;$0.002 per capture, 25,000 prepaid credits per year that never reset or expire&lt;/strong&gt;, plus a built-in MCP server. It is not the cheapest option overall, and this post says so plainly below.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the alternatives compare
&lt;/h2&gt;

&lt;p&gt;Prices verified June 2026 from each provider's public pricing page, normalized to roughly 10,000 grabs per month. "Included" is each provider's entry-plan allowance. The honest caveat up front: &lt;strong&gt;two providers list a lower per-grab rate than Grabbit.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Per grab&lt;/th&gt;
&lt;th&gt;Billing&lt;/th&gt;
&lt;th&gt;Included&lt;/th&gt;
&lt;th&gt;Monthly waste&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grabbit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;td&gt;Annual flat&lt;/td&gt;
&lt;td&gt;25,000 / yr&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;One-line + MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScreenshotOne&lt;/td&gt;
&lt;td&gt;$0.0079&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;2,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urlbox&lt;/td&gt;
&lt;td&gt;$0.0066&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;2,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ApiFlash&lt;/td&gt;
&lt;td&gt;$0.0035&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;1,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScrapingBee&lt;/td&gt;
&lt;td&gt;$0.0049 †&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;~10,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserless&lt;/td&gt;
&lt;td&gt;$0.0013 ‡&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;20,000 units / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thum.io&lt;/td&gt;
&lt;td&gt;$0.0002&lt;/td&gt;
&lt;td&gt;Usage metered&lt;/td&gt;
&lt;td&gt;$1–$20 / mo min.&lt;/td&gt;
&lt;td&gt;Minimum spend&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;† ScrapingBee is credit-based (roughly 25 credits per screenshot grab). ‡ Browserless bills in units (roughly 1 unit per grab) and is a managed browser you drive with your own scripts, not a one-call REST endpoint.&lt;/p&gt;

&lt;p&gt;Browserless and Thum.io are cheaper per grab. The trade-offs are real: Browserless is a headless browser you drive with Puppeteer or Playwright, so you still write and maintain the capture code, and Thum.io has a narrow feature set (no full-page capture, no wait controls, no selector targeting). If raw per-grab price is the only thing you care about, one of those two is the honest recommendation, not Grabbit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the alternative to the pain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If monthly resets are wasting money
&lt;/h3&gt;

&lt;p&gt;On a monthly reset plan, a team that captures 1,500 pages in January and 9,000 in March pays the full monthly allowance both months, and January's unused capacity evaporates. You pad the plan to cover the busy month and forfeit the difference every quiet one. Flat annual prepaid credits remove that pressure: you buy a block once, it covers actual usage, and nothing expires at the end of a cycle. For variable workloads (OG images on publish, monitoring runs, batch research), the billing model often matters more than the headline rate. See &lt;a href="https://www.grabbit.live/blog/screenshot-api-pricing" rel="noopener noreferrer"&gt;why monthly screenshot quotas waste money&lt;/a&gt; for the full math.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you need a first-party SDK
&lt;/h3&gt;

&lt;p&gt;ScreenshotOne maintains client libraries for Ruby, PHP, Go, Java, C#, and more. If you want a supported SDK in your language rather than a raw HTTP call, that is a genuine advantage and a good reason to stay with it. Grabbit is a plain REST endpoint plus an MCP server, not a per-language SDK set.&lt;/p&gt;

&lt;h3&gt;
  
  
  If an AI agent needs to see the page
&lt;/h3&gt;

&lt;p&gt;Most screenshot APIs were designed before agent frameworks existed: you get an API key and call the endpoint from backend code, which means custom glue for every agent that needs visual data. Grabbit and ScreenshotOne both ship an MCP server, so an agent registers capture as a callable tool and gets back a hosted &lt;code&gt;image_url&lt;/code&gt; with no glue code. If agent onboarding is why you are shopping, prefer a provider with a one-line or MCP path over a REST-only endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you just want the lowest possible rate
&lt;/h3&gt;

&lt;p&gt;Browserless ($0.0013) or Thum.io ($0.0002). Accept that Browserless means writing your own browser scripts and Thum.io means a thin feature set. That is the trade you make for the lowest number in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try an alternative before you pay
&lt;/h2&gt;

&lt;p&gt;You can wire up Grabbit and run it in CI before adding a card. Every account gets a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge; switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) for real captures. Here is a real request against Grabbit's API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Set &lt;code&gt;full_page: false&lt;/code&gt; with a fixed &lt;code&gt;height&lt;/code&gt; (240 to 1080) for a viewport shot, add &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Point at a &lt;code&gt;sk_test_&lt;/code&gt; key first, so migrating an existing integration costs nothing to prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monthly reset waste, variable volume:&lt;/strong&gt; Grabbit (flat annual, no reset).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest per-grab rate, willing to script:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple thumbnails, minimal budget:&lt;/strong&gt; Thum.io.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-party SDKs for a non-JS backend:&lt;/strong&gt; ScreenshotOne.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An AI agent needs to call it:&lt;/strong&gt; Grabbit or ScreenshotOne (both ship MCP).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your comparison is really "managed cloud browser vs. a screenshot endpoint," see the &lt;a href="https://www.grabbit.live/blog/browserbase-alternative" rel="noopener noreferrer"&gt;Browserbase alternative breakdown&lt;/a&gt;. For a wider field than this table, read our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt;. For Grabbit's pricing, parameters, and agent onboarding, see &lt;a href="https://www.grabbit.live/screenshots-as-a-service" rel="noopener noreferrer"&gt;screenshots as a service&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/screenshot-api-alternatives" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>The Best Spec-Driven Development Tools in 2026 (Tested &amp; Compared)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:23:45 +0000</pubDate>
      <link>https://dev.to/braingrid/the-best-spec-driven-development-tools-in-2026-tested-compared-2cah</link>
      <guid>https://dev.to/braingrid/the-best-spec-driven-development-tools-in-2026-tested-compared-2cah</guid>
      <description>&lt;p&gt;Adding a spec-driven development tool does not make your development spec-driven. Most of them just generate a folder of markdown, hand it to the same agent that was already guessing, and call the ceremony a method.&lt;/p&gt;

&lt;p&gt;That gap is exactly what a builder in r/AI_Agents was circling this week when the whole "loop engineering" idea got picked apart. His verdict on the wave of new agent-workflow tooling was blunt: &lt;a href="https://www.reddit.com/r/AI_Agents/comments/1vlanp8/wait_am_i_just_an_idiot_or_is_all_the_talk_about/" rel="noopener noreferrer"&gt;Cron jobs with a shiny new UI are still cron jobs&lt;/a&gt;. He is right about most of them, and the same critique lands on half the "spec-driven" category. A markdown template with a shiny new UI is still a markdown template.&lt;/p&gt;

&lt;p&gt;So which of these tools actually change the outcome, and which just add paperwork? We use spec-driven development to ship our own product every day, so this is the field guide we wish existed: what each tool really does, who it fits, and the one axis that separates the ones that work from the ones that generate homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hypothesis: the spec is the easy half
&lt;/h2&gt;

&lt;p&gt;Here is the claim this guide is built to test. &lt;strong&gt;The value of a spec-driven development tool is not in writing the spec. It is in what happens after.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing a spec has never been the hard part. In an r/LLMDevs thread this week on where the real bottleneck sits, one builder described his workflow, having the AI write explicit input/output/behavior contracts for every function before implementing anything, and added the line that says everything: &lt;a href="https://www.reddit.com/r/LLMDevs/comments/1vh749h/comments/" rel="noopener noreferrer"&gt;90% of vibe coders still don't do it&lt;/a&gt;. Someone asked what prompt would backfill that discipline into an existing codebase. The reply was two words: it's called spec-driven development.&lt;/p&gt;

&lt;p&gt;That is the tell. People reinvent this practice from scratch, hit the wall, and only then learn it already has a name and a category of tools. The category is real. But most of the tools stop at the easy half. They help you produce a beautiful spec, then hand the spec to the same coding agent that will report "done" the moment it stops running, whether or not the build matches a single line you wrote.&lt;/p&gt;

&lt;p&gt;The tools worth your time are the ones that close the loop: spec, build, and then verify the build against the spec before anyone calls it done. Keep that axis in mind as we go through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Spec Kit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt; is the reference implementation of the category, an open-source CLI toolkit from GitHub that walks your coding agent through a structured pipeline: Constitution, Specify, Clarify, Plan, Tasks, Implement. It writes markdown templates that plug into Claude Code, Copilot, Gemini CLI, and most other agents, so it is agent-agnostic by design.&lt;/p&gt;

&lt;p&gt;Its strength is also its ceiling. Spec Kit is a scaffolding for the workflow, not a product that runs it for you. You get discipline and a shared vocabulary, which is genuinely valuable, but you are still driving the whole thing from a terminal, and the verification step is whatever your agent decides to do on its own. It fits engineers who want structure without a new IDE. If you have a repo and you are comfortable in the CLI, start here. We wrote a full &lt;a href="https://www.braingrid.ai/blog/github-spec-kit-tutorial-existing-project" rel="noopener noreferrer"&gt;Spec Kit walkthrough for an existing project&lt;/a&gt; if you want the hands-on version.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSpec
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Fission-AI/OpenSpec" rel="noopener noreferrer"&gt;OpenSpec&lt;/a&gt; is the lightweight answer to Spec Kit's ceremony. It breaks a high-level prompt into granular, ordered task lists and keeps a set of markdown files in sync as the agent works, so it stays out of your way. If Spec Kit felt like too much process for a solo project, OpenSpec is the trimmed-down version that still gives the agent a plan to follow instead of a vibe.&lt;/p&gt;

&lt;p&gt;The trade-off is that lighter also means less opinionated about what "done" means. You get a cleaner task breakdown and less overhead, and you give up the fuller lifecycle framing. It fits solo builders and small teams who want just enough structure to stop the agent from wandering. We put it head to head with Spec Kit in &lt;a href="https://www.braingrid.ai/blog/openspec-vs-spec-kit-vs-braingrid" rel="noopener noreferrer"&gt;OpenSpec vs Spec Kit vs BrainGrid&lt;/a&gt; if you want the direct comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro (and the spec-driven IDE camp)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, Amazon's spec-driven IDE, represents the other end of the spectrum: a full VS Code-style environment built around specs. You describe requirements in natural language, Kiro generates user stories, design docs, and steering files, and you check off implementation against them inside the editor. It is the most "product" of the open toolkits, with interactive UI to track features from requirement to code.&lt;/p&gt;

&lt;p&gt;The cost is lock-in and weight. You are adopting an IDE, not a workflow you can bolt onto the tools you already use. For a developer who wants a native, self-contained spec-driven environment, that is a fair trade. For a non-technical founder, an IDE is still an IDE, with all the friction that implies. We compared its philosophy to ours in &lt;a href="https://www.braingrid.ai/blog/kiro-vs-braingrid-spec-driven-development" rel="noopener noreferrer"&gt;Kiro vs BrainGrid&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  BMAD and the framework crowd
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bmad-code-org/BMAD-METHOD" rel="noopener noreferrer"&gt;BMAD-METHOD&lt;/a&gt; and the growing shelf of SDD frameworks (Spec Kitty, Agent OS, Tessl, and more) are for people who want to assemble their own pipeline from parts. They give you agent personas, planning phases, and templates you wire together yourself. Powerful in the right hands, and a rabbit hole in the wrong ones.&lt;/p&gt;

&lt;p&gt;This is where a warning belongs. The moment picking a framework becomes its own project, the tool has failed at its job. If you have spent a weekend comparing steering-doc syntaxes instead of shipping a feature, &lt;a href="https://www.braingrid.ai/blog/stop-shopping-for-spec-driven-development-tools" rel="noopener noreferrer"&gt;you are shopping for tools instead of building&lt;/a&gt;. The framework crowd fits engineers who genuinely enjoy building their own harness. Most builders do not, and should not have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract-and-API-spec category
&lt;/h2&gt;

&lt;p&gt;There is a second, older meaning of "spec-driven" that shows up in the same searches: API-first tools like &lt;a href="https://swagger.io/tools/swaggerhub/" rel="noopener noreferrer"&gt;SwaggerHub&lt;/a&gt;, &lt;a href="https://specmatic.in/" rel="noopener noreferrer"&gt;Specmatic&lt;/a&gt;, and &lt;a href="https://typespec.io" rel="noopener noreferrer"&gt;TypeSpec&lt;/a&gt;. These treat an OpenAPI or contract file as the source of truth and enforce it in CI before code ships. They are excellent, and they solve a different problem: the contract between services, not the requirement behind a feature. If your pain is "my agent keeps breaking the API between the frontend and backend," this is your aisle. If your pain is "my agent built the wrong thing confidently," it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;Everything above is strong at producing a spec. The question our own hypothesis forces is what happens next, and that is where we built &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; to be different.&lt;/p&gt;

&lt;p&gt;You describe an idea in plain English, and the Planning Agent turns it into a structured requirement with acceptance criteria, data models, and designs, asking clarifying questions and pushing back when the intent is vague. That is the part most tools stop at. Then the Builder Agent takes over: it runs the build against that spec, either in a managed cloud sandbox with a live preview or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP. And then the part almost nobody automates happens. Verification checks the build against every acceptance criterion, and the feature is not done until the evidence says it matches what you asked for.&lt;/p&gt;

&lt;p&gt;That closes the loop the r/codex thread was worried about this week, where builders debated whether smarter agents still need review and landed on a hard truth: &lt;a href="https://www.reddit.com/r/codex/comments/1vkscm1/apparently_now_that_codexcc_are_getting_better_we/" rel="noopener noreferrer"&gt;they do not own mistakes&lt;/a&gt;, so someone still has to prove the work is right. A spec you can verify against is how you prove it without reading every line of code yourself. The spec is not paperwork. It is the standard the build gets checked against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old way vs new way
&lt;/h2&gt;

&lt;p&gt;The difference between using a spec-driven tool and actually doing spec-driven development comes down to one habit.&lt;/p&gt;

&lt;p&gt;Old way: "Build me a CRM with React." The agent generates something, reports success, and you find out at feature four that the auth logic conflicts with the state management it wrote in feature two.&lt;/p&gt;

&lt;p&gt;New way: a requirement that says a contact-management view displays contacts in a sortable table with name, company, and last-contacted date; clicking a row opens a detail panel; only authenticated users can access it; unauthenticated users redirect to /login. Every one of those lines is a criterion the build can be checked against. The agent still moves fast. It just cannot quietly ship the wrong thing.&lt;/p&gt;

&lt;p&gt;The tool that generates the second version for you is doing useful work. The tool that also verifies the build against it is doing the whole job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for you
&lt;/h2&gt;

&lt;p&gt;If you are building a SaaS product with an AI coding agent right now, here is the concrete implication. Adopting any tool on this list will make your third and fourth features less likely to break, because the agent finally has a plan and a shared definition of the feature. That alone is worth it.&lt;/p&gt;

&lt;p&gt;But do not stop at the spec. The failure mode this whole category exists to fix is not "the agent had no plan." It is "the agent said done and it wasn't." Pick your tool on the verification axis. If two tools produce equally good specs, the one that checks the build against the spec is the one that saves you the afternoon you would have spent discovering the gap in production.&lt;/p&gt;

&lt;p&gt;And the honest trade-off: spec-driven development is slower on feature one. You are front-loading the thinking. The payback comes at feature five, when your codebase is still coherent and your agent is still building the thing you asked for instead of the thing it guessed. If you are shipping a one-day throwaway, skip all of it and vibe. If you are building something real, the spec is the cheapest insurance you will buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is spec-driven development?
&lt;/h3&gt;

&lt;p&gt;Spec-driven development is a workflow where you write a structured specification, requirements, constraints, and acceptance criteria, before an AI agent writes code, and the spec becomes the source of truth the build is measured against. It replaces "prompt, hope, and patch" with "specify, build, and verify." The point is not the document. It is that a clear spec gives the agent something concrete to build toward and something concrete to be checked against. We cover the full method in &lt;a href="https://www.braingrid.ai/blog/spec-driven-development" rel="noopener noreferrer"&gt;Spec-Driven Development: Ship Reliable Software Faster with AI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the best spec-driven development tools?
&lt;/h3&gt;

&lt;p&gt;For agent-agnostic structure from the CLI, GitHub Spec Kit is the reference tool. For a lighter task-focused workflow, OpenSpec. For a full spec-driven IDE, Kiro. For assembling your own pipeline, frameworks like BMAD. For API contracts specifically, SwaggerHub or TypeSpec. And for a workflow that plans, builds, and then verifies the build against acceptance criteria in one loop, BrainGrid. The right pick depends on whether you want scaffolding you drive yourself or a system that closes the loop for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between spec-driven development tools?
&lt;/h3&gt;

&lt;p&gt;They differ mainly on two axes: how much they do for you, and whether they verify the result. Spec Kit and OpenSpec are scaffolding you drive from a terminal. Kiro is a full IDE. Framework kits like BMAD are parts you assemble. Most of them stop after generating the spec and planning the work. The meaningful divide is verification: does the tool check the finished build against the spec, or does it trust the agent's own "done"?&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between BDD and spec-driven development?
&lt;/h3&gt;

&lt;p&gt;Behavior-driven development (BDD) focuses on describing expected behavior as executable scenarios ("given, when, then") that become automated tests. Spec-driven development is broader: the spec covers requirements, constraints, data models, and acceptance criteria, and drives both the build and its verification, not only the test suite. BDD scenarios can live inside a spec-driven workflow as one form of acceptance criteria. SDD is the wider frame; BDD is one technique for expressing part of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a spec-driven tool, or can I just write a good prompt?
&lt;/h3&gt;

&lt;p&gt;A single good prompt works for a single small feature. It stops working once a project has many moving parts, because nothing carries your intent from one session to the next and nothing checks the result. A spec-driven tool exists to make that intent durable and verifiable. If you keep re-explaining your app to the agent every session, or keep discovering broken features you thought were done, you have outgrown prompting and the category is for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the AI Product Planner that turns your idea into a spec your coding agent can build against, then verifies the build matches it before you call it done. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/spec-driven-development-tools" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Verify AI-Generated Code (Before It Breaks Production)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Tue, 11 Aug 2026 11:17:15 +0000</pubDate>
      <link>https://dev.to/braingrid/how-to-verify-ai-generated-code-before-it-breaks-production-2h0h</link>
      <guid>https://dev.to/braingrid/how-to-verify-ai-generated-code-before-it-breaks-production-2h0h</guid>
      <description>&lt;p&gt;The agent said it was done. It ran the app, showed you a green result, and wrote a tidy summary of what it built. None of that tells you whether the code does what you asked. Those are two different questions, and the gap between them is where production breaks.&lt;/p&gt;

&lt;p&gt;Here is the hypothesis this guide rests on: verifying AI-generated code is not reading it line by line, it is checking it against a target you defined before the build. The more of your building an agent does, the less "I read it and it looked fine" scales, and the more you need a repeatable way to prove the change matches intent. The good news is that this is a learnable process, not a talent. The bad news is that almost nobody is taught it, so most builders default to eyeballing a demo and hoping.&lt;/p&gt;

&lt;h2&gt;
  
  
  "It runs" is not "it works"
&lt;/h2&gt;

&lt;p&gt;An AI coding agent optimizes for the shortest path to something that looks complete. Ask for a login flow and it will build the happy path, run it once, watch a user log in, and call that done. It is not lying. It genuinely did produce a thing that works, for the one case it tried. What it did not do is check the wrong password, the expired session, the empty email field, or the second user whose data must not leak into the first user's account. Those are the cases that surface in production, at the worst possible time, in front of a real person.&lt;/p&gt;

&lt;p&gt;This is the distinction &lt;a href="https://x.com/stas_sorokin_/status/2083480323476656211" rel="noopener noreferrer"&gt;Stanislav Sorokin named cleanly&lt;/a&gt;, writing about agent runs on X:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Completion is a runtime event. Success is a verified state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the whole problem in nine words. The agent stopping is a fact about the loop. Whether it succeeded is a separate fact that has to be established on purpose, and the agent will not establish it for you unless you told it what success means. Verification is how you turn "it stopped" into "it works," and it is the step the demo quietly skips.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five checks that actually catch things
&lt;/h2&gt;

&lt;p&gt;When developers on &lt;a href="https://www.reddit.com/r/ExperiencedDevs/comments/1rzq738/what_tools_and_techniques_are_you_using_to_verify/" rel="noopener noreferrer"&gt;r/ExperiencedDevs compared notes&lt;/a&gt; on verifying AI code, the useful answers were not "read every line." They were specific, repeatable checks. Here is the shortlist that catches the most, in the order that catches it fastest.&lt;/p&gt;

&lt;p&gt;Start with intent, not code. Before you look at a single function, restate what the change was supposed to do and confirm the code is even solving that problem. Agents drift: you asked for a filter and got a sort, or the feature works but silently changed an unrelated screen. Catching a wrong-problem build here saves you from carefully reviewing code that was never going to be right.&lt;/p&gt;

&lt;p&gt;Run the tests, and then read them. A passing suite feels like proof until you notice the agent wrote tests that assert the happy path and nothing else. The test that matters is the one for the case you are worried about, and if it is missing, the green checkmark is meaningless. Read what the tests actually assert before you trust that they passed.&lt;/p&gt;

&lt;p&gt;Exercise the edge cases by hand. Wrong password. Empty input. The second concurrent user. The thing that was working yesterday. Agents are strong on the path they were shown and weak on the paths they were not, so five minutes of deliberately trying to break it finds more than an hour of reading finds.&lt;/p&gt;

&lt;p&gt;Check it in a real environment, not the demo. &lt;a href="https://www.linkedin.com/posts/addyosmani_ai-programming-softwareengineering-activity-7338086913477627905-hO2w" rel="noopener noreferrer"&gt;Addy Osmani put the rule plainly&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Always test AI-generated code in a sandbox before merging.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A demo runs on the machine that just built it, with the data that happens to be loaded. A sandbox that looks like production surfaces the hardcoded path, the missing environment variable, and the assumption that only holds on the developer's laptop. This is exactly where the "it works on my machine" class of bug lives.&lt;/p&gt;

&lt;p&gt;Diff for collateral damage. AI agents edit more than you asked. Look at the full change, not just the new feature, and ask what else moved. A refactor that "cleaned up" a shared function may have broken three callers you did not think to test.&lt;/p&gt;

&lt;p&gt;Notice what these have in common: not one of them is "understand every line the agent wrote." You verify against behavior and intent, not against a code-reading marathon you do not have time for. That is the shift. You are not auditing the agent's homework, you are proving the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification needs a target, and the target is the receipt
&lt;/h2&gt;

&lt;p&gt;Here is the reframe. Every one of those checks assumes you already know what "correct" looks like. The edge cases you test, the behavior you confirm, the intent you check against, all of it depends on a &lt;a href="https://www.braingrid.ai/blog/definition-of-done-for-ai-builders" rel="noopener noreferrer"&gt;definition of done&lt;/a&gt; that existed before the build started. If that definition lives only in your head, verification becomes a vibe, and a vibe does not scale past the third feature.&lt;/p&gt;

&lt;p&gt;A builder in &lt;a href="https://www.reddit.com/r/ClaudeCode/comments/1vdmg1n/" rel="noopener noreferrer"&gt;r/ClaudeCode described the fix&lt;/a&gt; without naming a product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need a receipt for each agent. That receipt, verified, becomes the record.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the missing piece. A receipt is a checkable claim: "this feature is done because these specific conditions were confirmed." Written before the build, it is the &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;acceptance criteria the agent builds toward&lt;/a&gt;. Verified after, it is the evidence that the build matches intent. Without receipts, you are left staring at a diff trying to reconstruct what the change was supposed to prove. With them, verification is mechanical: check the change against each criterion, and a criterion either passed or it did not.&lt;/p&gt;

&lt;p&gt;Compare the two ways of asking an agent to be done.&lt;/p&gt;

&lt;p&gt;Vague: "Add login and make sure it works."&lt;/p&gt;

&lt;p&gt;With a receipt: "Add email and password login. A valid pair signs the user in and redirects to /dashboard. A wrong password shows an inline error and does not redirect. An empty field is rejected before submit. A logged-in user hitting /login is redirected to /dashboard. Only the authenticated user's own records are returned."&lt;/p&gt;

&lt;p&gt;The first gives the agent nothing to verify against, so it invents its own bar and clears it. The second is five checkable statements. The agent builds toward them, and you (or a verifier) confirm each one with evidence instead of opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;This is the layer &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; works in. You describe a feature in plain language and the Planning Agent turns it into structured requirements with acceptance criteria written to be checkable, the "when this, the system shall that" statements that double as the receipt. Then the Builder Agent builds against those criteria, in a BrainGrid-managed cloud sandbox with a live preview, or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP. A feature is not marked done until verification checks the build against every criterion and the evidence says it matches intent.&lt;/p&gt;

&lt;p&gt;That closes &lt;a href="https://www.braingrid.ai/loop" rel="noopener noreferrer"&gt;the loop&lt;/a&gt; the five checks are reaching for. Plan the receipt, build against it, verify with evidence, repeat. The intent is captured as a durable record instead of a sentence in a chat window that scrolls away, so the thing you verify against outlives the session that created it. Everyone else sells speed of generation. The point of a receipt is certainty of outcome.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A["Agent says&amp;lt;br/&amp;gt;'done'"] --&amp;gt; B{"Verify against&amp;lt;br/&amp;gt;what?"}
  B --&amp;gt;|"No receipt"| C["Eyeball the demo,&amp;lt;br/&amp;gt;hope"]
  C --&amp;gt; D["Bug surfaces&amp;lt;br/&amp;gt;in production"]
  B --&amp;gt;|"Receipt exists"| E["Check each&amp;lt;br/&amp;gt;acceptance criterion"]
  E --&amp;gt; F["Evidence per&amp;lt;br/&amp;gt;criterion"]
  F --&amp;gt; G["Done means&amp;lt;br/&amp;gt;verified"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;Verification is not free. Writing acceptance criteria before the build and checking them after is real work, and for a throwaway script or a weekend experiment it is overkill. If you are prototyping to learn whether an idea is worth pursuing, skip the receipts and move fast. The cost is justified the moment the code is going to be used by someone other than you, or is going to be built on top of next week. That is the line: verify what has to be trusted, not what you are about to throw away. Applying the full process to a one-off is how you make AI building feel slower than it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for you
&lt;/h2&gt;

&lt;p&gt;If you are building a SaaS product with Claude Code or Cursor right now, this means your verification bottleneck is not the reading, it is the missing target. The reason &lt;a href="https://www.braingrid.ai/blog/why-reviewing-ai-code-costs-more-than-writing-it" rel="noopener noreferrer"&gt;reviewing AI code feels endless&lt;/a&gt; is that you are reconstructing intent at review time instead of defining it at plan time. Move that definition earlier. Write down what done means as checkable statements before the agent starts, and the after-the-fact review collapses from "read everything and hope I catch it" into "confirm these specific things passed." You pay the clarity cost once, up front, instead of paying the review tax on every feature forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action steps
&lt;/h2&gt;

&lt;p&gt;For your next AI-built feature, run this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before the build, write 3 to 6 checkable statements of what "done" means, including the edge cases you are worried about.&lt;/li&gt;
&lt;li&gt;Have the agent build against those statements, not a one-line prompt.&lt;/li&gt;
&lt;li&gt;Run the tests, then read what they assert, and add the missing edge-case test.&lt;/li&gt;
&lt;li&gt;Exercise the failure paths by hand: wrong input, empty input, second user, the thing that worked yesterday.&lt;/li&gt;
&lt;li&gt;Test in a production-like environment, never just the demo that built it.&lt;/li&gt;
&lt;li&gt;Diff the full change and confirm nothing outside the feature moved.&lt;/li&gt;
&lt;li&gt;Check the result against each statement from step one. Anything not confirmed is not done.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you validate AI-generated code?
&lt;/h3&gt;

&lt;p&gt;Validate it against a definition of "done" you wrote before the build, not by reading every line after. Confirm the code solves the intended problem, run and read the tests, exercise the edge and failure cases by hand, test it in a production-like sandbox rather than the demo environment, and diff the full change for edits outside the feature. Each check confirms behavior against intent instead of auditing the agent's code line by line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a way to identify what an AI agent actually changed?
&lt;/h3&gt;

&lt;p&gt;Yes, and you should always look. Review the complete diff, not just the new feature the agent describes. AI coding agents frequently edit shared functions, refactor unrelated code, or adjust configuration as a side effect. Reading the full set of changes is how you catch collateral damage in code the agent did not mention in its summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does AI-generated code pass tests but still break in production?
&lt;/h3&gt;

&lt;p&gt;Because the agent often writes tests that assert only the happy path it already built, so the suite passes without covering the cases that fail. A green checkmark proves the tests that exist passed, not that the right tests exist. Read what the tests actually assert, and add coverage for the edge cases and failure paths you care about before trusting the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to read AI-generated code to verify it?
&lt;/h3&gt;

&lt;p&gt;Not line by line. Effective verification checks behavior against a defined target: does it do what you asked, does it handle the cases you specified, does it work in a real environment. You will read specific code when a check fails and you need to find out why, but the goal is proving the result against acceptance criteria, not understanding every function the agent wrote.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the fastest way to verify AI code before merging?
&lt;/h3&gt;

&lt;p&gt;Test it in a sandbox that resembles production, then check the change against the acceptance criteria you defined before the build. If those criteria exist, verification is mechanical: each one either passed with evidence or it did not. If they do not exist, you are reconstructing intent at review time, which is exactly why reviewing AI code so often takes longer than writing it.&lt;/p&gt;

&lt;p&gt;The shift is simple to state and hard to skip: define what done means before the agent builds, and verification stops being a guess. It works because it gets more necessary as models get better, not less. More autonomy means more decisions nobody wrote down, and evidence is how you trust what you did not read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first app-building platform that turns your idea into checkable requirements, builds against them, and verifies every one with evidence. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/how-to-verify-ai-generated-code" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Browserbase Alternative: When You Need a Screenshot, Not a Whole Cloud Browser</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:43:50 +0000</pubDate>
      <link>https://dev.to/grabbit/browserbase-alternative-when-you-need-a-screenshot-not-a-whole-cloud-browser-dc1</link>
      <guid>https://dev.to/grabbit/browserbase-alternative-when-you-need-a-screenshot-not-a-whole-cloud-browser-dc1</guid>
      <description>&lt;p&gt;If you are searching for a Browserbase alternative, the first question worth asking is not "which vendor," it is "do I actually need a cloud browser?" Browserbase gives your code a full, programmable Chrome running in the cloud: persistent sessions, live interaction, stealth, the works. That is the right tool when you are driving a browser through multi-step flows. It is a lot of machinery when all you needed was a picture of a page.&lt;/p&gt;

&lt;p&gt;This is an honest comparison, including an explicit section on when you should stay on Browserbase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Browserbase is cloud browser infrastructure for AI agents and automation: you open a session, connect over CDP or an SDK, and operate a real browser remotely. It is built to run whole interaction flows, and it bills accordingly (by session and concurrency).&lt;/p&gt;

&lt;p&gt;If your job is narrower, capture a rendered screenshot of a URL, then a &lt;strong&gt;screenshot API&lt;/strong&gt; is the simpler and usually cheaper alternative. You send one request with a URL and get back a hosted image. There is no session to open, keep alive, or tear down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;Grabbit&lt;/a&gt; is that alternative when the job is a screenshot: &lt;strong&gt;$0.002 per capture&lt;/strong&gt; on flat prepaid credits that never reset or expire monthly, plus a built-in MCP server so an agent can call it as a tool. Grabbit is not a cloud browser and does not pretend to be one, and this post says plainly below when Browserbase is the correct choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different jobs that look similar
&lt;/h2&gt;

&lt;p&gt;The reason "Browserbase alternative" is a confusing search is that a screenshot and a browser session look related but are different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A cloud browser&lt;/strong&gt; is a live, stateful thing. You navigate, click, type, wait, read the DOM, maybe hand control to a human. You are &lt;em&gt;operating&lt;/em&gt; the page. Browserbase, Browserless, Hyperbrowser, Anchor Browser, and Steel all sell this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A screenshot API&lt;/strong&gt; is a stateless request. You name a URL and how you want it rendered, and you get an image. You are &lt;em&gt;observing&lt;/em&gt; the page, not operating it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the "Browserbase alternatives" you will find in a SERP are other cloud browsers, which makes sense if you truly need to drive a browser. But a large share of the traffic to that search is people who reached for a cloud browser because they thought a screenshot required one. It does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they compare for the screenshot job
&lt;/h2&gt;

&lt;p&gt;Scoped to "I need a rendered image of a URL," not to full browser automation. Cloud-browser pricing is session or minute based, so a per-grab comparison is only fair for the capture use case. Prices verified August 2026 from each provider's public pricing page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Billing shape&lt;/th&gt;
&lt;th&gt;Best when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grabbit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screenshot API&lt;/td&gt;
&lt;td&gt;Per grab ($0.002), flat prepaid&lt;/td&gt;
&lt;td&gt;You need a rendered image of a URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserbase&lt;/td&gt;
&lt;td&gt;Cloud browser&lt;/td&gt;
&lt;td&gt;Per session / concurrency&lt;/td&gt;
&lt;td&gt;You drive a real browser through steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserless&lt;/td&gt;
&lt;td&gt;Cloud browser + REST screenshot&lt;/td&gt;
&lt;td&gt;Per unit / monthly&lt;/td&gt;
&lt;td&gt;You want full control and self-host options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyperbrowser&lt;/td&gt;
&lt;td&gt;Cloud browser (containers)&lt;/td&gt;
&lt;td&gt;Per session / monthly&lt;/td&gt;
&lt;td&gt;Lightweight isolated sessions, CAPTCHA handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest read: if you need persistent sessions and live interaction, do not pick a screenshot API, pick a cloud browser. If you need a picture of a page, do not rent a whole browser to take it. Grabbit is also &lt;strong&gt;not the cheapest screenshot API&lt;/strong&gt; in the field; Browserless and Thum.io list lower per-grab rates. Grabbit's edge is the fit and the billing model, prepaid credits that never expire, not a bottom-of-market rate. For the wider field of screenshot APIs specifically, see our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the billing shapes differ
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the cost comparison feel unfair until you see it: you are paying for different things.&lt;/p&gt;

&lt;p&gt;A cloud browser holds a real Chrome open for you. You pay for the time that browser is alive and for how many you run at once, because keeping browsers healthy at concurrency is the expensive, hard part (memory creep, restart storms, sticky workers). That cost is real and Browserbase absorbs it so you do not lose weekends to it.&lt;/p&gt;

&lt;p&gt;A screenshot API runs the render, uploads the image, and is done. There is no session to keep warm between calls, so it bills per capture. For a workload that is "render this URL, give me the image," paying for session time is paying for a capability you are not using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it before you commit
&lt;/h2&gt;

&lt;p&gt;You can wire up Grabbit and run it in CI before adding a card. Every account gets a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge; switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) when you want real captures. Here is a real request against Grabbit's API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://www.browserbase.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Use &lt;code&gt;full_page: true&lt;/code&gt; for a scrolling capture, &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content to settle, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Because test captures are free placeholders, prototyping the swap from a cloud-browser screenshot call to Grabbit costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  For AI agents
&lt;/h2&gt;

&lt;p&gt;Many people arrive at Browserbase because an agent needs to work with the web, and "the agent needs the web" gets rounded up to "the agent needs a whole browser." Sometimes it does: if the agent clicks, types, and navigates across steps, that is a cloud-browser job and Browserbase (or an agent framework on top of it) is the right call.&lt;/p&gt;

&lt;p&gt;But a large slice of agent-web work is just &lt;em&gt;seeing&lt;/em&gt; a page: feed the rendered view into a vision step, verify your own frontend, or ground the model in what a URL actually looks like. For that, a screenshot API is the lighter path. Grabbit ships an &lt;strong&gt;MCP server&lt;/strong&gt;, so the agent registers capture as a callable tool and gets back a hosted &lt;code&gt;image_url&lt;/code&gt; to pass downstream, with no browser session to manage. For the agent-eyes use case in depth, see &lt;a href="https://www.grabbit.live/blog/screenshots-for-ai-agents" rel="noopener noreferrer"&gt;screenshots for AI agents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stay on Browserbase
&lt;/h2&gt;

&lt;p&gt;An honest comparison names when not to switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need multi-step interaction.&lt;/strong&gt; Logging in, filling forms, clicking through a flow, reading the DOM between steps. A screenshot API captures a page; it cannot operate one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need persistent, authenticated sessions.&lt;/strong&gt; Holding a logged-in browser open across calls is exactly what a cloud browser is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You use an interactive agent framework.&lt;/strong&gt; Tools like Stagehand drive a real browser. A screenshot endpoint is not a substitute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need live takeover or stealth.&lt;/strong&gt; Human-in-the-loop control and advanced anti-bot handling are cloud-browser features, not screenshot-API features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grabbit's pitch is not "we replace Browserbase." It is narrower and honest: if the job is a screenshot, you do not need a whole cloud browser to take it, and paying per session for a per-image job is waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need a rendered image of a URL:&lt;/strong&gt; a screenshot API (Grabbit, or a cheaper-per-grab option if raw rate is all that matters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You drive a browser through multi-step flows:&lt;/strong&gt; stay on Browserbase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want a cloud browser but with self-host options:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent needs to see, not operate, a page:&lt;/strong&gt; Grabbit (one call + MCP).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent needs to click and navigate:&lt;/strong&gt; Browserbase or an interactive framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Grabbit's pricing, parameters, and agent onboarding, see the &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; page. To schedule captures without running any browser yourself, see &lt;a href="https://www.grabbit.live/blog/screenshot-automation" rel="noopener noreferrer"&gt;screenshot automation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/browserbase-alternative" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>EARS Notation, Explained: The Requirements Syntax Behind Every AI Spec</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:14:29 +0000</pubDate>
      <link>https://dev.to/braingrid/ears-notation-explained-the-requirements-syntax-behind-every-ai-spec-28f1</link>
      <guid>https://dev.to/braingrid/ears-notation-explained-the-requirements-syntax-behind-every-ai-spec-28f1</guid>
      <description>&lt;p&gt;A requirements syntax invented in 2009 for jet engine software is quietly becoming the house style for AI coding tools. It was designed for a room full of engineers arguing over a spec. Its best audience turned out to be a machine that never argues.&lt;/p&gt;

&lt;p&gt;EARS notation, short for the Easy Approach to Requirements Syntax, came out of Rolls-Royce when &lt;a href="https://alistairmavin.com/ears/" rel="noopener noreferrer"&gt;Alistair Mavin&lt;/a&gt; and colleagues got tired of aerospace requirements that read like legal fog. The &lt;a href="https://ieeexplore.ieee.org/document/5328509/" rel="noopener noreferrer"&gt;2009 IEEE paper&lt;/a&gt; has been cited over 500 times. For fifteen years it lived in systems engineering, the discipline that writes requirements for things that kill people when they fail. Then the AI tools arrived, and EARS started showing up where nobody in 2009 would have predicted: in the specs that coding agents read before they build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What EARS notation actually is
&lt;/h2&gt;

&lt;p&gt;EARS is a constraint on how you write a requirement, not a new document format or a tool you install. The idea is that most bad requirements are bad in the same few ways, so you give authors a small set of sentence templates that make the common mistakes impossible. Every EARS requirement follows the same clause order:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While &lt;code&gt;&amp;lt;precondition&amp;gt;&lt;/code&gt;, when &lt;code&gt;&amp;lt;trigger&amp;gt;&lt;/code&gt;, the &lt;code&gt;&amp;lt;system&amp;gt;&lt;/code&gt; shall &lt;code&gt;&amp;lt;response&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the whole grammar. The clauses always appear in that order, most are optional, and the word "shall" carries the actual behavior. From that one skeleton, five patterns cover almost everything you need to say.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;ubiquitous&lt;/strong&gt; requirement is always true, no condition attached: "The system shall encrypt all stored passwords." An &lt;strong&gt;event-driven&lt;/strong&gt; requirement fires on a trigger: "When a user submits the signup form, the system shall send a verification email." A &lt;strong&gt;state-driven&lt;/strong&gt; requirement holds while something is true: "While a payment is processing, the system shall disable the submit button." An &lt;strong&gt;unwanted-behavior&lt;/strong&gt; requirement handles the sad path, the one everyone forgets: "If the payment provider returns an error, then the system shall display the failure reason and preserve the cart." And an &lt;strong&gt;optional-feature&lt;/strong&gt; requirement scopes to a configuration: "Where two-factor authentication is enabled, the system shall require a code on login."&lt;/p&gt;

&lt;p&gt;Read those back. Notice what the template forced you to do. You named the exact trigger, the exact state, the exact system, and the exact response. There is no room for "the app should probably handle errors gracefully," because the grammar has no slot for "probably" or "gracefully." That constraint is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reframe: it was built for humans, it pays off with agents
&lt;/h2&gt;

&lt;p&gt;Here is the hypothesis this post rests on. EARS was designed to make requirements unambiguous for a human reader, and it did that job for fifteen years. But its value goes up, not down, the moment the reader is an AI agent, because an agent takes the sentence literally in a way a human colleague never quite does.&lt;/p&gt;

&lt;p&gt;When a human reads "the system should handle invalid input," they fill the gap with judgment. They have seen a hundred forms, they know what validation usually means, and they quietly do the reasonable thing. That gap-filling is a feature when the reader is a senior engineer and a liability when the reader is an agent, because the agent also fills the gap, just not necessarily with your version of reasonable. It picks a plausible interpretation, writes code against it, and the demo runs. The mismatch surfaces three weeks later when a real user pastes an emoji into a field you never specified.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/github/spec-kit/issues/1356" rel="noopener noreferrer"&gt;GitHub Spec Kit team is actively debating EARS integration&lt;/a&gt; for exactly this reason. The issue puts it plainly: EARS gives authors sentence templates that help them write specifications AI agents can parse more reliably. Amazon's Kiro already generates its acceptance criteria in EARS by default, &lt;a href="https://www.braingrid.ai/blog/kiro-vs-braingrid-spec-driven-development" rel="noopener noreferrer"&gt;one of two very different takes on spec-driven development&lt;/a&gt;. The tools converging here are not doing it out of nostalgia for a 2009 aerospace standard. They are doing it because a constrained sentence is a better instruction to a literal machine than a fluent paragraph is.&lt;/p&gt;

&lt;p&gt;This is the same lesson the AI building community keeps rediscovering in its own words. Steinberger's line that your job is to &lt;a href="https://www.braingrid.ai/blog/loop-engineering" rel="noopener noreferrer"&gt;design the loops that prompt your agents&lt;/a&gt; is really a statement about inputs: the loop is only as good as the target you hand it. EARS is one disciplined way to write that target so the agent and the verifier read it the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vague requirement versus EARS requirement
&lt;/h2&gt;

&lt;p&gt;The difference is easiest to see side by side. Take a single feature, password reset, written the way it usually reaches an agent versus written in EARS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vague: "Users should be able to reset their password securely."&lt;/p&gt;

&lt;p&gt;EARS: "When a user requests a password reset, the system shall send a reset link to the account email within one minute. The reset link shall expire 30 minutes after issue. When a user opens a valid reset link, the system shall allow a new password to be set. If a user opens an expired or already-used link, then the system shall display 'This link is no longer valid' and offer to send a new one. When a password reset completes, the system shall invalidate all existing sessions for that account."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first version is a wish. It has one word, "securely," doing an enormous amount of undefined work. The second version is a set of facts you can check. Each "shall" is either true of the running app or it is not, with no room to argue. An agent can build against it, and just as important, a verifier can measure the result against it without asking anyone what "securely" was supposed to mean.&lt;/p&gt;

&lt;p&gt;That is the connection most explainers miss. A well-formed EARS requirement is already an acceptance criterion. The syntax that makes intent unambiguous is the same syntax that makes "done" checkable. Write the requirement in EARS and you have not just specified the feature, you have specified the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A["Vague intent:&amp;lt;br/&amp;gt;'handle errors gracefully'"] --&amp;gt; B["Agent fills the gap&amp;lt;br/&amp;gt;with a guess"]
  B --&amp;gt; C["Demo runs,&amp;lt;br/&amp;gt;looks done"]
  C --&amp;gt; D["Bug surfaces&amp;lt;br/&amp;gt;in production"]
  E["EARS requirement:&amp;lt;br/&amp;gt;'If X, then the system&amp;lt;br/&amp;gt;shall do Y'"] --&amp;gt; F["Agent builds against&amp;lt;br/&amp;gt;a literal target"]
  F --&amp;gt; G["Verifier checks&amp;lt;br/&amp;gt;each 'shall'"]
  G --&amp;gt; H["Done means&amp;lt;br/&amp;gt;verified"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;This is the layer &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; works in. You describe a feature in plain language, "let users reset their password," and the Planning Agent turns it into structured requirements with acceptance criteria written to be checkable, the EARS-shaped "when this, the system shall that" statements rather than a paragraph of hope. Then the Builder Agent, working in a cloud sandbox or in your own GitHub repo through Claude Code, Cursor, or Codex, builds against those criteria, and a feature is not done until each one is verified with evidence.&lt;/p&gt;

&lt;p&gt;The point is not that you must memorize five patterns and hand-write "shall" sentences all day. Most builders never learn the acronym, and they should not have to. The point is that the discipline EARS encodes, name the trigger, name the system, name the exact response, cover the unwanted path, is exactly the discipline that separates a spec an agent can build reliably from a prompt it has to guess at. BrainGrid captures that intent as a durable record so the plan outlives the chat window, instead of decaying the moment the context fills up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;EARS is not free, and pretending it is would be dishonest. Constrained syntax is slower to write than a fluent sentence, and it can feel stiff, almost robotic, when you first switch to it. "When a user submits the form, the system shall validate the email field" is less pleasant to read than "the form should check the email." Mavin's own work concedes EARS is a starting discipline, not a universal law: some requirements, particularly complex interacting behaviors, do not fold neatly into one template, and forcing them can make things worse rather than clearer. It is a floor for quality, not a ceiling.&lt;/p&gt;

&lt;p&gt;It also does not verify anything on its own. A perfectly formed EARS requirement that nobody checks is still just a nicely worded wish. The syntax makes "done" definable. Something still has to do the defining-versus-reality comparison, which is the verification step, not the writing step. EARS gets you a target the machine can read. It does not pull the trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you build with agents
&lt;/h2&gt;

&lt;p&gt;If you hand your coding agent one-line prompts and get back plausible code that breaks on the second user, the fix is not a smarter model. It is a less ambiguous instruction. You do not need to adopt EARS formally to get the benefit. You need to write requirements the way EARS forces you to: state the trigger, state the exact behavior, and always write the "if this goes wrong, then the system shall" case, because that is the one your agent will otherwise invent for you.&lt;/p&gt;

&lt;p&gt;A requirements syntax built for jet engines turns out to be the right shape for the age of coding agents, for a reason that has nothing to do with aerospace. The literal reader rewards the precise writer. EARS just happens to be the cleanest way anyone has found to write precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is EARS notation?
&lt;/h3&gt;

&lt;p&gt;EARS notation, the Easy Approach to Requirements Syntax, is a structured way to write natural-language requirements using a small set of keywords and a fixed clause order. Developed by Alistair Mavin and colleagues at Rolls-Royce in 2009, it constrains every requirement to the shape "While &lt;code&gt;&amp;lt;precondition&amp;gt;&lt;/code&gt;, when &lt;code&gt;&amp;lt;trigger&amp;gt;&lt;/code&gt;, the &lt;code&gt;&amp;lt;system&amp;gt;&lt;/code&gt; shall &lt;code&gt;&amp;lt;response&amp;gt;&lt;/code&gt;," which removes the ambiguity that makes ordinary requirements hard to build and impossible to test cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the five EARS patterns?
&lt;/h3&gt;

&lt;p&gt;The five patterns are ubiquitous (always true: "the system shall encrypt all stored passwords"), event-driven (triggered: "when a user submits the form, the system shall send an email"), state-driven (conditional on a state: "while a payment is processing, the system shall disable the button"), unwanted behavior (error handling: "if the provider returns an error, then the system shall display the failure"), and optional feature (scoped to a configuration: "where 2FA is enabled, the system shall require a code"). Almost every requirement you need to write fits one of these five shapes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is EARS used in spec-driven development and AI coding?
&lt;/h3&gt;

&lt;p&gt;Because an AI agent reads a requirement literally, and a constrained sentence is a more reliable instruction than a fluent paragraph. Tools like Amazon's Kiro generate acceptance criteria in EARS, and the GitHub Spec Kit team is debating adding it, precisely because EARS templates produce specs that agents parse consistently. A well-formed EARS requirement is also already an acceptance criterion, so the same syntax that makes intent unambiguous makes "done" checkable, which is the core of &lt;a href="https://www.braingrid.ai/spec-driven-development" rel="noopener noreferrer"&gt;spec-driven development&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between EARS notation and acceptance criteria?
&lt;/h3&gt;

&lt;p&gt;They overlap heavily. EARS is a syntax for writing any requirement clearly; an acceptance criterion is a condition that defines when a feature is done. The connection is that a requirement written in EARS is usually already a good acceptance criterion, because "when X, the system shall Y" is both a statement of intent and a checkable test. For the full craft of writing criteria an agent can verify, see &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;how to write acceptance criteria an AI agent can actually verify&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I have to use EARS to write good requirements for AI agents?
&lt;/h3&gt;

&lt;p&gt;No. EARS is one disciplined way to hit the target, not the only one. What matters is that your requirement names the exact trigger, the exact system, the exact response, and the unwanted-path behavior, which is precisely the discipline EARS enforces. You can get the same benefit by writing that way without ever learning the acronym, which is essentially what BrainGrid's Planning Agent does for you when it turns a plain-language idea into structured, verifiable criteria.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first platform that turns your idea into requirements and acceptance criteria your agent can build and verify against. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/ears-notation" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Monthly Screenshot Quotas Waste Your Money (Screenshot API Pricing, Explained)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:42:15 +0000</pubDate>
      <link>https://dev.to/grabbit/why-monthly-screenshot-quotas-waste-your-money-screenshot-api-pricing-explained-1l04</link>
      <guid>https://dev.to/grabbit/why-monthly-screenshot-quotas-waste-your-money-screenshot-api-pricing-explained-1l04</guid>
      <description>&lt;p&gt;If you are comparing screenshot API pricing, the pricing pages all look roughly the same: a few tiers, a monthly price, a screenshot quota per tier, and overage per extra capture. The number that decides your real cost is not on the card. It is the quota that resets every month, and how much of it you actually use. On a spiky workload, a monthly reset plan makes you pay for a peak you hit occasionally and forfeit the rest. This post explains the mechanic, then shows how to read a pricing page so you buy captures instead of empty allowance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Most screenshot APIs bill a &lt;strong&gt;fixed monthly subscription with a reset quota&lt;/strong&gt;: you pay for, say, 2,000 captures a month whether you make 2,000 or 200, and next month the counter goes back to zero. That model is fine when your usage is steady. It quietly wastes money when your usage is &lt;strong&gt;spiky&lt;/strong&gt;, which screenshot workloads almost always are.&lt;/p&gt;

&lt;p&gt;The waste is the gap between what you provision and what you use. To cover an occasional busy month you size up your plan, then forfeit the unused allowance in every quiet month. The sticker price hides that gap.&lt;/p&gt;

&lt;p&gt;Grabbit takes the other approach: &lt;strong&gt;$50 per year for 25,000 prepaid captures&lt;/strong&gt; ($0.002 each) that &lt;strong&gt;never reset or expire monthly&lt;/strong&gt;. Grabbit is not the cheapest screenshot API on headline price, and this post is explicit about that below. The point here is the pricing &lt;em&gt;model&lt;/em&gt;, not a rock-bottom rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How screenshot API pricing usually works
&lt;/h2&gt;

&lt;p&gt;Walk a typical pricing page and you will see one of three billing models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monthly subscription with a reset quota (the common one).&lt;/strong&gt; A tier bundles a fixed number of captures per month. Unused captures do not roll over. Go over and you pay overage per extra grab, or get blocked until next cycle. This is what most listings on a "screenshot api pricing" search use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage metered.&lt;/strong&gt; You pay per request with a small monthly minimum. No quota to waste, but the minimum spend is its own small floor, and metered plans often have a thinner feature set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prepaid credits.&lt;/strong&gt; You buy a block of captures once. They draw down as you use them and do not expire on a monthly boundary. Grabbit sells these on a flat annual plan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The three models can show nearly identical per-grab math on paper and produce very different bills, because the reset quota charges you for allowance, not for work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a reset quota wastes money on spiky workloads
&lt;/h2&gt;

&lt;p&gt;Screenshot volume is rarely a flat line. The real pattern looks like bursts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A batch of Open Graph images generated the day you publish a dozen posts.&lt;/li&gt;
&lt;li&gt;A visual monitoring run that fires on a schedule, then nothing until the next one.&lt;/li&gt;
&lt;li&gt;A one-off research crawl capturing a list of competitor pages, then quiet.&lt;/li&gt;
&lt;li&gt;An agent pipeline that captures heavily during a task and idles between tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put concrete numbers on it. Say you capture &lt;strong&gt;1,500 pages in January, 9,000 in March, and a few hundred most other months.&lt;/strong&gt; To not get blocked in March, you provision a plan sized for roughly 9,000 captures a month. In January you pay for that whole allowance and use a sixth of it. Every quiet month, you pay for thousands of captures you never make. Over a year, the forfeited allowance can dwarf the cost of the captures you actually took.&lt;/p&gt;

&lt;p&gt;That is the mechanic. &lt;strong&gt;A reset quota bills your peak every month and hands you nothing back for the troughs.&lt;/strong&gt; The lower the ratio of your average month to your busiest month, the more a monthly plan overcharges you.&lt;/p&gt;

&lt;p&gt;Prepaid credits invert it: you buy a block once, draw it down at your real pace, and the busy month and the quiet month both bill only the captures you made. For variable capture volume, the billing model usually matters more than a fraction of a cent on the per-grab rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat: cheapest sticker price is not Grabbit
&lt;/h2&gt;

&lt;p&gt;A "screenshot api pricing" search is full of pages claiming the lowest number, so here is the honest version. Grabbit is &lt;strong&gt;not&lt;/strong&gt; the cheapest screenshot API by headline price. Some providers advertise entry plans from $5 to $9 per month, and on raw per-grab rate &lt;strong&gt;Browserless ($0.0013)&lt;/strong&gt; and &lt;strong&gt;Thum.io ($0.0002)&lt;/strong&gt; both list lower numbers than Grabbit's $0.002.&lt;/p&gt;

&lt;p&gt;The trade-offs behind those lower numbers are real: Browserless is a managed headless browser you drive with your own Puppeteer or Playwright scripts rather than a single REST call, and Thum.io has a limited feature set (no full-page capture, no wait controls, no selector targeting). If the only thing you care about is the lowest per-grab rate and you are willing to script, one of those is the honest pick, not Grabbit.&lt;/p&gt;

&lt;p&gt;Grabbit's claim is narrower: flat annual billing with &lt;strong&gt;zero monthly reset waste&lt;/strong&gt;, and one-line agent onboarding. If your pain is a quota you keep forfeiting, that is the thing that saves you money, not the sticker price. Prices here were verified June 2026 from each provider's public pricing page, normalized to roughly 10,000 grabs per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "temporary need" is the category's real pricing problem
&lt;/h2&gt;

&lt;p&gt;There is a deeper reason the reset-quota model fits this category badly. The screenshot API market skews toward &lt;strong&gt;temporary needs&lt;/strong&gt;: a project that ends, a marketing job that ships, a migration that finishes. When the market leader ScreenshotOne was publicly torn down in 2026, the founder said the category's core problem in the open: there is no single ICP it can scale with, and customers churn because the need was temporary. A monthly subscription is the worst possible fit for a temporary need, you keep paying after the work is done until you remember to cancel.&lt;/p&gt;

&lt;p&gt;Prepaid credits that never expire suit a temporary-or-spiky need better: you buy what the job needs, use it when the job runs, and nothing keeps billing you between jobs. (Whether that changes long-run retention is a separate question we are not claiming to have answered, it is a better fit for the buying pattern, full stop.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read a screenshot API pricing page
&lt;/h2&gt;

&lt;p&gt;When you land on a pricing page, get past the sticker price and check five things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reset or roll over?&lt;/strong&gt; Does unused quota carry forward, or evaporate at the cycle boundary? This is the single biggest driver of real cost on spiky usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effective cost per capture &lt;em&gt;you make&lt;/em&gt;.&lt;/strong&gt; Divide the plan price by the captures you realistically make in an average month, not by the quota. That is your true per-grab cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overage behavior.&lt;/strong&gt; Do you pay per extra grab, or get hard-blocked until next cycle? A hard block can break a publish or a monitoring run at the worst moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What the free tier excludes.&lt;/strong&gt; Free plans often gate full-page capture, custom viewports, or output formats. Confirm the free number is a real preview of the paid product, not a stripped-down one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature parity across tiers.&lt;/strong&gt; Some providers put the parameters you need (selector targeting, wait delays, WebP) behind a higher tier, so the cheap plan cannot actually do your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the integration before you pay a cent
&lt;/h2&gt;

&lt;p&gt;You do not have to guess. Grabbit gives every account a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge, so you can wire up the API and run it in CI before adding a card. Switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) when you want real captures. Here is a real request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Add &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Point at a &lt;code&gt;sk_test_&lt;/code&gt; key first: test captures are free placeholders, so proving out an integration costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spiky or seasonal volume, quota waste is your pain:&lt;/strong&gt; a flat prepaid plan like Grabbit (no monthly reset).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steady, predictable monthly volume:&lt;/strong&gt; a monthly subscription wastes nothing, and the per-grab math is what it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest per-grab rate, willing to write scripts:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple thumbnails, minimal budget:&lt;/strong&gt; Thum.io.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low, flat volume and you enjoy owning infra:&lt;/strong&gt; self-host Puppeteer or Playwright.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a full field comparison, see our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt; and the head-to-head &lt;a href="https://www.grabbit.live/blog/screenshotone-alternative" rel="noopener noreferrer"&gt;ScreenshotOne alternative&lt;/a&gt; breakdown. If an agent is what needs to capture pages, the &lt;a href="https://www.grabbit.live/blog/screenshots-for-ai-agents" rel="noopener noreferrer"&gt;screenshots for AI agents&lt;/a&gt; guide covers the integration path. For Grabbit's pricing, parameters, and onboarding, see &lt;a href="https://www.grabbit.live/screenshots-as-a-service" rel="noopener noreferrer"&gt;screenshots as a service&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/screenshot-api-pricing" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>What Is a CLAUDE.md File? The One Anthropic's Own Docs Tell You to Keep Short</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:13:59 +0000</pubDate>
      <link>https://dev.to/braingrid/what-is-a-claudemd-file-the-one-anthropics-own-docs-tell-you-to-keep-short-3l7o</link>
      <guid>https://dev.to/braingrid/what-is-a-claudemd-file-the-one-anthropics-own-docs-tell-you-to-keep-short-3l7o</guid>
      <description>&lt;p&gt;Everyone tells you to write a CLAUDE.md file, and almost nobody tells you what it is actually for. So you create one, paste in everything you can think of, and watch your agent behave beautifully for a week. Then it starts ignoring the file you spent an afternoon on, and a thread titled &lt;a href="https://www.reddit.com/r/ClaudeAI/comments/1nokaln/what_is_the_point_of_claudemd/" rel="noopener noreferrer"&gt;"What is the point of CLAUDE.md?"&lt;/a&gt; with 180 comments suddenly makes total sense.&lt;/p&gt;

&lt;p&gt;Here is the claim worth sitting with. A CLAUDE.md file is not your project's memory. It is a set of standing instructions the model reads at the start of a session, and the more you treat it like a durable record of what your product is supposed to be, the faster it turns into a liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a CLAUDE.md file actually is
&lt;/h2&gt;

&lt;p&gt;A CLAUDE.md file is a plain Markdown file that &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; reads automatically at the start of every session. It goes in your project's root, you write it by hand, and it holds the things the agent would otherwise have to guess: your tech stack, your build and test commands, your coding conventions, the folders it should not touch. Anthropic's own docs describe it as persistent instructions for a project. The keyword there is instructions, not knowledge.&lt;/p&gt;

&lt;p&gt;That distinction is the whole post. The file is prepended to the conversation, so it costs context tokens on every single turn. It is not a database the agent queries when it needs something. It is a preamble it carries the entire time, whether the current task needs those rules or not. Which is exactly why the advice that actually works, once you get past "just write one," is to keep it short.&lt;/p&gt;

&lt;p&gt;The counterintuitive part is that cutting the file down often makes the agent better, not worse. One builder &lt;a href="https://x.com/kingwilliam_/status/2083596741719261535" rel="noopener noreferrer"&gt;reported cutting roughly 80% of his Claude Code context&lt;/a&gt; and getting sharper results, echoing guidance from Anthropic's own team that less instruction beats more. That reframes the file from a place to hoard context into a budget you spend carefully. A 400-line CLAUDE.md is not a well-documented project. It is a 400-line tax on every request, and past a certain point the model starts skimming it, which is the exact behavior the Reddit thread was complaining about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes inside a CLAUDE.md file
&lt;/h2&gt;

&lt;p&gt;The useful contents are narrow and stable. Think of the small set of facts that are true today and will still be true next month: the frameworks you use, the command to run tests, the command to build, the linter you enforce, the one architectural rule you never want violated ("all API calls go through the client in &lt;code&gt;lib/api&lt;/code&gt;, never fetch directly"). These earn their place because they change slowly and apply to nearly every task.&lt;/p&gt;

&lt;p&gt;Here is the difference between a file that helps and one that rots, side by side.&lt;/p&gt;

&lt;p&gt;Rots: "We migrated the auth flow last Tuesday and the new endpoint is &lt;code&gt;/v2/login&lt;/code&gt;, but the old one still works for now, and Sarah is refactoring the session handling this sprint so check with her before touching it."&lt;/p&gt;

&lt;p&gt;Helps: "Auth lives in &lt;code&gt;src/auth&lt;/code&gt;. Run &lt;code&gt;pnpm test:auth&lt;/code&gt; after any change there."&lt;/p&gt;

&lt;p&gt;The first one is a status update. It will be false in a week, and when it is, the agent will confidently act on it anyway. The second is a durable fact about where things live and how to verify them. One is a note to a coworker. The other is a rule for a machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you commit your CLAUDE.md file?
&lt;/h2&gt;

&lt;p&gt;Yes, for the project-level file. The whole value is that everyone on the team, and every remote agent, loads the exact same rules. Commit the &lt;code&gt;CLAUDE.md&lt;/code&gt; in your repo root the way you commit your linter config, because it is the same kind of artifact: a shared standard, not a personal preference. Check it into Git and your agent behaves consistently no matter who runs it or where.&lt;/p&gt;

&lt;p&gt;Your personal, machine-wide file (the one in &lt;code&gt;~/.claude/&lt;/code&gt;) is different. It holds your individual workflow quirks, it lives in your home directory outside any repo, and it should stay there. The rule of thumb: project rules are team property and get committed; personal preferences are yours and do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reframe: instructions are not a record
&lt;/h2&gt;

&lt;p&gt;Now the part that most "how to write a great CLAUDE.md" posts miss. Builders keep reaching for CLAUDE.md, or Claude's auto-memory, to solve a problem it was never designed for: remembering what the product is supposed to do. What did we decide about how billing works? Why is this feature built the way it is? What counts as done for the checkout flow? That is not instruction. That is your product's record, and stuffing it into a file the model skims on every turn is how it goes wrong.&lt;/p&gt;

&lt;p&gt;The evidence for this is everywhere once you look. In a recent &lt;a href="https://www.reddit.com/r/ClaudeCode/comments/1vfcmmw/" rel="noopener noreferrer"&gt;r/ClaudeCode thread on whether anyone actually uses Claude's memory feature&lt;/a&gt;, four separate builders landed on the same verdict without coordinating. One put it bluntly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I made the mistake of trying to use it for project knowledge and now it is bleeding into other projects smh. Don't be like me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Others in the same thread said it "quickly gets outdated," that it stayed "entirely opaque" for a month before they noticed a wrong note, and that memory "should be reserved for user preferences and nothing more. Not for actual knowledge." Four people, one conclusion: the model's memory is a preference store, not a project record. It bleeds, it goes stale, and you cannot see what is in it.&lt;/p&gt;

&lt;p&gt;This is the capability point that does not go away as models improve. Better models make it more tempting to offload durable knowledge into the model's context, not less, because the agent feels smart enough to trust. But a smarter agent working from a stale note is not more reliable. It is more confidently wrong, and harder to debug, because the mistake traces back to something it "remembered" that stopped being true weeks ago. Models are stateless. Your product isn't. (If you want the deeper version of why auto-memory rots, we wrote it up in &lt;a href="https://www.braingrid.ai/blog/claude-code-memory-state-file-pattern" rel="noopener noreferrer"&gt;the state file pattern&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the product record actually belongs
&lt;/h2&gt;

&lt;p&gt;So if instructions live in CLAUDE.md and durable knowledge does not belong in memory, where does the record of what your product is supposed to do actually live? Outside the model, in something you own and update deliberately.&lt;/p&gt;

&lt;p&gt;This is the gap &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; fills. You describe a feature, and the Planning Agent turns it into a requirement with explicit acceptance criteria, the specific, checkable statements of what done means for that feature. When the Builder Agent builds it, whether in a BrainGrid Cloud sandbox or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP, verification checks the result against every one of those criteria before the feature counts as done. And all of it, the specs, the decisions, the criteria, the verifications, accumulates per product as a record you can read.&lt;/p&gt;

&lt;p&gt;The difference from a CLAUDE.md file is the point. CLAUDE.md tells the agent how you work: your commands, your conventions, your house rules. The product record holds what you decided and why, and it does not depend on the model remembering anything. One is a preamble the model reads. The other is the source of truth the model is checked against. You still want a lean CLAUDE.md. You just stop asking it to be something it was never built to be. This is the heart of &lt;a href="https://www.braingrid.ai/context-engineering" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt;: deciding what the model should carry in its head, and what belongs in a record outside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;Keeping durable knowledge out of the model's context has a real cost: it is more work up front. Writing an acceptance criterion is slower than typing a sentence into a chat and hoping the agent remembers it. The lazy path genuinely feels faster on day one. It just stops feeling faster the first time you spend an afternoon debugging behavior that traces back to a note nobody knew was there. The effortless option rots. The deliberate one compounds. That is the trade, and it is worth making on any project you intend to keep.&lt;/p&gt;

&lt;p&gt;If you're building a real product with Claude Code or Cursor right now, this means your CLAUDE.md should get shorter over time, not longer, and the knowledge you were tempted to cram into it needs a home the model doesn't own.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a CLAUDE.md file necessary?
&lt;/h3&gt;

&lt;p&gt;Not strictly, but it is worth it for any project you return to. Without one, the agent re-guesses your stack, your commands, and your conventions every session, and guesses wrong often enough to waste real time. A short, accurate CLAUDE.md removes that friction. Skip it only for a throwaway experiment you will never open again.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do you put in a CLAUDE.md file?
&lt;/h3&gt;

&lt;p&gt;The stable facts an agent needs on nearly every task: your tech stack, the commands to run and test and build the project, your core coding conventions, and the hard rules you never want broken. Keep it lean. Leave out anything that changes week to week, like current sprint status or in-progress decisions, because that is exactly what goes stale and misleads the agent later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I commit my CLAUDE.md file to Git?
&lt;/h3&gt;

&lt;p&gt;Commit the project-level &lt;code&gt;CLAUDE.md&lt;/code&gt; in your repo root, because its value is that every teammate and every agent loads the same rules. Do not worry about the personal file in your home directory (&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;); it lives outside the repo and holds your individual preferences, so it stays local by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Claude ignore my CLAUDE.md file?
&lt;/h3&gt;

&lt;p&gt;Usually because the file is too long. Everything in CLAUDE.md is prepended to every request, so a bloated file both costs tokens on every turn and gets skimmed rather than followed. Cut it to the rules that matter most, phrase them as clear directives, and the model follows them far more reliably. If a rule is critical, keeping it near the top and stated plainly helps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a CLAUDE.md file the same as agent memory?
&lt;/h3&gt;

&lt;p&gt;No. CLAUDE.md is instructions you write and control, read at the start of each session. Auto-memory is notes the agent writes for itself in the background, in a Claude-specific location. The file you own stays accurate because you edit it; the notes the agent writes tend to go stale and bleed across projects, which is why most builders reserve memory for preferences and keep real project knowledge in files, or in a &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;product record&lt;/a&gt; that lives outside the model entirely.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first platform that gives your product a record the model can be checked against, not just instructions it reads. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/what-is-a-claude-md-file" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Open Graph Meta Tags: The Complete Reference (Every Tag, With Examples)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:41:15 +0000</pubDate>
      <link>https://dev.to/grabbit/open-graph-meta-tags-the-complete-reference-every-tag-with-examples-36f6</link>
      <guid>https://dev.to/grabbit/open-graph-meta-tags-the-complete-reference-every-tag-with-examples-36f6</guid>
      <description>&lt;p&gt;Open Graph meta tags are the snippets in your page's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that control how a link looks when someone shares it. Get them right and your URL unfurls into a rich card with a title, a summary, and an image. Miss them and the same link shows up as bare text. This is the complete reference: every tag worth setting, what each one does, and copy-paste examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer: five tags
&lt;/h2&gt;

&lt;p&gt;Most pages need exactly five Open Graph tags. Put these in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your page title"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A one-line summary of the page."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/your-page"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"website"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Facebook, LinkedIn, Slack, Discord, iMessage, and WhatsApp all read these. Set them and your link previews work almost everywhere. The rest of this reference covers the optional tags and the X-specific ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The required tags, one by one
&lt;/h2&gt;

&lt;p&gt;These five carry the card. Skip any of them and the preview degrades.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The headline of the card. Independent of the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The summary line under the title. One or two sentences.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The picture in the card. Absolute URL, 1200 by 630 for the large card.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The canonical URL of the page, without tracking parameters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category: &lt;code&gt;website&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;video.movie&lt;/code&gt;, and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few details that trip people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:title&lt;/code&gt; is not your &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;.&lt;/strong&gt; They can match, but &lt;code&gt;og:title&lt;/code&gt; lets you write a version tuned for sharing. Keep it under about 60 characters so it does not truncate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:url&lt;/code&gt; should be the canonical URL.&lt;/strong&gt; Use the clean permanent address, not the one with &lt;code&gt;?utm_source=...&lt;/code&gt; on it, so every share collapses to the same object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:image&lt;/code&gt; must be an absolute &lt;code&gt;https://&lt;/code&gt; URL.&lt;/strong&gt; A relative path like &lt;code&gt;/og.png&lt;/code&gt; will not resolve for the crawler. It also has to be publicly reachable: if the image needs a login, the card shows nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The image tags that make the large card render
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;og:image&lt;/code&gt; alone works, but three companion tags make the difference between a big card and a small thumbnail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:alt"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A short description of the image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring &lt;code&gt;og:image:width&lt;/code&gt; and &lt;code&gt;og:image:height&lt;/code&gt; lets the platform reserve the right space and render the large card immediately instead of guessing the dimensions on first fetch. Use &lt;strong&gt;1200 by 630 pixels&lt;/strong&gt;, the size every major platform treats as the full-width preview. &lt;code&gt;og:image:alt&lt;/code&gt; is the accessibility description, read by screen readers on platforms that expose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optional tags worth setting
&lt;/h2&gt;

&lt;p&gt;Beyond the core five, a handful of tags add polish and matter for specific &lt;code&gt;og:type&lt;/code&gt; values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Brand"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:locale"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"en_US"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:site_name&lt;/code&gt;&lt;/strong&gt; is the name of the overall site, shown as a small label on some cards (LinkedIn, for instance). Set it once to your brand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:locale&lt;/code&gt;&lt;/strong&gt; declares the language and region, like &lt;code&gt;en_US&lt;/code&gt; or &lt;code&gt;fr_FR&lt;/code&gt;. Useful if your pages are localized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When &lt;code&gt;og:type&lt;/code&gt; is &lt;code&gt;article&lt;/code&gt;, these structured tags become relevant and show up on news and blog cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:published_time"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"2026-08-08T09:00:00Z"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/authors/jane"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:section"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Engineering"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:tag"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"open graph"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need the &lt;code&gt;article:*&lt;/code&gt; tags on pages that are genuinely articles. For a marketing page or a landing page, &lt;code&gt;og:type&lt;/code&gt; of &lt;code&gt;website&lt;/code&gt; and the core five are enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  X (Twitter) tags: what you actually need
&lt;/h2&gt;

&lt;p&gt;X reads its own &lt;code&gt;twitter:&lt;/code&gt; tags but falls back to your &lt;code&gt;og:&lt;/code&gt; tags when they are absent. The one tag you should always set is &lt;code&gt;twitter:card&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;summary_large_image&lt;/code&gt;, X renders a small square thumbnail beside the text instead of the big image card. Note the attribute difference: &lt;code&gt;og:&lt;/code&gt; tags use &lt;code&gt;property=&lt;/code&gt;, while &lt;code&gt;twitter:&lt;/code&gt; tags use &lt;code&gt;name=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can stop there. X will pull the title, description, and image from &lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, and &lt;code&gt;og:image&lt;/code&gt;. Only add these if you want a &lt;em&gt;different&lt;/em&gt; headline or image on X than everywhere else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A headline tuned for X"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A summary tuned for X."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A complete, copy-paste example
&lt;/h2&gt;

&lt;p&gt;Here is the full &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; block for a typical article page, combining everything above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Open Graph Meta Tags: The Complete Reference&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Open Graph --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Open Graph Meta Tags: The Complete Reference"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Every og: tag worth setting, with examples."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/reference.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/blog/og-meta-tags"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Brand"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- X (Twitter) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to generate the og:image from a URL
&lt;/h2&gt;

&lt;p&gt;Writing the tags is the easy part. The hard part is producing a unique, on-brand &lt;code&gt;og:image&lt;/code&gt; for every page without designing one by hand. The pattern that scales: build &lt;strong&gt;one HTML template&lt;/strong&gt; that renders the page title and your branding, then &lt;strong&gt;capture that template as an image&lt;/strong&gt; at 1200 by 630.&lt;/p&gt;

&lt;p&gt;A screenshot API turns that into a single request. Point it at your template URL with a fixed viewport, and get back a hosted image you can drop straight into &lt;code&gt;og:image&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://grabbit.live/api/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_your_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://yoursite.com/og-template?title=Your+Post+Title",
    "width": 1200,
    "height": 630,
    "format": "png"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes the hosted image URL. Set &lt;code&gt;og:image&lt;/code&gt; to it, and every page gets its own preview card generated from real HTML, no design tool required. This is the same approach behind &lt;a href="https://www.grabbit.live/blog/next-js-og-image" rel="noopener noreferrer"&gt;dynamic OG images in Next.js&lt;/a&gt; and any other framework: render a URL, screenshot it, ship the result.&lt;/p&gt;

&lt;p&gt;For the deeper background on why the image matters and how platforms crop it, see &lt;a href="https://www.grabbit.live/blog/what-is-an-og-image" rel="noopener noreferrer"&gt;what an OG image is&lt;/a&gt;. Once your tags are live, run the page through an &lt;a href="https://www.grabbit.live/blog/og-image-checker" rel="noopener noreferrer"&gt;OG image checker&lt;/a&gt; to confirm the crawler sees what you expect, and if a preview looks wrong, an &lt;a href="https://www.grabbit.live/blog/open-graph-debugger" rel="noopener noreferrer"&gt;Open Graph debugger&lt;/a&gt; shows you the exact tags each platform scraped.&lt;/p&gt;

&lt;p&gt;Grabbit is a screenshots-as-a-service API built for exactly this job: point it at a URL, get back a hosted image, no headless browser to run. Live grabs are a flat $0.002 each on prepaid credits that never expire, and a test key renders free placeholders so you can wire up your OG pipeline before adding a card. See the &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; for the full reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Injecting og: tags with client-side JavaScript.&lt;/strong&gt; Crawlers run little to no JS, so they see an empty &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. Server-render your meta tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A relative &lt;code&gt;og:image&lt;/code&gt; path.&lt;/strong&gt; It must be an absolute &lt;code&gt;https://&lt;/code&gt; URL, or the image will not resolve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting &lt;code&gt;twitter:card&lt;/code&gt;.&lt;/strong&gt; Without &lt;code&gt;summary_large_image&lt;/code&gt;, X shows a small thumbnail instead of the large card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not clearing the cache after a fix.&lt;/strong&gt; Platforms cache the scrape. Use each platform's debugger to force a re-scrape once you have corrected a tag.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/og-meta-tags" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>seo</category>
      <category>html</category>
    </item>
    <item>
      <title>What Is AI-Native Development? (And Why \"Disposable Software\" Is Only Half True)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:15:16 +0000</pubDate>
      <link>https://dev.to/braingrid/what-is-ai-native-development-and-why-disposable-software-is-only-half-true-30of</link>
      <guid>https://dev.to/braingrid/what-is-ai-native-development-and-why-disposable-software-is-only-half-true-30of</guid>
      <description>&lt;p&gt;Search "AI-native development" today and every definition on the first page was written by a platform vendor. IBM, DevOps.com, a half-dozen consultancies, all describing a paradigm where "intelligent agents act as primary implementers" and "platforms enforce guardrails." It reads like the future arriving on schedule, clean and inevitable.&lt;/p&gt;

&lt;p&gt;Then you open the Reddit threads where builders actually live in this paradigm, and the tone flips. Under a post titled "Is demand for new software decreasing?", the top comment is three words repeated twice: &lt;a href="https://www.reddit.com/r/cscareers/comments/1uc8naj" rel="noopener noreferrer"&gt;software is now disposable&lt;/a&gt;. The reply underneath: "I hate this so much."&lt;/p&gt;

&lt;p&gt;Same paradigm. Two completely different emotions. The vendors sell AI-native as liberation. The people doing it feel something closer to grief. Both are describing something real, and the gap between them is the most useful thing to understand about where software is going.&lt;/p&gt;

&lt;p&gt;Here is the hypothesis this post is going to test: AI-native development is real and worth adopting, "disposable software" is true for a narrower slice of what you build than the grief implies, and the thing that separates the disposable from the durable is not the agent. It's whether a plan and a set of acceptance criteria outlive the chat that produced the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-native development, defined honestly
&lt;/h2&gt;

&lt;p&gt;Strip away the vendor gloss and AI-native development means one thing: you build assuming the agent writes most of the code, and you organize your work around that assumption instead of fighting it.&lt;/p&gt;

&lt;p&gt;That is a real shift, not a marketing one. In the old model, AI was a consultant on your shoulder. You wrote the code and asked ChatGPT when you got stuck. AI-native inverts the default. The agent writes the first draft of almost everything, and your job moves up a level, to deciding what gets built, describing it precisely enough that the agent can execute, and verifying that what came back actually does the job.&lt;/p&gt;

&lt;p&gt;Patrick Debois, who has done more than anyone to name this shift, &lt;a href="https://www.infoq.com/presentations/patterns-ai-native-development/" rel="noopener noreferrer"&gt;frames it as four patterns&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transitioning from producer to manager, focusing on intent over implementation through spec-driven development, moving from delivery to discovery, and managing agentic knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that list twice and notice what is not on it. None of the four patterns is "write better code." All four are about the work that surrounds the code: intent, management, discovery, memory. The framing reorders the whole job. The thing you used to be paid for, producing code, is now pattern zero, the part the agent handles, and every named pattern lives above it.&lt;/p&gt;

&lt;p&gt;That is the honest core of AI-native development. The coding got cheap. Everything around the coding became the actual job. If you have felt your role quietly change from typing to describing and checking, you already work this way, whether or not you have a word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old way and the AI-native way, side by side
&lt;/h2&gt;

&lt;p&gt;The difference is easiest to see in a single feature.&lt;/p&gt;

&lt;p&gt;Old way: you get a ticket that says "add password reset." You know your codebase, so you open the auth module, write the endpoint, wire up the email, handle the token expiry, and test it as you go. The knowledge of what "done" means lives in your head the entire time, because you are the one holding all the pieces.&lt;/p&gt;

&lt;p&gt;AI-native way: you tell an agent "add password reset" and it produces a working-looking flow in four minutes. Now the knowledge that used to live in your head has to live somewhere the agent can see, or it doesn't get applied at all. Did it expire the token after one use? Rate-limit the request endpoint? Invalidate old sessions on reset? The agent will happily skip every one of those unless someone wrote them down, because "add password reset" is not a specification. It's a wish.&lt;/p&gt;

&lt;p&gt;That is the whole game. In the old model, the spec could stay implicit because the person writing the code was the person who knew the requirements. In the AI-native model, the writer and the knower are different entities, and anything you don't make explicit is anything the agent is free to get wrong. This is the practical difference between &lt;a href="https://www.braingrid.ai/blog/vibe-coding-vs-agentic-coding" rel="noopener noreferrer"&gt;vibe coding and agentic coding&lt;/a&gt;: one accepts whatever the agent produces, the other writes down what "produced correctly" means first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where "disposable software" is actually true
&lt;/h2&gt;

&lt;p&gt;Now the disposability claim, taken seriously.&lt;/p&gt;

&lt;p&gt;The builders saying "software is now disposable" are not wrong. There is a large and growing category of software that genuinely is throwaway now, and pretending otherwise would be dishonest. The internal script that reformats a CSV once a quarter. The landing page for a launch that runs for two weeks. The prototype you build to answer a single question in a meeting, screenshot, and never open again. The one-off tool that scrapes a report so you don't have to.&lt;/p&gt;

&lt;p&gt;For all of that, disposability is not a loss. It's the point. Building these used to cost an afternoon of a developer's time, which meant they mostly didn't get built. Now they cost ten minutes, so they get built, used, and discarded, and the world is slightly better for it. If the software has a lifespan measured in days and a blast radius of exactly one person, spec-driven rigor is overhead you should skip. Vibe your way through it and move on.&lt;/p&gt;

&lt;p&gt;The grief in those threads is real, but a lot of it is misattributed. What feels like "all software is disposable now" is usually "the disposable category got much bigger and much more visible." That is a different and less alarming claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's a bug, not a feature
&lt;/h2&gt;

&lt;p&gt;The line gets crossed the moment other people depend on the thing.&lt;/p&gt;

&lt;p&gt;A demo that only you will ever run can be disposable. The customer-facing app it turns into cannot. The script that formats your own CSV can be disposable. The billing job that formats every customer's invoice cannot. The prototype that proves an idea can be disposable. The product you charge money for, that holds someone's data, that you will still be editing in six months, cannot be. Not because rigor is virtuous, but because "disposable" and "load-bearing" are opposites, and you don't get to be both.&lt;/p&gt;

&lt;p&gt;Here is the trap AI-native development sets, and it's worth stating plainly because almost nobody warns you about it. The disposable version and the durable version look identical when they're born. The agent produces the same clean, confident, working-looking output either way. That is &lt;a href="https://www.braingrid.ai/blog/illusion-of-completeness-ai-demo" rel="noopener noreferrer"&gt;the illusion of completeness&lt;/a&gt;: looking done and being done are different properties, and the agent only guarantees the first one. The demo that will run once and the app that will run for years come out of the same prompt looking equally finished. You cannot tell them apart by looking, which means the moment your throwaway quietly becomes load-bearing, and it will, because the useful ones always do, you inherit a codebase that was never specified, never verified, and never meant to survive.&lt;/p&gt;

&lt;p&gt;Disposable software is fine until the day it stops being disposable. And that day never announces itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes software non-disposable
&lt;/h2&gt;

&lt;p&gt;So what actually converts a disposable build into a durable one? Not more code. The code is the cheap part now, and adding more of it just gives you more to distrust.&lt;/p&gt;

&lt;p&gt;What makes software non-disposable is the layer above the code: a plan that says what this thing is supposed to do, and a set of &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;acceptance criteria an agent can actually verify&lt;/a&gt; that says how you'll know it does it. Those two artifacts are what let you change the software next month without fear, because they tell the next agent, and the next you, what must stay true. Code without them is a black box you're afraid to touch. Code with them is a system you can keep evolving. That is the entire difference between something you throw away and something you build on.&lt;/p&gt;

&lt;p&gt;This is exactly the gap &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is built to close, and it's the whole point of practicing &lt;a href="https://www.braingrid.ai/agentic-engineering" rel="noopener noreferrer"&gt;agentic engineering&lt;/a&gt; instead of just prompting faster. You describe what you want, and the Planning Agent turns it into a real requirement with acceptance criteria, the questions you didn't think to ask, and a spec that survives the conversation. Then the Builder Agent writes the code, in our cloud or in your own repo with Claude Code, Cursor, or Codex. And nothing counts as done until it's been verified against those criteria, with evidence that it does what you intended. The agent forgets everything the moment the session ends. The plan and the criteria don't. That persistence is what makes the output something you can depend on instead of something you'll be scared to open later.&lt;/p&gt;

&lt;p&gt;It runs as a loop, Plan then Build then Verify then Repeat, and the point of the loop is that the durable version costs you almost nothing extra up front. You get the plan for free by describing the idea, and you get to keep it. The tax of AI-native development was never the coding. It was inheriting code nobody wrote down the intent for. The loop pays that tax at the start, once, instead of every time you come back scared to change something.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you're building right now
&lt;/h2&gt;

&lt;p&gt;If you're shipping with agents today, the practical move is not "spec everything" or "spec nothing." It's knowing which side of the line a given build sits on, before you start.&lt;/p&gt;

&lt;p&gt;Ask one question: will anyone other than me depend on this, now or later? If the honest answer is no and will stay no, vibe it and enjoy the speed. That is AI-native development working exactly as advertised, and the disposability is a gift. If the answer is yes, or if there's a real chance a throwaway becomes something people rely on, then the ten minutes you spend writing down what "done" means is the cheapest insurance you will ever buy. You are not adding process. You are refusing to inherit a load-bearing black box.&lt;/p&gt;

&lt;p&gt;The verdict on the hypothesis: AI-native development is real, disposable software is real, and the grief is mostly about not knowing where the line is. The line is dependence. Below it, throwaway is a feature. Above it, the plan and the criteria are what turn the agent's confident output into something you can actually trust. The agent gave you the code for free. The intent is the part you have to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI-native development?
&lt;/h3&gt;

&lt;p&gt;AI-native development is an approach to building software where you assume AI agents write most of the code and you organize your work around that assumption. Instead of AI acting as an occasional assistant while you write the code yourself, the agent produces the first draft of almost everything, and your job shifts up a level: deciding what to build, specifying it precisely, and verifying that the result does what you intended. The four recurring patterns are moving from producer to manager, focusing on intent over implementation through spec-driven development, shifting from delivery to discovery, and managing knowledge so context survives across sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the 4 patterns of AI-native development?
&lt;/h3&gt;

&lt;p&gt;The four patterns, as named by Patrick Debois, are: (1) transitioning from producer to manager, where you operate and review agent output instead of writing every line; (2) focusing on intent over implementation through spec-driven development, where living specifications matter more than granular code; (3) moving from delivery to discovery, where the work shifts from shipping known features to exploring what should exist; and (4) managing agentic knowledge, where feedback from code, tickets, and incidents continuously updates the context your agents work from. All four are about the work surrounding the code, not the code itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between AI-native and AI-first?
&lt;/h3&gt;

&lt;p&gt;AI-first usually means AI is a priority or a default tool in how you work, bolted onto an existing process. AI-native means the process itself was designed from the ground up assuming AI does the core work, the way "cloud-native" meant designing for the cloud rather than lifting an old app into it. In practice, an AI-first team adds an agent to its existing workflow; an AI-native team rebuilds the workflow around the agent, which is why intent, specification, and verification become the load-bearing skills rather than typing speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is AI-native development the same as vibe coding?
&lt;/h3&gt;

&lt;p&gt;No, though they overlap. Vibe coding means prompting an agent and accepting what comes back without a written spec or verification, which is a legitimate mode for throwaway work. AI-native development is the broader practice of building around agents, and for anything that needs to last it adds the discipline vibe coding skips: a plan, acceptance criteria, and verification against them. Vibe coding is one style of AI-native work that is fine when the software is disposable and dangerous the moment it becomes something people depend on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does AI-native development mean software is disposable now?
&lt;/h3&gt;

&lt;p&gt;Only for a specific slice of it. Software that one person uses briefly, a one-off script, a short-lived landing page, a throwaway prototype, genuinely is disposable now, and that category has grown a lot. But the moment other people depend on the software, or a throwaway quietly becomes load-bearing, disposability turns from a feature into a liability. The dividing line is dependence, and what keeps depended-on software from being disposable is a plan and acceptance criteria that outlive the code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first app-building platform: it plans before it builds, then verifies every change against acceptance criteria, so the software you depend on never has to be disposable. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/ai-native-development" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Selenium vs Playwright: Which Should You Use in 2026?</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Fri, 07 Aug 2026 11:42:57 +0000</pubDate>
      <link>https://dev.to/grabbit/selenium-vs-playwright-which-should-you-use-in-2026-2idn</link>
      <guid>https://dev.to/grabbit/selenium-vs-playwright-which-should-you-use-in-2026-2idn</guid>
      <description>&lt;p&gt;Playwright and Selenium are the two tools most teams weigh when they automate a browser, and in 2026 the momentum is clearly with Playwright: it just passed Selenium in adoption surveys for the first time. But "which is winning" and "which should you use" are different questions. The right answer depends on your language, your existing suite, and whether you are automating tests or just need an image back from a URL. Here is the honest comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New project, JavaScript or TypeScript or Python:&lt;/strong&gt; Playwright. Auto-waiting, a smaller API, and three bundled browser engines make it the faster path to reliable automation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing enterprise Selenium suite, or a non-JS ecosystem (Java, C#, Ruby):&lt;/strong&gt; Selenium is still a reasonable default. The WebDriver standard, the language breadth, and the grid ecosystem are hard to walk away from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need cross-browser tests with the least flakiness:&lt;/strong&gt; Playwright. One API drives Chromium, Firefox, and WebKit, and auto-waiting removes most timing bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You just need a screenshot of a URL, not a test framework:&lt;/strong&gt; neither. A &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;hosted screenshot API&lt;/a&gt; returns a hosted image from one request, with no browser to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this post shows where the two actually differ so you can decide with the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: WebDriver vs direct control
&lt;/h2&gt;

&lt;p&gt;The core difference is how each tool talks to the browser.&lt;/p&gt;

&lt;p&gt;Selenium drives browsers through the &lt;strong&gt;W3C WebDriver&lt;/strong&gt; protocol. Your script sends commands to a driver (chromedriver, geckodriver), which relays them to the browser over HTTP. That standardization is Selenium's superpower and its tax: it works with almost any browser and language, but every command is a separate round trip.&lt;/p&gt;

&lt;p&gt;Playwright talks to the browser over a &lt;strong&gt;single persistent connection&lt;/strong&gt; using the browser's own debugging protocol. Fewer round trips, and it can observe the page state directly, which is what makes auto-waiting possible.&lt;/p&gt;

&lt;p&gt;This one design choice explains most of the practical differences below: speed, waiting, and reliability all trace back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language and browser support
&lt;/h2&gt;

&lt;p&gt;This is where Selenium still leads.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Selenium&lt;/th&gt;
&lt;th&gt;Playwright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;Java, Python, C#, Ruby, JavaScript, and more&lt;/td&gt;
&lt;td&gt;JavaScript/TypeScript, Python, Java, C#&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browsers&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Edge, Safari (real installs) + huge grid/cloud ecosystem&lt;/td&gt;
&lt;td&gt;Bundled Chromium, Firefox, WebKit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;W3C WebDriver&lt;/td&gt;
&lt;td&gt;Browser debug protocol (not a standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your team is on Ruby, or you must drive a specific real browser build on a specific OS through a cloud grid, Selenium's breadth wins. If you want three engines that cover the matrix most apps care about, with zero driver management, Playwright's bundled browsers are simpler.&lt;/p&gt;

&lt;p&gt;Note that "WebKit" in Playwright is the engine behind Safari, not Safari itself, so it is a very close approximation rather than a byte-for-byte Safari render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-waiting: Playwright's biggest day-to-day win
&lt;/h2&gt;

&lt;p&gt;Flaky tests are almost always timing bugs. An element is not clickable yet, or the network has not settled, and a fixed &lt;code&gt;sleep&lt;/code&gt; either wastes time or fails intermittently.&lt;/p&gt;

&lt;p&gt;Selenium makes you manage this. The recommended pattern is an explicit wait:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;until&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;selenium-webdriver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;forBrowser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;until&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elementLocated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright waits for you. Its locators auto-wait for the element to be attached, visible, and actionable before acting, so the same flow needs no explicit wait:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Across a full suite, deleting hundreds of &lt;code&gt;wait&lt;/code&gt; and &lt;code&gt;sleep&lt;/code&gt; calls is the single biggest reliability and maintenance improvement teams report after migrating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Playwright is generally faster, and the architecture explains why. Selenium's per-command HTTP round trips add up over a long test, and explicit waits are often padded to be safe. Playwright's single connection and auto-waiting mean it acts as soon as the page is ready, not after a fixed delay.&lt;/p&gt;

&lt;p&gt;The gap is real on multi-step flows. On a single screenshot it is mostly noise, because page load time dominates and both tools spend that time the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locators: role and text over XPath
&lt;/h2&gt;

&lt;p&gt;Selenium's world is CSS and XPath selectors tied to the DOM structure. That works, but a renamed class or an extra wrapper &lt;code&gt;div&lt;/code&gt; breaks the locator.&lt;/p&gt;

&lt;p&gt;Playwright still supports CSS and XPath, but pushes you toward user-facing locators that read the accessibility tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Playwright: resilient, user-facing locators&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome back&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These survive DOM refactors because they find the button labelled "Submit" rather than its position in the markup. It is a small API difference with a big effect on how often tests break when the frontend changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from Selenium to Playwright
&lt;/h2&gt;

&lt;p&gt;Migration is worth it when flaky tests and explicit-wait upkeep are costing you real time, and your app is JavaScript-heavy. The wait-code deletion alone often pays for the move.&lt;/p&gt;

&lt;p&gt;It is not worth it when your Selenium suite is stable, your team lives in a language Playwright serves poorly, or your grid, reporting, and CI are deeply wired into WebDriver. A working suite is an asset; do not rewrite it for fashion.&lt;/p&gt;

&lt;p&gt;If you do migrate, do it incrementally: run new tests in Playwright while the Selenium suite keeps guarding old ground, and port flaky tests first, because those are exactly the ones auto-waiting fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots: where both tools are overkill
&lt;/h2&gt;

&lt;p&gt;Both frameworks take screenshots, and the code is nearly identical. In Selenium:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;takeScreenshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if a screenshot is the &lt;em&gt;only&lt;/em&gt; thing you need, either tool is a lot of moving parts. You are installing a driver or a bundled browser, running headless Chromium in your own infrastructure, and owning the parts nobody enjoys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provisioning.&lt;/strong&gt; Headless browsers need a long list of system libraries. Slim containers and serverless functions hit missing-dependency errors before the first pixel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory and zombie processes.&lt;/strong&gt; A browser not closed on every error path leaks memory and orphans processes until the box falls over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patching.&lt;/strong&gt; Browser engines ship security updates constantly, so a long-lived capture service becomes a browser fleet you keep current.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency.&lt;/strong&gt; One browser does one job at a time well. Thousands of captures a day means a pool, a queue, and back-pressure to build and operate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is test code, and it is the same whether you picked Selenium or Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same capture as one API call
&lt;/h2&gt;

&lt;p&gt;When screenshots are a feature you ship rather than a step inside a test, a hosted &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; skips the browser entirely. Here is a full-page capture as a single request to Grabbit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "full_page": true,
    "format": "webp"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a hosted &lt;code&gt;image_url&lt;/code&gt; you can use directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grb_01jx..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.grabbit.live/grabs/grb_01jx....webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1180&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The options you reach for in either framework map onto request parameters: &lt;code&gt;fullPage&lt;/code&gt; becomes &lt;code&gt;full_page&lt;/code&gt;, a locator or selector becomes the &lt;code&gt;selector&lt;/code&gt; field, and a manual wait becomes &lt;code&gt;delay_ms&lt;/code&gt; (0 to 10000). Width accepts 320 to 1920, height 240 to 1080, and &lt;code&gt;format&lt;/code&gt; is &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, or &lt;code&gt;webp&lt;/code&gt;. Pricing is a flat $0.002 per capture on prepaid credits that never reset, so a screenshot you run once a week costs the same per image as one you run a thousand times a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greenfield project, want the least flakiness:&lt;/strong&gt; Playwright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing enterprise suite, or a non-JS stack, or a specific real-browser grid:&lt;/strong&gt; Selenium.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning automation from scratch in 2026:&lt;/strong&gt; start with Playwright, add Selenium for enterprise roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You just need an image from a URL, not a test framework:&lt;/strong&gt; skip the browser and call an API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the framework-specific deep dives, see &lt;a href="https://www.grabbit.live/blog/playwright-screenshot" rel="noopener noreferrer"&gt;taking screenshots in Playwright&lt;/a&gt; and &lt;a href="https://www.grabbit.live/blog/selenium-screenshot" rel="noopener noreferrer"&gt;taking screenshots in Selenium&lt;/a&gt;. If you are weighing hosted options, the &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of screenshot APIs&lt;/a&gt; covers the trade-offs without the marketing, and &lt;a href="https://www.grabbit.live/blog/puppeteer-vs-playwright" rel="noopener noreferrer"&gt;Puppeteer vs Playwright&lt;/a&gt; compares the two Chromium-first libraries head to head.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/selenium-vs-playwright" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>What Is an Agent Loop? The Reason-Act-Observe Cycle, Explained</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Fri, 07 Aug 2026 11:18:43 +0000</pubDate>
      <link>https://dev.to/braingrid/what-is-an-agent-loop-the-reason-act-observe-cycle-explained-2592</link>
      <guid>https://dev.to/braingrid/what-is-an-agent-loop-the-reason-act-observe-cycle-explained-2592</guid>
      <description>&lt;p&gt;A chatbot answers you once and stops. An agent keeps going until the job is done. The difference between the two is not a bigger model or a cleverer prompt. It is a loop.&lt;/p&gt;

&lt;p&gt;That loop is the most important idea in AI building right now, and it is also the most misunderstood. The search volume for "agent loop" is up more than 1,000% in a year, and half the explanations you will find describe the mechanism perfectly while missing the part that actually determines whether your agent ships something good or burns through your budget producing confident garbage. The mechanism is simple. What you feed it is the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agent loop actually is
&lt;/h2&gt;

&lt;p&gt;An agent loop is a cycle: the model reasons about a task, takes an action using a tool, observes the result, and then repeats, feeding what it just learned back into the next turn. It keeps going until it decides the work is done or it hits a limit you set.&lt;/p&gt;

&lt;p&gt;Strip away the diagrams and it is four moves. Reason: the model looks at the task and its history and decides what to do next. Act: it calls a tool, running a terminal command, editing a file, hitting an API. Observe: it reads what came back, including the errors. Repeat: it writes that result into its own context and starts the next turn with more information than it had before.&lt;/p&gt;

&lt;p&gt;That fourth step is the one that matters. Anyone can call a model and get a paragraph. Writing the result back into the context, so the next turn is informed by the last, is what turns a stateless text generator into something that can pursue a goal across many steps. Simon Willison put the whole thing about as bluntly as it can be put:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An LLM agent runs tools in a loop to achieve a goal.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://simonwillison.net/2025/Sep/18/agents/" rel="noopener noreferrer"&gt;Simon Willison&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LangChain's team, writing about how they build agents, describe the core the same way:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At its core, an agent is just a model calling tools in a loop until a task is complete.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://www.langchain.com/blog/the-art-of-loop-engineering" rel="noopener noreferrer"&gt;LangChain, "The Art of Loop Engineering"&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have used Claude Code, Cursor's agent mode, or Codex, you have watched this happen. You ask for a feature. The agent reads a file, tries an edit, runs the tests, sees three of them fail, reads the error, fixes the edit, runs the tests again. Each of those is one turn through the loop. You did not prompt it five times. You prompted it once, and the loop did the other four turns on its own. Chain a few of these loops together across a whole task and you get an &lt;a href="https://www.braingrid.ai/blog/what-is-an-agentic-workflow" rel="noopener noreferrer"&gt;agentic workflow&lt;/a&gt;; the loop is the atom that everything larger is built from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the loop, not the model, is the unlock
&lt;/h2&gt;

&lt;p&gt;Here is the reframe most explainers skip. The loop is not a feature of the model. It is a feature of the harness wrapped around the model. The same model that gives you one flat answer in a chat window becomes an agent the moment you put it in a loop that can act and observe.&lt;/p&gt;

&lt;p&gt;This is why the last two years felt like such a step change even when the underlying models improved only incrementally. a16z's Yoko Li described the shift precisely this week:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An AI model can almost always produce another answer. It can revise the paragraph again. It can try another implementation. Instead of a human prompting a model, inspecting the result, describing what went wrong, and prompting it again, we can ask the system to perform the whole cycle itself.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://x.com/a16z/status/2085448947359965220" rel="noopener noreferrer"&gt;Yoko Li, a16z&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the promise. The human used to be the loop, prompting and inspecting and re-prompting by hand. Now the system runs the cycle itself, and you step up a level to design the loop instead of running it. It is a genuine gain in leverage, one full step up the ladder.&lt;/p&gt;

&lt;p&gt;It is also a genuine transfer of risk, and this is the part the excited version leaves out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop's blind spot: it does not know what "done" means
&lt;/h2&gt;

&lt;p&gt;A loop can act. A loop can observe. A loop can repeat. The one thing a loop cannot do is tell you whether the thing it produced is actually right. It only knows whether it finished.&lt;/p&gt;

&lt;p&gt;That gap is not academic. It is the single most common failure builders describe, and this week I watched three people, in three different corners of the internet, name the same thing from three different directions. An agent-ops practitioner put it as a distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the loop stops, teams are tempted to mark success. But an agent can stop after a tool error or an unconfirmed write. Completion is a runtime event. Success is a verified state.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://x.com/stas_sorokin_/status/2083480323476656211" rel="noopener noreferrer"&gt;Stanislav Sorokin&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Completion is a runtime event. Success is a verified state. Nine words, and they carry the entire problem. The loop terminates when the model returns a final answer with no pending tool calls. That is completion. Whether the feature it built does what you wanted, handles the edge cases, guards the auth route, and does not silently break the thing next to it, that is success, and the loop has no idea. It stopped. That is all it knows.&lt;/p&gt;

&lt;p&gt;A builder in r/ClaudeCode arrived at the fix on their own, without a vendor telling them to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need a receipt for each a,b,c agent. That receipt, verified, becomes the record. That's how you not only perform loop engineering, but auditable loop engineering.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://www.reddit.com/r/ClaudeCode/comments/1vdmg1n/" rel="noopener noreferrer"&gt;r/ClaudeCode commenter&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They reinvented acceptance criteria and evidence from scratch because they needed them and nothing shipped them. That is the tell. When people independently build the same missing piece, the piece is not optional. It is load-bearing, and the loop leaves it out.&lt;/p&gt;

&lt;p&gt;Without something to check against, a loop that runs longer does not get you closer to done. It gets you a more expensive way to be wrong. A commenter in r/VibeCodeDevs said it in one line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without those guardrails, loop engineering stops being leverage and just becomes an expensive way to keep the meter running.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://www.reddit.com/r/VibeCodeDevs/comments/1vcf5r1/" rel="noopener noreferrer"&gt;r/VibeCodeDevs commenter&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What changes for you
&lt;/h2&gt;

&lt;p&gt;If you are building with an agent right now, this means your job moved. It did not disappear. When you were prompting one step at a time, you were the verifier: you read each response, caught the wrong turn, and corrected it before the next step. The loop takes that seat. Nobody is reading each turn anymore, which means the standard the agent is checked against has to exist before the run, not in your head during it.&lt;/p&gt;

&lt;p&gt;Concretely, the difference looks like this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Loop with no target: "Build a contact management view." The agent builds a table, calls it done, and stops. It works in the demo. It has no auth guard, the filter breaks on an empty state, and you find out in production.&lt;/p&gt;

&lt;p&gt;Loop with a target: "Build a contact management view. Sortable table with name, company, last-contacted date. Row click opens a detail panel. Only authenticated users can access it; unauthenticated users redirect to /login. Empty state shows a 'no contacts yet' message." Now the loop has something to check itself against, and "done" means every one of those is true.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second version is not a better prompt. It is a definition of done the loop can verify against. That is the input the loop cannot generate for itself, and it is exactly where the whole approach lives or dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;This is the gap BrainGrid is built to close. The loop is a powerful engine with no steering wheel, and the steering wheel is a plan with acceptance criteria. In BrainGrid, you describe what you want to build and the Planning Agent turns it into a requirement with &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;acceptance criteria&lt;/a&gt;, the observable, testable conditions that define done, before any code runs. Then the Builder Agent runs the &lt;a href="https://www.braingrid.ai/loop" rel="noopener noreferrer"&gt;loop&lt;/a&gt;, either in a BrainGrid Cloud sandbox with a live preview or in your own GitHub repo through Claude Code, Cursor, or Codex. When the loop stops, the work is checked against those criteria, and a feature is not done until the evidence says it does what you intended.&lt;/p&gt;

&lt;p&gt;That is the receipt the r/ClaudeCode builder wanted, generated for you instead of hand-rolled. The loop still does the acting and observing. BrainGrid supplies the one thing the loop cannot: a definition of done that outlives any single run and a record of whether the run met it.&lt;/p&gt;

&lt;p&gt;The honest trade-off: writing acceptance criteria up front is work, and for a throwaway script it is work you can skip. Nobody needs a verified definition of done for a one-off data cleanup. The moment the thing you are building is something other people will use, though, the criteria stop being overhead and become the only thing standing between "the loop finished" and "the feature works."&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop is the engine. The spec is the destination.
&lt;/h2&gt;

&lt;p&gt;An agent loop is reason, act, observe, repeat, running until a stop condition is met. That is what turns a model into an agent, and it is genuinely the most important primitive in AI building today. But a loop optimizes for termination, not correctness. It will happily stop on a broken result and report success, because stopping is the only signal it has.&lt;/p&gt;

&lt;p&gt;The models will keep getting better at running the loop. They will not get better at knowing what you meant. That part is still yours, and the way you hand it to the agent is a plan it can check itself against. Give the loop a destination and it becomes leverage. Give it none and it becomes a very fast way to arrive somewhere you did not want to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an agent loop?
&lt;/h3&gt;

&lt;p&gt;An agent loop is the continuous cycle an AI agent runs to complete a task: it reasons about what to do next, takes an action using a tool, observes the result, and repeats, feeding each result back into its context until it reaches a stop condition. This loop is what separates an agent, which can pursue a multi-step goal, from a chatbot, which answers once and stops.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the steps in an agent loop?
&lt;/h3&gt;

&lt;p&gt;Most agent loops have four stages. Reason: the model evaluates the task and decides the next step. Act: it executes a tool, such as running a command, editing a file, or calling an API. Observe: it reads the output or error and feeds it back into its context. Repeat: it starts the next turn with that new information. The fourth step, writing the result back so the next turn is better informed, is what makes it an agent rather than a series of one-off answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an agent loop stop?
&lt;/h3&gt;

&lt;p&gt;A loop ends when the model returns a final answer with no pending tool calls, meaning it believes the work is complete. Harnesses also add safety limits: caps on the number of iterations, a wall-clock time budget, a token budget, and error detection that breaks out when the agent keeps retrying the same failing action. Reaching a stop condition tells you the loop finished; it does not tell you the result is correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between an agent loop and loop engineering?
&lt;/h3&gt;

&lt;p&gt;An agent loop is the underlying cycle, reason, act, observe, repeat. &lt;a href="https://www.braingrid.ai/blog/loop-engineering" rel="noopener noreferrer"&gt;Loop engineering&lt;/a&gt; is the practice of designing the system that runs those loops for you, on a schedule, so you are no longer prompting the agent by hand. Put simply, the agent loop is the engine, and loop engineering is the discipline of building and steering a fleet of those engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my AI agent say it's done when the code is broken?
&lt;/h3&gt;

&lt;p&gt;Because the loop's stop condition is completion, not correctness. The agent stops when it has no more actions queued, which can happen even after a tool error or an unverified change. The loop has no built-in notion of whether the result matches what you wanted. The fix is to give it an external standard to check against, acceptance criteria that define done in observable, testable terms, so "finished" and "correct" stop being the same claim.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the system that takes an idea to a live product you can trust, giving your agent's loop the one thing it can't supply itself: a plan with acceptance criteria to verify against. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/what-is-an-agent-loop" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>How to Run Headless Chrome in Docker for Screenshots (and the Maintenance Tax It Adds)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Thu, 06 Aug 2026 11:41:20 +0000</pubDate>
      <link>https://dev.to/grabbit/how-to-run-headless-chrome-in-docker-for-screenshots-and-the-maintenance-tax-it-adds-2pl0</link>
      <guid>https://dev.to/grabbit/how-to-run-headless-chrome-in-docker-for-screenshots-and-the-maintenance-tax-it-adds-2pl0</guid>
      <description>&lt;p&gt;You can run headless Chrome in Docker, and for a controlled workload it is the right call. The catch is that a bare &lt;code&gt;apt install chromium&lt;/code&gt; plus &lt;code&gt;chrome --headless&lt;/code&gt; does not work: Chrome refuses to start, crashes on big pages, renders text as empty boxes, and leaves zombie processes behind. This guide gives you a Dockerfile that actually runs, explains each flag that makes it work, and names the ongoing maintenance so you can decide whether to run Chrome yourself or hand the render off to an API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;To run headless Chrome in a container you need four things the base image does not give you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chromium plus its shared libraries&lt;/strong&gt; (a slim base ships neither).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The right launch flags&lt;/strong&gt;: &lt;code&gt;--no-sandbox&lt;/code&gt;, &lt;code&gt;--disable-gpu&lt;/code&gt;, &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts&lt;/strong&gt;, or every screenshot renders text as boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An init process&lt;/strong&gt; to reap the zombie Chrome children, plus a &lt;strong&gt;non-root user&lt;/strong&gt; so &lt;code&gt;--no-sandbox&lt;/code&gt; is not a gaping hole.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Miss any one and it fails in a way whose error message does not name the real cause. That is the whole reason this problem has a dozen Stack Overflow answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Dockerfile that works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-slim&lt;/span&gt;

&lt;span class="c"&gt;# Chromium + the shared libs a slim base is missing, fonts, and an init.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;      chromium &lt;span class="se"&gt;\
&lt;/span&gt;      fonts-liberation fonts-noto-color-emoji &lt;span class="se"&gt;\
&lt;/span&gt;      libnss3 libatk-bridge2.0-0 libgbm1 libasound2 &lt;span class="se"&gt;\
&lt;/span&gt;      tini &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Run as a non-root user, because we launch with --no-sandbox below.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--create-home&lt;/span&gt; chrome
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; chrome&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/chrome&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CHROME_BIN=/usr/bin/chromium&lt;/span&gt;

&lt;span class="c"&gt;# tini (PID 1) reaps the zombie Chrome children a long-running renderer leaves.&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["tini", "--"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["chromium", \&lt;/span&gt;
     "--headless=new", \
     "--no-sandbox", \
     "--disable-gpu", \
     "--disable-dev-shm-usage", \
     "--screenshot=/home/chrome/out.png", \
     "--window-size=1280,720", \
     "https://example.com"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and run it, then copy the screenshot out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; chrome-shot &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;:/out"&lt;/span&gt; chrome-shot &lt;span class="se"&gt;\&lt;/span&gt;
  chromium &lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;new &lt;span class="nt"&gt;--no-sandbox&lt;/span&gt; &lt;span class="nt"&gt;--disable-dev-shm-usage&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--screenshot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/out/out.png &lt;span class="nt"&gt;--window-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280,720 https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything after &lt;code&gt;chrome-shot&lt;/code&gt; overrides the &lt;code&gt;CMD&lt;/code&gt;, so you can point it at any URL without rebuilding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each flag is actually fixing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--no-sandbox&lt;/code&gt;&lt;/strong&gt; is the flag everyone hits first. Chrome's sandbox needs Linux user namespaces and kernel calls that Docker's default seccomp and AppArmor profiles block, so Chrome cannot fork its sandboxed children and exits with a terse &lt;code&gt;Failed to move to new namespace&lt;/code&gt;. &lt;code&gt;--no-sandbox&lt;/code&gt; skips the sandbox. It is a real security boundary you are removing, which is exactly why the Dockerfile above runs as a non-root &lt;code&gt;chrome&lt;/code&gt; user: trusted URLs plus a non-root user keeps the blast radius small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--disable-dev-shm-usage&lt;/code&gt;&lt;/strong&gt; is the one that bites later, in production, on a page you did not test with. Docker mounts &lt;code&gt;/dev/shm&lt;/code&gt; at 64MB by default. Chrome uses that shared-memory region for its render process, and a large or image-heavy page overruns it, crashing the tab with &lt;code&gt;session deleted because of page crash&lt;/code&gt;. This flag tells Chrome to write to &lt;code&gt;/tmp&lt;/code&gt; instead. The alternative is &lt;code&gt;docker run --shm-size=1gb&lt;/code&gt;, but the flag travels with the image, so prefer it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--disable-gpu&lt;/code&gt;&lt;/strong&gt; avoids the container trying to reach a GPU that is not there. Harmless to keep even where it is no longer strictly required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--headless=new&lt;/code&gt;&lt;/strong&gt; opts into the modern headless mode (the default implementation since Chrome 112), which is the same browser as headful Chrome, so what you capture matches what a user sees. Older tutorials use bare &lt;code&gt;--headless&lt;/code&gt;, which on current Chrome maps to the new mode anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts the tutorials skip
&lt;/h2&gt;

&lt;p&gt;The Dockerfile runs. That is not the same as being done, and the gap is where the maintenance tax lives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fonts.&lt;/strong&gt; A slim base image ships no fonts, so text renders as tofu boxes and emoji disappear. You install font packages by hand (&lt;code&gt;fonts-liberation&lt;/code&gt;, &lt;code&gt;fonts-noto-color-emoji&lt;/code&gt;, and CJK packs if you capture non-Latin pages). Every new script you need to render is another package to remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zombie processes.&lt;/strong&gt; Chrome spawns child processes, and a long-running container without a real &lt;code&gt;init&lt;/code&gt; as PID 1 accumulates defunct entries until the process table is a mess. That is what &lt;code&gt;tini&lt;/code&gt; (or &lt;code&gt;dumb-init&lt;/code&gt;, or &lt;code&gt;docker run --init&lt;/code&gt;) is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory growth.&lt;/strong&gt; Chrome's memory climbs over a long-lived process, and under load it does not always come back down. The common production workaround is to restart the renderer on a schedule or after N captures, which is its own moving part to build and watch. This is the exact pain developers describe: Chromium memory leaks forcing you to constantly kill and restart pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patching.&lt;/strong&gt; A pinned Chromium is a frozen attack surface. Keeping it current means rebuilding the image on Chrome's release cadence, then re-testing that none of the flags above regressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hard once. All of it is forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to hand the render to an API instead
&lt;/h2&gt;

&lt;p&gt;Running Chrome in Docker earns its keep when you want full control of the browser, capture at high volume where per-request cost matters, or need the container for more than screenshots. If all you need is "give me an image of this URL," the container is a lot of standing infrastructure for one HTTP call.&lt;/p&gt;

&lt;p&gt;A hosted screenshot API removes the image, the flags, the fonts, the &lt;code&gt;/dev/shm&lt;/code&gt; sizing, the zombie reaping, and the restart cron. You send a URL and get a hosted image back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://grabbit.live/api/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_your_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "full_page": true,
    "format": "webp"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is JSON with a hosted &lt;code&gt;image_url&lt;/code&gt; you can cache or embed. The same request handles the things the container makes you handle yourself: it waits for the page, sizes the viewport, and captures the full scrolling page when you pass &lt;code&gt;full_page: true&lt;/code&gt;. Add &lt;code&gt;delay_ms&lt;/code&gt; (0 to 10000) to wait for late-loading content, &lt;code&gt;format&lt;/code&gt; (&lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, or &lt;code&gt;webp&lt;/code&gt;) to pick the output, or &lt;code&gt;selector&lt;/code&gt; to crop to a single element.&lt;/p&gt;

&lt;p&gt;Grabbit is prepaid at a flat $0.002 per live grab, and the credits do not reset or expire monthly, so a spiky screenshot workload does not pay for a subscription tier it never fills. Test-environment keys return a placeholder for free, so you can wire it up before adding a card. This is not the cheapest per-grab rate on the market, but it is one HTTP call with no container to keep alive. See the &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; for the full parameter reference.&lt;/p&gt;

&lt;p&gt;The honest trade is control versus operations. If you want the browser under your own roof, the Dockerfile above is a solid start, and the &lt;a href="https://www.grabbit.live/blog/puppeteer-memory-leak" rel="noopener noreferrer"&gt;maintenance failure modes are worth reading up front&lt;/a&gt;. If you would rather not run Chrome at all, &lt;a href="https://www.grabbit.live/blog/playwright-docker" rel="noopener noreferrer"&gt;skipping the container&lt;/a&gt; is the point of the API.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/chrome-headless-docker" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
