<?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: Hirohisa Tanaka</title>
    <description>The latest articles on DEV Community by Hirohisa Tanaka (@tanahiro2010).</description>
    <link>https://dev.to/tanahiro2010</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%2F4009689%2F61f754c4-028a-4437-a87a-1c780cc7aecc.JPG</url>
      <title>DEV Community: Hirohisa Tanaka</title>
      <link>https://dev.to/tanahiro2010</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tanahiro2010"/>
    <language>en</language>
    <item>
      <title>Why PHP Uses snake_case Functions but camelCase Methods</title>
      <dc:creator>Hirohisa Tanaka</dc:creator>
      <pubDate>Thu, 13 Aug 2026 04:58:14 +0000</pubDate>
      <link>https://dev.to/tanahiro2010/why-php-uses-snakecase-functions-but-camelcase-methods-16jd</link>
      <guid>https://dev.to/tanahiro2010/why-php-uses-snakecase-functions-but-camelcase-methods-16jd</guid>
      <description>&lt;p&gt;Built-in functions are usually snake_case.&lt;br&gt;
Modern framework code is usually camelCase.&lt;/p&gt;

&lt;p&gt;That is PHP: a language where naming conventions can feel oddly split.&lt;/p&gt;

&lt;p&gt;So which naming convention is actually "correct" in PHP?&lt;/p&gt;

&lt;p&gt;I'm tanahiro2010, and this is one of those small-but-persistent questions that keeps coming back whenever I write PHP.&lt;/p&gt;

&lt;p&gt;Let me say this up front: this article is not about declaring either camelCase or snake_case the one true style. Instead, it looks at why PHP's naming conventions appear to differ by layer, traces that split through PHP's history, and offers a practical way to decide how to name things in your own code.&lt;/p&gt;

&lt;p&gt;While writing this, I tried not to rely only on memory or second-hand explanations. Where possible, I checked primary sources such as the official PHP manual, the PHP-FIG website, and the PEAR manual. For historical details I could not fully verify, especially around early PHP-FIG membership and some framework-specific context, I explicitly mark them as unconfirmed or inferential.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on terminology&lt;/strong&gt;&lt;br&gt;
This article includes a few terms that may be unfamiliar if you are new to PHP or web development. When that happens, I add a short explanation in a blockquote like this. You can skip these blocks if you already know the terms; the main argument should still be readable without them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Have you seen code like this?
&lt;/h2&gt;

&lt;p&gt;When writing PHP, you often run into functions like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$replace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$subject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, these names use &lt;code&gt;snake_case&lt;/code&gt;: words connected with underscores.&lt;/p&gt;

&lt;p&gt;But modern PHP code, especially framework-based code, often looks more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getParsedBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$userRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, the names use &lt;code&gt;camelCase&lt;/code&gt;: words joined together, with later words starting with uppercase letters.&lt;/p&gt;

&lt;p&gt;Inside the same language, PHP, two very different-looking styles coexist. This article traces where that split came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The conclusion first&lt;/li&gt;
&lt;li&gt;Why PHP built-in functions look like snake_case&lt;/li&gt;
&lt;li&gt;PHP 3 and the spread of function-based web programming&lt;/li&gt;
&lt;li&gt;PEAR: shared conventions before PSR&lt;/li&gt;
&lt;li&gt;PHP 5 and the rise of OOP PHP&lt;/li&gt;
&lt;li&gt;Framework culture in the late 2000s&lt;/li&gt;
&lt;li&gt;A side note on Symfony helpers&lt;/li&gt;
&lt;li&gt;PHP-FIG and PSR&lt;/li&gt;
&lt;li&gt;What PSR-1 says, and what it does not say&lt;/li&gt;
&lt;li&gt;Why naming still hurts&lt;/li&gt;
&lt;li&gt;Practical guidelines for modern PHP&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The conclusion first
&lt;/h2&gt;

&lt;p&gt;Before getting into the history, here is the conclusion of this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular functions, including global functions, helper functions, and procedural APIs: &lt;code&gt;snake_case&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Class methods: &lt;code&gt;camelCase&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After looking through PHP's history, this feels like the most natural compromise to me.&lt;/p&gt;

&lt;p&gt;That said, this is not an absolute law. Always prioritize the following when they apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If an existing project already has a convention, follow it.&lt;/li&gt;
&lt;li&gt;If your framework has a convention, such as Laravel, Symfony, or WordPress, follow it.&lt;/li&gt;
&lt;li&gt;If you are naming a public API, preserving backward compatibility matters more than stylistic purity.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is PSR?&lt;/strong&gt;&lt;br&gt;
PSR stands for PHP Standard Recommendation. PSRs are standards created by PHP-FIG, a group that defines coding styles and shared interfaces for the PHP ecosystem. PSR-1 and PSR-12 are examples. PSRs are not part of the PHP language specification itself; they are community standards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One important premise: PHP does not enforce naming style at the syntax level. Whether you write names in &lt;code&gt;snake_case&lt;/code&gt;, &lt;code&gt;camelCase&lt;/code&gt;, or something else, the PHP interpreter will usually run the code just fine. In other words, PHP lets you write code that works even if it ignores naming conventions. This becomes important later when we talk about why naming still causes pain.&lt;/p&gt;

&lt;p&gt;So why does "functions use snake_case, methods use camelCase" feel natural in PHP? Let's go back to 1995.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why PHP built-in functions look like snake_case
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PHP did not begin as a carefully designed language
&lt;/h3&gt;

&lt;p&gt;PHP began as a small set of CGI binaries written by Rasmus Lerdorf in 1994 to track visits to his online resume. It was called "Personal Home Page Tools" or "PHP Tools". The source code was released in June 1995 (&lt;a href="https://www.php.net/manual/en/history.php.php" rel="noopener noreferrer"&gt;PHP: History of PHP - Manual&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is CGI?&lt;/strong&gt;&lt;br&gt;
CGI stands for Common Gateway Interface. It is a mechanism that lets a web server call an external program and return the program's output to the browser. In the 1990s, CGI programs written in languages like Perl were a common way to generate dynamic web pages. PHP started in this world as a set of C-based executables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In September 1995, PHP evolved into "FI" or "Forms Interpreter". In April 1996, the two were combined as "PHP/FI". Then in 1997, Andi Gutmans and Zeev Suraski, who were in Tel Aviv at the time, rewrote the parser and worked with Rasmus Lerdorf to create a new language. In June 1998, PHP 3 was released as the official successor to PHP/FI 2.0. The name also changed to the recursive acronym "PHP: Hypertext Preprocessor" (&lt;a href="https://www.php.net/manual/en/history.php.php" rel="noopener noreferrer"&gt;PHP: History of PHP - Manual&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In other words, PHP was not born from the sequence "design a complete language specification, then implement it." It grew from practical personal tools into a language because people needed it. Nobody was designing API naming conventions in anticipation of a massive global ecosystem thirty years later. That is the first point to keep in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  A function culture grew from there
&lt;/h3&gt;

&lt;p&gt;Useful web development features, such as string handling, array operations, file operations, and database access, were added as functions.&lt;/p&gt;

&lt;p&gt;Many functions that still exist in PHP today are part of that lineage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;mb_strlen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;mysqli_connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, many of them use &lt;code&gt;snake_case&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A technical note: why snake_case was likely natural
&lt;/h3&gt;

&lt;p&gt;PHP's implementation is written in C. The core runtime is called the Zend Engine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is the Zend Engine?&lt;/strong&gt;&lt;br&gt;
The Zend Engine is the internal engine that parses and executes PHP code. It was developed by Andi Gutmans and Zeev Suraski and has been the execution foundation of PHP since PHP 3.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In C and its standard library ecosystem, function names are traditionally lowercase and often use underscores, although not always. Examples include names such as &lt;code&gt;strcpy&lt;/code&gt;, &lt;code&gt;memcpy&lt;/code&gt;, and &lt;code&gt;time&lt;/code&gt;. Many early PHP built-in functions were thin wrappers around C functions or C libraries, so it is reasonable to think that C naming culture influenced PHP's function names.&lt;/p&gt;

&lt;p&gt;I do not mean this as a claim that every PHP function directly follows C naming rules. I have not exhaustively verified that. It is better understood as a general tendency.&lt;/p&gt;

&lt;h3&gt;
  
  
  But it is not completely consistent
&lt;/h3&gt;

&lt;p&gt;If the story ended here, we might say "PHP built-ins are all snake_case." But that would not be accurate. Standard classes in PHP often have camelCase or PascalCase-style method names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$reflectionClass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$reflectionMethod&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getParameters&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$dateTime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$timezone&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is Reflection?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ReflectionClass&lt;/code&gt; and &lt;code&gt;ReflectionMethod&lt;/code&gt; are part of PHP's built-in Reflection API. Reflection lets a program inspect information about classes, methods, parameters, modifiers, and more at runtime. Frameworks and testing tools often use it internally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we need to distinguish between "global function culture" and "standard class/method culture". That difference connects directly to the later spread of object-oriented PHP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is OOP?&lt;/strong&gt;&lt;br&gt;
OOP stands for object-oriented programming. It is a way of designing programs around classes, where data and behavior are grouped together as properties and methods. The idea that "class methods use camelCase" comes up repeatedly later in this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The PHP manual also has a page called &lt;a href="https://www.php.net/manual/en/userlandnaming.rules.php" rel="noopener noreferrer"&gt;Userland Naming Guide&lt;/a&gt;. It states that function names should use underscores between words, and that both camelCase and PascalCase appear in class names. It also names &lt;code&gt;strpos()&lt;/code&gt; as an example of an old naming mistake because it does not follow the recommended extension prefix rule.&lt;/p&gt;

&lt;p&gt;So if PHP naming feels inconsistent, you are not imagining it. The official manual itself acknowledges historical inconsistency.&lt;/p&gt;

&lt;p&gt;My summary for this section is: PHP's built-in functions look snake_case not because of a perfectly planned language design, but because early PHP grew out of practical C-based implementation culture.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. PHP 3 and the spread of function-based web programming
&lt;/h2&gt;

&lt;p&gt;PHP 3 was released in June 1998 as the official successor to PHP/FI. Around this point, PHP development expanded from a personal project into a broader multi-person effort. Features were added rapidly through extension modules.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is an extension module?&lt;/strong&gt;&lt;br&gt;
An extension adds functionality to PHP itself. For example, the &lt;code&gt;curl&lt;/code&gt; extension provides HTTP communication through the cURL library, and the &lt;code&gt;mysqli&lt;/code&gt; extension provides MySQL access. Many PHP built-in functions are grouped by extensions like these.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;During this period, practical web development needs such as database access, form handling, and session management drove PHP's growth. Functions were added as needed. There was not yet a strong centralized process for reviewing naming consistency. Functionality and usefulness came first; naming consistency came later, if at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Once a name is public, changing it is hard
&lt;/h3&gt;

&lt;p&gt;Once a function name is public and widely used, it becomes difficult to change. This is a backward compatibility problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is backward compatibility?&lt;/strong&gt;&lt;br&gt;
Backward compatibility, often abbreviated BC, means code written for an older version of software keeps working in newer versions. Renaming or removing a function can break every existing codebase that uses it, so many languages and frameworks treat public API names as something that should not be changed casually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP's old &lt;code&gt;mysql_*&lt;/code&gt; functions, such as &lt;code&gt;mysql_query&lt;/code&gt;, are a good example. They went through deprecation and were eventually removed, but that took a long time. This illustrates how hard it is to change names and APIs after they become widely used. I am keeping this example at a high level here because I did not fully verify the detailed year-by-year timeline for this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming is not decided by philosophy alone
&lt;/h3&gt;

&lt;p&gt;So far, we can see that naming depends on several axes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When it was created&lt;/td&gt;
&lt;td&gt;Pre-2000 PHP built-ins vs post-PSR code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who created it&lt;/td&gt;
&lt;td&gt;Rasmus Lerdorf individually vs a group such as PHP-FIG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which culture it came from&lt;/td&gt;
&lt;td&gt;C-style function culture vs OOP framework culture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whether compatibility can be broken&lt;/td&gt;
&lt;td&gt;Global functions with huge compatibility cost vs new framework APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which API layer it belongs to&lt;/td&gt;
&lt;td&gt;Built-in language functions vs userland classes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is easy to say PHP naming is inconsistent because the design philosophy was inconsistent. But in practice, PHP's naming style is the result of several historical layers overlapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. PEAR: shared conventions before PSR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is PEAR?
&lt;/h3&gt;

&lt;p&gt;Before PSR, PHP already had shared coding conventions. They came from PEAR.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is PEAR?&lt;/strong&gt;&lt;br&gt;
PEAR stands for PHP Extension and Application Repository. It is a repository and package distribution system for reusable PHP libraries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One fact-checking note: in an earlier draft, I wrote that PEAR was released in December 2002. After checking multiple sources, I found that PEAR was started around 1999 by Stig Bakken. It is said to have grown out of discussions at the PHP Developers' Meeting in Tel Aviv in January 2000. I could not find a primary source confirming the 2002 date, so this article treats PEAR as a project that began around 1999. I have not pinned down the exact founding date or release timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  PEAR naming conventions
&lt;/h3&gt;

&lt;p&gt;PEAR had clear naming conventions more than a decade before PSR (&lt;a href="https://pear.php.net/manual/en/standards.naming.php" rel="noopener noreferrer"&gt;PEAR Manual: Naming Conventions&lt;/a&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Class names&lt;/td&gt;
&lt;td&gt;Initial uppercase, hierarchy expressed with underscores&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Net_Finger&lt;/code&gt;, &lt;code&gt;HTML_Upload_Error&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method names&lt;/td&gt;
&lt;td&gt;Lowercase first word, then studly caps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;connect()&lt;/code&gt;, &lt;code&gt;getData()&lt;/code&gt;, &lt;code&gt;buildSomeWidget()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global functions&lt;/td&gt;
&lt;td&gt;Package name prefix, then studly caps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;XML_RPC_serializeData()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constants&lt;/td&gt;
&lt;td&gt;Uppercase with underscores&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DB_DATASOURCENAME&lt;/code&gt;, &lt;code&gt;SERVICES_AMAZON_S3_LICENSEKEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting part is that PEAR recommended a camel-like style even for global functions. So the simple story "PHP functions have always been snake_case" is not quite true. Even before PSR, there was already a separate naming culture inside the PHP community that differed from PHP built-in function style.&lt;/p&gt;

&lt;h3&gt;
  
  
  PEAR class names as pseudo-namespaces
&lt;/h3&gt;

&lt;p&gt;Class names such as &lt;code&gt;Net_Finger&lt;/code&gt; and &lt;code&gt;HTML_Upload_Error&lt;/code&gt; may look strange today, but they had a technical reason.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is a namespace?&lt;/strong&gt;&lt;br&gt;
A namespace is a way to prevent name collisions by giving classes and functions a kind of address. PHP introduced the &lt;code&gt;namespace&lt;/code&gt; keyword in PHP 5.3 in 2009. Before that, PHP did not have native namespaces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the PEAR era, PHP did not have namespaces, so libraries avoided class-name collisions by encoding hierarchy in the class name itself: package name, underscore, class name. Today you might write &lt;code&gt;Net\Finger&lt;/code&gt;; back then, &lt;code&gt;Net_Finger&lt;/code&gt; served a similar purpose through naming convention alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this section tells us
&lt;/h3&gt;

&lt;p&gt;The important point is that the "built-in function layer" and the "library distribution layer" belonged to different cultures.&lt;/p&gt;

&lt;p&gt;Also, my conclusion in this article, "regular functions use snake_case", does not come from PEAR. PEAR actually recommended camel-like global function names. My conclusion comes from PHP's built-in function culture and practical modern PHP context, not directly from PEAR.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. PHP 5 and the rise of OOP PHP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Zend Engine 2.0 changed
&lt;/h3&gt;

&lt;p&gt;PHP 5 was released in July 2004. It introduced Zend Engine 2.0 and a new object model (&lt;a href="https://www.php.net/manual/en/history.php.php" rel="noopener noreferrer"&gt;PHP: History of PHP - Manual&lt;/a&gt;). The biggest technical change was how objects behaved.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In PHP 4, passing or assigning objects could internally copy them.&lt;/li&gt;
&lt;li&gt;In PHP 5, objects were handled through handles, closer to references in many object-oriented languages.&lt;/li&gt;
&lt;li&gt;Access modifiers such as &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;protected&lt;/code&gt;, and &lt;code&gt;public&lt;/code&gt; were introduced.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;interface&lt;/code&gt; and &lt;code&gt;abstract class&lt;/code&gt; were introduced.&lt;/li&gt;
&lt;li&gt;Magic methods such as &lt;code&gt;__construct&lt;/code&gt; and &lt;code&gt;__destruct&lt;/code&gt; were clarified.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What are magic methods?&lt;/strong&gt;&lt;br&gt;
Magic methods are special methods that begin with two underscores, such as &lt;code&gt;__construct()&lt;/code&gt;. PHP calls them automatically at specific moments, such as when an object is created.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These changes made PHP a language where serious class design became practical. This became the foundation for object-oriented PHP culture.&lt;/p&gt;

&lt;h3&gt;
  
  
  From calling functions to writing methods
&lt;/h3&gt;

&lt;p&gt;The look of PHP code also started changing around this period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PHP 4-ish: calling functions&lt;/span&gt;
&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PHP 5 and later: writing methods&lt;/span&gt;
&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getParsedBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method names such as &lt;code&gt;getParsedBody()&lt;/code&gt;, &lt;code&gt;getStatusCode()&lt;/code&gt;, &lt;code&gt;findById()&lt;/code&gt;, &lt;code&gt;setCreatedAt()&lt;/code&gt;, &lt;code&gt;hasPermission()&lt;/code&gt;, and &lt;code&gt;isPublished()&lt;/code&gt; became common. The &lt;code&gt;get&lt;/code&gt; / &lt;code&gt;set&lt;/code&gt; / &lt;code&gt;is&lt;/code&gt; / &lt;code&gt;has&lt;/code&gt; patterns are common not only in PHP, but also in many object-oriented programming communities.&lt;/p&gt;

&lt;p&gt;PHP 5 is where PHP really started to develop a culture of thinking about names at the method level.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Framework culture in the late 2000s
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frameworks that adopted camelCase methods
&lt;/h3&gt;

&lt;p&gt;After PHP 5, frameworks and libraries such as CakePHP, Symfony, Zend Framework, Doctrine, and CodeIgniter appeared and grew.&lt;/p&gt;

&lt;p&gt;Here is a rough summary of their method naming tendencies, based on what I was able to check. This is not an exhaustive verification of every version or module.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Method naming tendency&lt;/th&gt;
&lt;th&gt;General character&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CakePHP&lt;/td&gt;
&lt;td&gt;Full-stack framework&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;Convention-over-configuration MVC framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symfony&lt;/td&gt;
&lt;td&gt;Full-stack framework&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;OOP-oriented components and framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zend Framework&lt;/td&gt;
&lt;td&gt;Framework / library collection&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;Enterprise-oriented design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doctrine&lt;/td&gt;
&lt;td&gt;ORM library&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;Maps database tables to objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeIgniter&lt;/td&gt;
&lt;td&gt;Lightweight framework&lt;/td&gt;
&lt;td&gt;More snake_case-oriented&lt;/td&gt;
&lt;td&gt;Simplicity closer to procedural PHP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a id="glossary-orm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is an ORM?&lt;/strong&gt;&lt;br&gt;
ORM stands for Object-Relational Mapping. It maps database tables to program objects. Doctrine is one of the best-known ORM libraries in PHP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As this table suggests, many major frameworks except CodeIgniter leaned toward camelCase method names. CodeIgniter is an important counterexample: not everything in that era was camelCase. It kept a style closer to procedural PHP and PHP's built-in function culture. I could not fully verify an official primary-source statement for that design intent, so I am treating it as a broad tendency rather than a definitive claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  From here on, some of this is circumstantial
&lt;/h3&gt;

&lt;p&gt;Next comes the question: why did PSR-1 choose camelCase for methods?&lt;/p&gt;

&lt;p&gt;To be honest, I could not find a primary source that directly explains the reasoning behind that choice. So from here, I am presenting a hypothesis based on circumstantial evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hypothesis: camelCase culture was already widespread
&lt;/h3&gt;

&lt;p&gt;My hypothesis is this:&lt;/p&gt;

&lt;p&gt;PSR-1 did not introduce camelCase to PHP from nowhere. Instead, it formalized and stabilized a camelCase culture that had already spread through object-oriented PHP frameworks by the late 2000s.&lt;/p&gt;

&lt;p&gt;To support that hypothesis, we need to look at PHP-FIG.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A side note on Symfony helpers
&lt;/h2&gt;

&lt;p&gt;Symfony 1.x code and documentation reveal an interesting detail. In the Symfony 1.x coding standards, class names and variable names were generally expected to use UpperCamelCase, also called PascalCase, but there were two exceptions: core classes prefixed with lowercase &lt;code&gt;sf&lt;/code&gt;, such as &lt;code&gt;sfController&lt;/code&gt; and &lt;code&gt;sfRequest&lt;/code&gt;, and template variables, which used underscore-separated notation (&lt;a href="https://symfony.com/legacy/doc/gentle-introduction/1_4/en/02-Exploring-Symfony-s-Code" rel="noopener noreferrer"&gt;Symfony 1.4 legacy documentation: Exploring Symfony's Code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I want to be careful here. In an earlier draft, I had written a more specific anecdote: that the Symfony documentation explained helper functions were not camelCase because helpers were functions, and therefore followed PHP core functions. When I rechecked primary sources, I could not confirm that specific Q&amp;amp;A-style explanation. What I did find was the documented rule about underscore notation for template variables.&lt;/p&gt;

&lt;p&gt;So I will avoid stating that helper explanation as fact.&lt;/p&gt;

&lt;p&gt;Still, the verified fact is useful: Symfony 1.x mostly used camelCase/PascalCase conventions for classes and variables, while some areas followed different conventions. This shows that two cultures coexisted even within the same framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a procedural, PHP-built-in-function-like culture&lt;/li&gt;
&lt;li&gt;an OOP framework, camelCase-oriented culture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That coexistence supports the way this article separates functions and methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. PHP-FIG and PSR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is PHP-FIG?
&lt;/h3&gt;

&lt;p&gt;The group behind PSRs is PHP-FIG.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is PHP-FIG?&lt;/strong&gt;&lt;br&gt;
PHP-FIG stands for PHP Framework Interop Group. It is a group created by PHP framework developers to improve interoperability across the PHP ecosystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to the official PHP-FIG FAQ, the group was formed in 2009 at the php|tek conference by several framework developers. It started with around five members and later grew through a voting process to include more than twenty member projects (&lt;a href="https://www.php-fig.org/faqs/" rel="noopener noreferrer"&gt;PHP-FIG FAQ&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is php|tek?&lt;/strong&gt;&lt;br&gt;
php|tek is a conference for the PHP community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is another fact-checking note. In an earlier draft, I listed specific founding members and associated projects, such as Matthew Weier O'Phinney for Zend Framework, Fabien Potencier for Symfony, Paul M. Jones for Solar/Aura, Jonathan Wage for Doctrine, and Nate Abele for Lithium. However, I could not find a primary source on the PHP-FIG website that explicitly identifies the original five people.&lt;/p&gt;

&lt;p&gt;After additional research, I found a retrospective explanation by a participant on the early &lt;code&gt;php.standards&lt;/code&gt; mailing list. It says that the group that became PHP-FIG included representatives from projects such as Agavi, CakePHP, PEAR, Phing, Solar, Symfony, and Zend Framework.&lt;/p&gt;

&lt;p&gt;Combining that information with individual profiles and project histories, such as official Zend/Laminas pages and interviews, gives us this rough argument:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;People associated with early PHP-FIG were involved in these projects.&lt;/li&gt;
&lt;li&gt;Many of those projects already leaned toward camelCase method names.&lt;/li&gt;
&lt;li&gt;Therefore, the early PHP-FIG ecosystem was already strongly connected to camelCase-oriented OOP PHP culture.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Person&lt;/th&gt;
&lt;th&gt;Project associated at the time&lt;/th&gt;
&lt;th&gt;Method naming tendency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Matthew Weier O'Phinney&lt;/td&gt;
&lt;td&gt;Zend Framework&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paul M. Jones&lt;/td&gt;
&lt;td&gt;Solar&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nate Abele&lt;/td&gt;
&lt;td&gt;CakePHP at the time, later Lithium&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fabien Potencier&lt;/td&gt;
&lt;td&gt;Symfony&lt;/td&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Representative names not fully confirmed&lt;/td&gt;
&lt;td&gt;PEAR / Phing / Agavi&lt;/td&gt;
&lt;td&gt;PEAR recommended camel-like global functions; Phing and Agavi not verified here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two cautions:&lt;/p&gt;

&lt;p&gt;First, this is not a definitive mapping of the original five founding members in 2009. It is a combination of early project names and people known to be associated with them.&lt;/p&gt;

&lt;p&gt;Second, I removed the Jonathan Wage / Doctrine pairing from the table because I could not verify it from primary sources during this research.&lt;/p&gt;

&lt;p&gt;Even with those cautions, the evidence I could confirm still supports the idea that early PHP-FIG was surrounded by projects where camelCase methods were already common.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interoperability was the keyword
&lt;/h3&gt;

&lt;p&gt;PHP-FIG's goal was interoperability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is interoperability?&lt;/strong&gt;&lt;br&gt;
Interoperability is the ability of different software systems to work together. In PHP-FIG's context, it means code written for one framework should be easier to combine with code from another framework.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal was to make shared PHP code, cross-framework libraries, and reusable components easier to mix together. One of the first topics PHP-FIG worked on was coding style, which later became PSR-1 and PSR-12.&lt;/p&gt;

&lt;p&gt;PHP-FIG has produced many standards beyond naming and formatting. PSR-3 defines a logging interface, PSR-4 defines an autoloading standard, and PSR-7 defines common HTTP message interfaces. PHP-FIG is not only about coding style; it is about interoperability across the PHP ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. What PSR-1 says, and what it does not say
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The purpose of PSR-1
&lt;/h3&gt;

&lt;p&gt;PSR-1, formally called "Basic Coding Standard", describes its purpose like this (&lt;a href="https://www.php-fig.org/psr/psr-1/" rel="noopener noreferrer"&gt;PSR-1: Basic Coding Standard&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This section of the standard comprises what should be considered the standard coding elements that are required to ensure a high level of technical interoperability between shared PHP code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, PSR-1 defines basic coding elements needed for shared PHP code to interoperate well.&lt;/p&gt;

&lt;h3&gt;
  
  
  What PSR-1 defines
&lt;/h3&gt;

&lt;p&gt;PSR-1 defines the following naming conventions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Class names&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StudlyCaps&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Class constants&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UPPER_CASE_WITH_UNDERSCORES&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method names&lt;/td&gt;
&lt;td&gt;&lt;code&gt;camelCase()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is StudlyCaps?&lt;/strong&gt;&lt;br&gt;
StudlyCaps means writing words together with each word starting with an uppercase letter. It is roughly equivalent to what many people call PascalCase, for example &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For property names, PSR-1 does not require one specific convention. It says &lt;code&gt;$StudlyCaps&lt;/code&gt;, &lt;code&gt;$camelCase&lt;/code&gt;, and &lt;code&gt;$under_score&lt;/code&gt; are all acceptable, but whichever convention is used should be applied consistently within a reasonable scope.&lt;/p&gt;

&lt;p&gt;PSR-12, "Extended Coding Style", adds more detailed rules for indentation, line breaks, class and method declarations, control structures, and so on. But it does not add a new rule such as "variables must be camelCase."&lt;/p&gt;

&lt;h3&gt;
  
  
  What PSR-1 does not define
&lt;/h3&gt;

&lt;p&gt;This distinction is the most important point for this article. PSR-1 does not define naming rules for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;regular global functions&lt;/li&gt;
&lt;li&gt;variable names&lt;/li&gt;
&lt;li&gt;existing PHP built-in functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the claim "Because PSR exists, PHP functions should be camelCase" does not follow from PSR-1 itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why built-in functions do not look like PSR
&lt;/h3&gt;

&lt;p&gt;The reason is simple: many built-in functions existed long before PSR. Renaming them now would break backward compatibility. Also, PSR-1 was never meant to impose naming rules on PHP's built-in functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why PSR does not look like built-in functions
&lt;/h3&gt;

&lt;p&gt;This part cannot be proven definitively from the sources I found. But based on the evidence from sections 6, 7, and 8, I think the most natural reading is:&lt;/p&gt;

&lt;p&gt;PSR-1 did not invent camelCase from scratch. It formalized the camelCase method culture already common in userland OOP frameworks at the time.&lt;/p&gt;

&lt;p&gt;As a side note, PSR-1 has a Meta Document that lists Paul M. Jones as an editor (&lt;a href="https://www.php-fig.org/psr/psr-1/meta/" rel="noopener noreferrer"&gt;PSR-1 Meta Document&lt;/a&gt;). According to Paul M. Jones's own blog post from June 4, 2012, PSR-1 and PSR-2 were accepted by vote, with PSR-1 passing 17 to 0 (&lt;a href="https://paul-m-jones.com/post/2012/06/04/php-fig-psr-1-and-2-accepted/" rel="noopener noreferrer"&gt;Paul M. Jones: PHP-FIG: PSR-1 and PSR-2 Accepted&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In an earlier draft, I wrote that PSR-1 was released on June 5, 2012. The primary source I confirmed is the June 4, 2012 blog report, and I could not confirm the exact vote closing date. So this article uses the more cautious phrasing "accepted in early June 2012." It is also more accurate to say "PHP-FIG created PSR-1, and Paul M. Jones was one of its editors" than to say simply "Paul M. Jones created PSR-1."&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Why naming still hurts
&lt;/h2&gt;

&lt;p&gt;PHP does not enforce naming conventions at the syntax level. Variables, functions, and methods can be snake_case, camelCase, PascalCase, or something else, and PHP will usually run the code.&lt;/p&gt;

&lt;p&gt;This has both benefits and costs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Benefit: Beginners can start writing PHP without worrying about naming rules, which lowers the learning barrier.&lt;/li&gt;
&lt;li&gt;Cost: Once code is written by a team, naming inconsistencies become visible in code review and hurt readability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personally, I have often been confused by code whose naming style was mixed, especially code written without knowledge of PHP's history or PSR, including code I wrote myself in the past.&lt;/p&gt;

&lt;p&gt;I do not think this means the original author was bad. It is a consequence of PHP itself: you can write working code without knowing the conventions.&lt;/p&gt;

&lt;p&gt;That is why it helps to know the history and then decide what your own project should do.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Practical guidelines for modern PHP
&lt;/h2&gt;

&lt;p&gt;Based on the history above, here are the factors I would consider in real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP built-in function culture&lt;/li&gt;
&lt;li&gt;OOP culture&lt;/li&gt;
&lt;li&gt;PSR rules&lt;/li&gt;
&lt;li&gt;project consistency&lt;/li&gt;
&lt;li&gt;framework conventions&lt;/li&gt;
&lt;li&gt;public API compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common arguments from both sides
&lt;/h3&gt;

&lt;p&gt;In practice, this debate usually produces arguments like these. Both sides have reasonable points. This article is not trying to prove one style superior to the other.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Camp&lt;/th&gt;
&lt;th&gt;Common argument&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;snake_case&lt;/td&gt;
&lt;td&gt;Matches PHP built-in functions; word boundaries are visually clear; less worry about uppercase/lowercase typos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;Matches PSR-1 for methods; aligns with OOP culture in Java, JavaScript, C#, and others; fits major framework conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My position is that we can reconcile both sides by separating the layer: inside classes or outside classes.&lt;/p&gt;

&lt;p&gt;Here is the concrete recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;snake_case&lt;/code&gt; for global functions, helper functions, and procedural APIs.

&lt;ul&gt;
&lt;li&gt;Reason: This aligns with PHP's built-in function culture and keeps procedural code visually consistent with the standard library.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;camelCase&lt;/code&gt; for class methods.

&lt;ul&gt;
&lt;li&gt;Reason: This follows OOP PHP culture and PSR-1.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For properties and variables, PSR-1 does not prescribe one rule, so prioritize consistency within the relevant scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And again, always prioritize these rules when they apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a project already consistently uses snake_case methods, follow that project.&lt;/li&gt;
&lt;li&gt;Respect the conventions of the surrounding ecosystem, such as Laravel, Symfony, or WordPress.&lt;/li&gt;
&lt;li&gt;For public APIs, preserving backward compatibility comes first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Before and after
&lt;/h3&gt;

&lt;p&gt;Suppose you have a class with mixed naming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: inconsistent method names&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;delete_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying this article's guideline, class methods become camelCase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After: methods unified as camelCase&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;deleteUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But helper functions outside a class remain snake_case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Plain helper functions outside classes stay snake_case&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;format_currency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical rule is: separate by whether the name belongs inside a class or outside one.&lt;/p&gt;

&lt;p&gt;Here is the decision flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    A[You need to choose a name] --&amp;gt; B{Does the project already have a convention?}
    B --&amp;gt;|Yes| C[Follow it]
    B --&amp;gt;|No| D{Does the framework have a convention?}
    D --&amp;gt;|Yes| C
    D --&amp;gt;|No| E{Is it a class method?}
    E --&amp;gt;|Yes| F[camelCase]
    E --&amp;gt;|No| G{Is it a global function or helper?}
    G --&amp;gt;|Yes| H[snake_case]
    G --&amp;gt;|No| I[Other cases: prioritize consistency within scope]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These conventions can also be checked mechanically instead of relying only on human review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is static analysis?&lt;/strong&gt;&lt;br&gt;
Static analysis means analyzing source code without running it, usually to detect possible bugs, type issues, or style violations. In PHP, PHPStan is one of the most widely used static analysis tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools such as PHP_CodeSniffer, which can detect and automatically fix coding style violations, and PHPStan, which performs static analysis, can help teams enforce PSR-1/PSR-12 and other style rules consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Summary
&lt;/h2&gt;

&lt;p&gt;To summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP naming conventions are not one single unified system.&lt;/li&gt;
&lt;li&gt;PHP built-in function culture, PEAR/library culture, OOP framework culture, and PSR culture exist as historical layers.&lt;/li&gt;
&lt;li&gt;Looking at that history, "functions use snake_case, methods use camelCase" feels like a natural practical guideline.&lt;/li&gt;
&lt;li&gt;But it is not an absolute rule. Existing project conventions, framework conventions, and backward compatibility always come first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was never especially good at history classes in school, but researching PHP's history was genuinely fun. If this article made you a little more curious, I would be happy. And if it makes you want to dig into the later history of PSR-2 to PSR-12, or the changes brought by PHP 7 and PHP 8, that would be even better.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/history.php.php" rel="noopener noreferrer"&gt;PHP: History of PHP - Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php-fig.org/psr/psr-1/" rel="noopener noreferrer"&gt;PHP-FIG: PSR-1 Basic Coding Standard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php-fig.org/psr/psr-1/meta/" rel="noopener noreferrer"&gt;PHP-FIG: PSR-1 Meta Document&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php-fig.org/faqs/" rel="noopener noreferrer"&gt;PHP-FIG: Frequently Asked Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php-fig.org/personnel/" rel="noopener noreferrer"&gt;PHP-FIG: Personnel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pear.php.net/manual/en/standards.naming.php" rel="noopener noreferrer"&gt;PEAR Manual: Naming Conventions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/userlandnaming.rules.php" rel="noopener noreferrer"&gt;PHP Manual: Userland Naming Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://symfony.com/legacy/doc/gentle-introduction/1_4/en/02-Exploring-Symfony-s-Code" rel="noopener noreferrer"&gt;Symfony 1.4 legacy documentation: Exploring Symfony's Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paul-m-jones.com/post/2012/06/04/php-fig-psr-1-and-2-accepted/" rel="noopener noreferrer"&gt;Paul M. Jones: PHP-FIG: PSR-1 and PSR-2 Accepted (2012-06-04)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news-web.php.net/php.standards/30" rel="noopener noreferrer"&gt;php.standards mailing list: Re: The How and the Why of this group as I remember it.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>history</category>
      <category>camelcase</category>
      <category>snakecase</category>
    </item>
    <item>
      <title>If You Can Give a Lightning Talk, You Can Probably Go Bungee Jumping</title>
      <dc:creator>Hirohisa Tanaka</dc:creator>
      <pubDate>Sat, 08 Aug 2026 12:42:03 +0000</pubDate>
      <link>https://dev.to/tanahiro2010/if-you-can-give-a-lightning-talk-you-can-probably-go-bungee-jumping-4005</link>
      <guid>https://dev.to/tanahiro2010/if-you-can-give-a-lightning-talk-you-can-probably-go-bungee-jumping-4005</guid>
      <description>&lt;h1&gt;
  
  
  If You Can Give a Lightning Talk, You Can Probably Go Bungee Jumping
&lt;/h1&gt;

&lt;p&gt;I have a question for anyone who has ever given a lightning talk.&lt;/p&gt;

&lt;p&gt;Do you remember those few dozen seconds right before you walked on stage?&lt;/p&gt;

&lt;p&gt;Do you remember what was happening to your body?&lt;/p&gt;

&lt;p&gt;I do.&lt;/p&gt;

&lt;p&gt;In fact, this article itself started as something I talked about during a lightning talk. And while I was speaking, a strange thought suddenly popped into my head:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Wait. Aren’t lightning talks kind of like bungee jumping?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Written down like that, it sounds like an absurd claim.&lt;/p&gt;

&lt;p&gt;I know.&lt;/p&gt;

&lt;p&gt;But at least in my own experience, the excitement I feel right before stepping onto a stage is surprisingly similar to what I feel when standing on a bungee platform and looking down.&lt;/p&gt;

&lt;p&gt;So in this article, I want to develop the ridiculous-sounding claim in the title — &lt;strong&gt;“If you can give a lightning talk, you can probably go bungee jumping”&lt;/strong&gt; — while keeping the casual spirit of the original idea and giving it at least some concrete reasoning.&lt;/p&gt;

&lt;p&gt;If, by the end of this article, the thought &lt;em&gt;“Bungee jumping sounds kind of interesting”&lt;/em&gt; appears in your head before &lt;em&gt;“Lightning talks are scary,”&lt;/em&gt; then this article has accomplished its mission.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nervousness Before a Lightning Talk and the Fear Before a Bungee Jump
&lt;/h2&gt;

&lt;p&gt;That restless feeling right before a lightning talk is probably nervousness.&lt;/p&gt;

&lt;p&gt;The similar feeling right before a bungee jump is probably fear.&lt;/p&gt;

&lt;p&gt;Strictly speaking, these are different emotions.&lt;/p&gt;

&lt;p&gt;Nervousness comes from uncertainty: &lt;em&gt;Can I do this well?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fear comes from perceived danger: &lt;em&gt;Am I safe?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Their causes are completely different.&lt;/p&gt;

&lt;p&gt;But if we move one level higher and look at what the body actually does, the two experiences start looking surprisingly similar.&lt;/p&gt;

&lt;p&gt;Your heart rate increases.&lt;/p&gt;

&lt;p&gt;Your palms get a little sweaty.&lt;/p&gt;

&lt;p&gt;Your breathing becomes shallow.&lt;/p&gt;

&lt;p&gt;You cannot quite settle down.&lt;/p&gt;

&lt;p&gt;And somehow, two contradictory thoughts exist at the same time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I want to escape.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I still want to do this.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More importantly, the peak of that reaction happens &lt;strong&gt;before the experience actually begins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With a lightning talk, the most nerve-racking moment is often right before stepping onto the stage, or just before opening your slides and saying the first sentence.&lt;/p&gt;

&lt;p&gt;Once you actually start talking, the nervousness tends to drop very quickly.&lt;/p&gt;

&lt;p&gt;Bungee jumping feels similar.&lt;/p&gt;

&lt;p&gt;Standing on the platform, having the equipment checked, and finally looking down is the scariest part.&lt;/p&gt;

&lt;p&gt;Once you jump, fear is no longer the only thing occupying your brain.&lt;/p&gt;

&lt;p&gt;There is another similarity too: once the experience begins, your body almost seems to take over.&lt;/p&gt;

&lt;p&gt;Once you start a lightning talk, your main remaining option is to keep talking.&lt;/p&gt;

&lt;p&gt;Once you jump off a bungee platform, continuing to fall is no longer exactly optional.&lt;/p&gt;

&lt;p&gt;That structure — &lt;strong&gt;the moment where starting removes most of your ways to back out&lt;/strong&gt; — might be one of the biggest reasons why the two experiences feel so similar to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;===&lt;/code&gt; Is False, but &lt;code&gt;==&lt;/code&gt; Might Be True
&lt;/h2&gt;

&lt;p&gt;Obviously, a lightning talk and bungee jumping are completely different activities.&lt;/p&gt;

&lt;p&gt;A lightning talk is an intellectual activity where you share ideas with an audience.&lt;/p&gt;

&lt;p&gt;Bungee jumping involves physically falling from a very high place while attached to a cord.&lt;/p&gt;

&lt;p&gt;The actions are different.&lt;/p&gt;

&lt;p&gt;The skills involved are different.&lt;/p&gt;

&lt;p&gt;The risks are definitely different.&lt;/p&gt;

&lt;p&gt;If we borrow a programming metaphor:&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="nx"&gt;lightningTalk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;bungeeJumping&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strict equality does not hold.&lt;/p&gt;

&lt;p&gt;But what if we narrow the comparison?&lt;/p&gt;

&lt;p&gt;What if we only compare these three things?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The emotional peak happens before starting.&lt;/li&gt;
&lt;li&gt;Once it starts, time feels extremely short.&lt;/li&gt;
&lt;li&gt;After it ends, you somehow want to do it again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under those conditions, at least in my head:&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="nx"&gt;lightningTalk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;bungeeJumping&lt;/span&gt;
&lt;span class="c1"&gt;// surprisingly true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we allow a little type coercion, they start looking oddly equivalent.&lt;/p&gt;

&lt;p&gt;But if we require both the type and value to match exactly, of course they are not.&lt;/p&gt;

&lt;p&gt;That is roughly the level of seriousness with which this entire article should be read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Lightning Talks and Bungee Jumping
&lt;/h2&gt;

&lt;p&gt;To make the argument slightly more structured, here is a direct comparison.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Perspective&lt;/th&gt;
&lt;th&gt;Lightning Talk&lt;/th&gt;
&lt;th&gt;Bungee Jumping&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before it starts&lt;/td&gt;
&lt;td&gt;Nervousness reaches its peak&lt;/td&gt;
&lt;td&gt;Fear reaches its peak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After it starts&lt;/td&gt;
&lt;td&gt;Time feels extremely short&lt;/td&gt;
&lt;td&gt;Time feels extremely short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After it ends&lt;/td&gt;
&lt;td&gt;You want to speak again&lt;/td&gt;
&lt;td&gt;You want to jump again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first similarity is that the emotional peak happens before the activity begins.&lt;/p&gt;

&lt;p&gt;For a lightning talk, it happens right before going on stage.&lt;/p&gt;

&lt;p&gt;For a bungee jump, it happens when you are standing on the platform and looking down.&lt;/p&gt;

&lt;p&gt;In my experience, the anxiety or fear rarely gets stronger after starting.&lt;/p&gt;

&lt;p&gt;The second similarity is how short the experience feels once it begins.&lt;/p&gt;

&lt;p&gt;Lightning talks are usually around five to ten minutes long, and once you start speaking, they tend to end much faster than expected.&lt;/p&gt;

&lt;p&gt;A bungee jump is even shorter.&lt;/p&gt;

&lt;p&gt;The period between jumping and reaching the end of the fall is only a matter of seconds, and subjectively it feels even shorter.&lt;/p&gt;

&lt;p&gt;The third similarity is that immediately afterward, you want to do it again.&lt;/p&gt;

&lt;p&gt;After a lightning talk, I often find myself thinking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What should I talk about next?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After bungee jumping, strangely enough, I get a similar feeling:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I kind of want to jump again.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is entirely based on my own experience.&lt;/p&gt;

&lt;p&gt;I have no scientific evidence that everyone reacts this way.&lt;/p&gt;

&lt;p&gt;But at least for me, it happens almost every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Other Types of Public Speaking?
&lt;/h2&gt;

&lt;p&gt;At this point, there is an obvious question.&lt;/p&gt;

&lt;p&gt;If public speaking in general feels similar to bungee jumping, why specifically talk about lightning talks?&lt;/p&gt;

&lt;p&gt;My answer is that not every kind of speaking feels the same.&lt;/p&gt;

&lt;p&gt;The speaking activities I have personally experienced can roughly be divided into three categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightning talks&lt;/li&gt;
&lt;li&gt;Regular conference or seminar talks&lt;/li&gt;
&lt;li&gt;Hands-on workshop instruction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of these, lightning talks feel the most like bungee jumping.&lt;/p&gt;

&lt;p&gt;A normal conference or seminar presentation tends to be much longer.&lt;/p&gt;

&lt;p&gt;It might last thirty minutes, an hour, or sometimes even longer.&lt;/p&gt;

&lt;p&gt;When you speak for that long, the nervousness gradually fades during the presentation. At some point, it starts feeling almost routine.&lt;/p&gt;

&lt;p&gt;That is not a bad thing.&lt;/p&gt;

&lt;p&gt;Preparing a longer presentation forces you to research the topic more deeply.&lt;/p&gt;

&lt;p&gt;The research itself often improves your understanding as an engineer.&lt;/p&gt;

&lt;p&gt;Questions from the audience and the preparation process can also expose gaps in your own knowledge.&lt;/p&gt;

&lt;p&gt;Those are all valuable things.&lt;/p&gt;

&lt;p&gt;But on the specific axis of &lt;strong&gt;“How similar is this to bungee jumping?”&lt;/strong&gt;, they do not matter very much.&lt;/p&gt;

&lt;p&gt;A long presentation feels less like jumping and more like walking a long distance until you finally reach the destination.&lt;/p&gt;

&lt;p&gt;Hands-on workshops are even more different.&lt;/p&gt;

&lt;p&gt;During a workshop, teaching is only part of the job.&lt;/p&gt;

&lt;p&gt;You also deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Differences between participants’ environments&lt;/li&gt;
&lt;li&gt;Mysterious errors&lt;/li&gt;
&lt;li&gt;Dependencies that refuse to install&lt;/li&gt;
&lt;li&gt;Sample code that suddenly stops working&lt;/li&gt;
&lt;li&gt;Problems that somehow only happen on one person’s machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I enjoy that too.&lt;/p&gt;

&lt;p&gt;But once a workshop begins, the dominant feeling is not &lt;strong&gt;“I’m jumping.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I am operating a live system.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So among the kinds of speaking I have experienced, lightning talks still feel the most similar to bungee jumping.&lt;/p&gt;

&lt;p&gt;They are short.&lt;/p&gt;

&lt;p&gt;The emotional peak happens right before the start.&lt;/p&gt;

&lt;p&gt;And after they end, you often want to do them again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scariest Part Is Before the Jump
&lt;/h2&gt;

&lt;p&gt;There is an obvious response to everything I have written so far:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Bungee jumping is scary.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;It is.&lt;/p&gt;

&lt;p&gt;I am not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;But for me, most of that fear is concentrated before the jump.&lt;/p&gt;

&lt;p&gt;You stand on the platform.&lt;/p&gt;

&lt;p&gt;You look down.&lt;/p&gt;

&lt;p&gt;You suddenly understand exactly how high you are.&lt;/p&gt;

&lt;p&gt;That moment is genuinely scary.&lt;/p&gt;

&lt;p&gt;But once you actually jump, the experience becomes surprisingly short.&lt;/p&gt;

&lt;p&gt;The falling sensation is somewhat similar to the drop on a roller coaster.&lt;/p&gt;

&lt;p&gt;There is definitely excitement after the jump begins, but the highest concentration of emotion exists in the moments before it.&lt;/p&gt;

&lt;p&gt;Lightning talks work in a similar way.&lt;/p&gt;

&lt;p&gt;The strongest nervousness usually happens before the talk.&lt;/p&gt;

&lt;p&gt;After it ends, instead of thinking only about how stressful it was, I naturally start wondering what I should speak about next.&lt;/p&gt;

&lt;p&gt;The important point is not that fear or nervousness disappears.&lt;/p&gt;

&lt;p&gt;It is understanding &lt;strong&gt;where the peak actually happens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And in both experiences, the peak comes before the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commitment Makes Backing Out Harder
&lt;/h2&gt;

&lt;p&gt;There is one more major similarity between lightning talks and bungee jumping.&lt;/p&gt;

&lt;p&gt;Making a commitment in advance makes it harder to back out later.&lt;/p&gt;

&lt;p&gt;Before submitting a lightning talk proposal, you can keep telling yourself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Maybe I’ll speak someday.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no problem with that.&lt;/p&gt;

&lt;p&gt;But once you submit the talk, something changes.&lt;/p&gt;

&lt;p&gt;Your name is now attached to a future event.&lt;/p&gt;

&lt;p&gt;You have accepted the role of &lt;strong&gt;“the person who is going to speak.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That suddenly turns a vague idea into an actual commitment.&lt;/p&gt;

&lt;p&gt;Now you have to make the slides.&lt;/p&gt;

&lt;p&gt;You have to research the topic.&lt;/p&gt;

&lt;p&gt;You have to prepare.&lt;/p&gt;

&lt;p&gt;Bungee jumping has a similar structure.&lt;/p&gt;

&lt;p&gt;Thinking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I want to try bungee jumping someday.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;can remain a vague idea forever.&lt;/p&gt;

&lt;p&gt;But saying publicly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“I’m going bungee jumping.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;makes the idea slightly more real.&lt;/p&gt;

&lt;p&gt;Your psychological escape route gets a little narrower.&lt;/p&gt;

&lt;p&gt;And personally, I find that process strangely entertaining.&lt;/p&gt;

&lt;p&gt;Of course, this is not an argument for forcing people into things they genuinely cannot or do not want to do.&lt;/p&gt;

&lt;p&gt;The idea is much lighter than that.&lt;/p&gt;

&lt;p&gt;Sometimes, making a half-joking, half-serious commitment is enough to make your future self actually move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign Up for a Lightning Talk So You Can Go Bungee Jumping
&lt;/h2&gt;

&lt;p&gt;You might have reached this point thinking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Bungee jumping still sounds difficult, but maybe I could try a lightning talk.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, that is not quite the order I am proposing.&lt;/p&gt;

&lt;p&gt;I am suggesting that you give a lightning talk &lt;strong&gt;so that you can eventually go bungee jumping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Submitting a lightning talk is a useful exercise in creating commitment.&lt;/p&gt;

&lt;p&gt;It teaches you how to turn:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Maybe someday.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Okay. I guess I’m actually doing this.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And compared with jumping off a tall platform, a five-minute talk is a relatively manageable first step.&lt;/p&gt;

&lt;p&gt;That said, once you submit a lightning talk, another problem appears:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do you actually do next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is roughly how I prepare.&lt;/p&gt;

&lt;p&gt;First, I research the topic as broadly as possible.&lt;/p&gt;

&lt;p&gt;I look through official documentation, related articles, implementation examples, old notes, and anything else that seems remotely relevant.&lt;/p&gt;

&lt;p&gt;At this stage, I intentionally avoid narrowing the scope too early.&lt;/p&gt;

&lt;p&gt;Then I dump everything into a note-taking app or Markdown file.&lt;/p&gt;

&lt;p&gt;I do not worry about structure.&lt;/p&gt;

&lt;p&gt;I write bullet points.&lt;/p&gt;

&lt;p&gt;I nest ideas.&lt;/p&gt;

&lt;p&gt;I add random observations.&lt;/p&gt;

&lt;p&gt;I leave things in even when I am not sure whether they will be useful later.&lt;/p&gt;

&lt;p&gt;Once I have accumulated roughly ten thousand Japanese characters’ worth of material, I finally start thinking seriously about the structure of the talk.&lt;/p&gt;

&lt;p&gt;Only then do I decide what to keep and what to remove.&lt;/p&gt;

&lt;p&gt;For me, trying to create a clean structure from the beginning often causes the work to stall because I simply do not have enough raw material yet.&lt;/p&gt;

&lt;p&gt;So instead, I prefer to collect too much information first and cut it down afterward.&lt;/p&gt;

&lt;p&gt;I also find it useful to bounce ideas off AI tools.&lt;/p&gt;

&lt;p&gt;Ideas that seem perfectly connected inside my own head often reveal logical gaps when I try to explain them to someone else.&lt;/p&gt;

&lt;p&gt;Even when the conversation partner is an AI, questions such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“How strongly can you actually support that claim?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What should the audience take away from this?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;can force me to organize my thoughts much more clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, So Who Wants to Go Bungee Jumping?
&lt;/h2&gt;

&lt;p&gt;So, after all of that, here is my conclusion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you can give a lightning talk, I think you can probably go bungee jumping too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we inspect the two activities strictly, there are obviously countless differences.&lt;/p&gt;

&lt;p&gt;But if we only compare these three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The emotional peak happens before starting.&lt;/li&gt;
&lt;li&gt;Once it starts, time feels extremely short.&lt;/li&gt;
&lt;li&gt;After it ends, you somehow want to do it again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I think lightning talks and bungee jumping are surprisingly similar experiences.&lt;/p&gt;

&lt;p&gt;And perhaps the most important part is the act of commitment itself.&lt;/p&gt;

&lt;p&gt;You make a decision in advance.&lt;/p&gt;

&lt;p&gt;You slightly reduce the number of escape routes available to your future self.&lt;/p&gt;

&lt;p&gt;And somewhere inside that situation, fear and excitement start existing at the same time.&lt;/p&gt;

&lt;p&gt;So yes.&lt;/p&gt;

&lt;p&gt;I recommend bungee jumping.&lt;/p&gt;

&lt;p&gt;Half as a joke.&lt;/p&gt;

&lt;p&gt;Half seriously.&lt;/p&gt;

&lt;p&gt;Whether you actually jump is entirely up to you.&lt;/p&gt;

&lt;p&gt;But if this article made you even slightly curious about it, feel free to reach out.&lt;/p&gt;

&lt;p&gt;Maybe we should go jump together someday.&lt;/p&gt;

</description>
      <category>career</category>
      <category>community</category>
      <category>beginners</category>
      <category>speaking</category>
    </item>
    <item>
      <title>I Built a Bridge for Google's New WebMCP Draft Spec — Here's What Broke</title>
      <dc:creator>Hirohisa Tanaka</dc:creator>
      <pubDate>Mon, 03 Aug 2026 10:22:51 +0000</pubDate>
      <link>https://dev.to/tanahiro2010/i-built-a-bridge-for-googles-new-webmcp-draft-spec-heres-what-broke-1691</link>
      <guid>https://dev.to/tanahiro2010/i-built-a-bridge-for-googles-new-webmcp-draft-spec-heres-what-broke-1691</guid>
      <description>&lt;p&gt;Hi, everyone.&lt;br&gt;
I usually go by &lt;strong&gt;tanahiro2010&lt;/strong&gt; in Japan. &lt;br&gt;
I'm a member of GDG Greater Kwansai.&lt;br&gt;
I gave the first-half talk at the Google I/O Extended Osaka 2026 hands-on session, "Let's Build WebMCP and Call It from an AI Agent!"&lt;br&gt;
This is a write-up of that hands-on session for Qiita (translated here for Dev.to).&lt;br&gt;
Here's the codelab we used:&lt;br&gt;
&lt;a href="https://learn.gdgs.jp/webmcp-agent/" rel="noopener noreferrer"&gt;https://learn.gdgs.jp/webmcp-agent/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is for people who already know MCP but have never heard of WebMCP, and for people who want to know "so what's this new spec Google put out, actually like?"&lt;/p&gt;
&lt;h2&gt;
  
  
  Have you heard of WebMCP?
&lt;/h2&gt;

&lt;p&gt;Let me just ask straight up: have you heard of WebMCP?&lt;br&gt;
I hadn't even heard it existed until I started putting together the hands-on materials.&lt;br&gt;
Just from the name, I assumed it was "the Web version of MCP."&lt;br&gt;
But once I actually read the spec and implemented it, it turned out to be a much quirkier spec than I expected.&lt;br&gt;
In this article I'll walk through what's actually in it, and what I learned by getting my hands dirty with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  First, what is MCP?
&lt;/h2&gt;

&lt;p&gt;Before getting into WebMCP, let's recap MCP (Model Context Protocol).&lt;br&gt;
MCP is a common interface for connecting external tools to an AI Agent.&lt;br&gt;
The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant Agent as AI Agent
    participant Server as MCP Server
    Agent-&amp;gt;&amp;gt;Server: Launch (stdio / HTTP)
    Server--&amp;gt;&amp;gt;Agent: tools/list (list of available tools)
    Agent-&amp;gt;&amp;gt;Server: tools/call (tool name + args)
    Server-&amp;gt;&amp;gt;Server: Execute the tool
    Server--&amp;gt;&amp;gt;Agent: Return the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point is that &lt;strong&gt;the tools stay registered for as long as the Agent is running&lt;/strong&gt;.&lt;br&gt;
Whether it's a local file operation or a tool that hits an internal API, you can keep calling it as long as you don't kill the Agent.&lt;br&gt;
The &lt;code&gt;webmcp-bridge-mcp&lt;/code&gt; I built for this is also just an ordinary MCP Server listening on stdio.&lt;br&gt;
From the perspective of a client like the Antigravity CLI, it's nothing more than "one more run-of-the-mill MCP Server."&lt;br&gt;
What's unusual is the WebMCP side, which I'll get to next.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is WebMCP?
&lt;/h2&gt;

&lt;p&gt;WebMCP is a draft spec published by the W3C Web Machine Learning Community Group.&lt;br&gt;
&lt;a href="https://webmachinelearning.github.io/webmcp/" rel="noopener noreferrer"&gt;https://webmachinelearning.github.io/webmcp/&lt;/a&gt;&lt;br&gt;
As of writing, it's the February 2026 draft — still at the proposal stage.&lt;/p&gt;

&lt;p&gt;In one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A mechanism by which a web page itself declares its own features as tools for an Agent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two ways to register a tool.&lt;/p&gt;
&lt;h3&gt;
  
  
  Imperative
&lt;/h3&gt;

&lt;p&gt;You register a tool directly from JavaScript.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&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="s2"&gt;reserve_hotel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reserve a hotel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&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;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&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;span class="nx"&gt;city&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape — &lt;code&gt;name&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;inputSchema&lt;/code&gt; / &lt;code&gt;execute&lt;/code&gt; — is almost identical to an MCP tool definition.&lt;br&gt;
Anyone who's touched MCP will look at this and immediately think, "oh, this is the same shape as that."&lt;/p&gt;
&lt;h3&gt;
  
  
  Declarative
&lt;/h3&gt;

&lt;p&gt;You turn an existing &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; into a tool just by adding attributes to it.&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;form&lt;/span&gt; &lt;span class="na"&gt;toolname=&lt;/span&gt;&lt;span class="s"&gt;"search_hotels"&lt;/span&gt; &lt;span class="na"&gt;tooldescription=&lt;/span&gt;&lt;span class="s"&gt;"Search hotels"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt; &lt;span class="na"&gt;toolparamdescription=&lt;/span&gt;&lt;span class="s"&gt;"City to search hotels in"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a form with &lt;code&gt;toolname&lt;/code&gt; / &lt;code&gt;tooldescription&lt;/code&gt; is found, a JSON Schema is automatically assembled from each &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;'s &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;required&lt;/code&gt;, and &lt;code&gt;toolparamdescription&lt;/code&gt; (or, if that's absent, the text of the associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Here's the interesting part: internally, the declarative form gets normalized into the same &lt;code&gt;registerTool()&lt;/code&gt; call as the imperative form.&lt;br&gt;
Rather than having two separate APIs, the declarative form is implemented as syntactic sugar over the imperative one.&lt;br&gt;
Personally, I genuinely like this design.&lt;/p&gt;

&lt;p&gt;The submission result is received via &lt;code&gt;SubmitEvent#respondWith()&lt;/code&gt;, as specified.&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="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentInvoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;event.agentInvoked&lt;/code&gt; lets you tell whether a human clicked the button or an Agent submitted the form.&lt;br&gt;
It's a small detail, but it ends up mattering a lot later.&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP and WebMCP: similar names, but...
&lt;/h2&gt;

&lt;p&gt;Because the name and the shape of the API are so similar, my initial impression was roughly "it's just the Web version of MCP."&lt;br&gt;
But once I actually dug in, they turned out to differ clearly along three axes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;WebMCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;AI Agents in general&lt;/td&gt;
&lt;td&gt;Per the spec, mainly browser-embedded Agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registration timing&lt;/td&gt;
&lt;td&gt;Once, at Agent startup&lt;/td&gt;
&lt;td&gt;Every time the page is opened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session lifetime&lt;/td&gt;
&lt;td&gt;Until the Agent is terminated&lt;/td&gt;
&lt;td&gt;Only while the page is open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution location&lt;/td&gt;
&lt;td&gt;Local or a service server&lt;/td&gt;
&lt;td&gt;Inside that browser, on that page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference I felt most viscerally was the session lifetime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stateDiagram-v2
    [*] --&amp;gt; Unavailable
    Unavailable --&amp;gt; Available: Open the tab
    Available --&amp;gt; Unavailable: Close/leave the tab
    Unavailable --&amp;gt; Available: Return to the tab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An MCP tool can be called as long as the Agent is up and running.&lt;br&gt;
But a WebMCP tool is completely tied to "whether that page is currently open."&lt;br&gt;
The moment you switch tabs, that tool disappears from view; go back, and it reappears.&lt;br&gt;
The idea that "the tab's lifetime = the tool's lifetime" felt pretty fresh to someone coming from an MCP-only mindset.&lt;/p&gt;
&lt;h2&gt;
  
  
  How much of it actually works, as of August 2026
&lt;/h2&gt;

&lt;p&gt;As of this writing (August 2026), WebMCP is still a draft at the proposal stage.&lt;br&gt;
Even Chrome's own docs introduce it as an "upcoming feature."&lt;br&gt;
&lt;a href="https://developer.chrome.com/docs/ai/webmcp?hl=ja" rel="noopener noreferrer"&gt;https://developer.chrome.com/docs/ai/webmcp?hl=ja&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can join the Origin Trial starting with Chrome 149&lt;/li&gt;
&lt;li&gt;To just try it locally, use &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It's explicitly noted that the API may still change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, you can't assume "the native implementation is enabled in every attendee's browser at the hands-on venue."&lt;br&gt;
This is where the thing I built comes in.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built — WebMCP Bridge
&lt;/h2&gt;

&lt;p&gt;There was a genuine gap between the hands-on requirements and the WebMCP spec if you approached it head-on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the spec assumes: WebMCP is for browser-embedded Agents&lt;/li&gt;
&lt;li&gt;The reality of the hands-on: participants wanted to call it from an existing MCP-capable Agent, like the Antigravity CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I decided to build something to bridge the two.&lt;br&gt;
With that in mind, I built a Chrome Extension and an MCP Server as a set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP Server: &lt;a href="https://github.com/tanahiro2010/webmcp-bridge-mcp" rel="noopener noreferrer"&gt;https://github.com/tanahiro2010/webmcp-bridge-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Chrome Extension: &lt;a href="https://github.com/tanahiro2010/webmcp-bridge-extension" rel="noopener noreferrer"&gt;https://github.com/tanahiro2010/webmcp-bridge-extension&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's one thing I was particular about here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only detect and execute the APIs defined by the WebMCP spec (&lt;code&gt;document.modelContext&lt;/code&gt; / annotated &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;s) as-is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't want to override the spec with some custom protocol of my own.&lt;br&gt;
The reason is simple: if what people learn in the spec and what actually runs end up diverging, there's no point in running the hands-on in the first place.&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview of the Bridge
&lt;/h2&gt;

&lt;p&gt;Here's the overall structure:&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
    Agent["AI Agent&amp;lt;br&amp;gt;(e.g. Antigravity CLI)"]
    MCP["webmcp-bridge-mcp&amp;lt;br&amp;gt;(MCP Server)"]
    Ext["webmcp-bridge-extension&amp;lt;br&amp;gt;(Chrome Extension)"]
    Page["Web page&amp;lt;br&amp;gt;(WebMCP-enabled)"]
    Agent &amp;lt;--&amp;gt;|stdio, MCP| MCP
    MCP &amp;lt;--&amp;gt;|WebSocket| Ext
    Ext &amp;lt;--&amp;gt;|content/injected script| Page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP Server (&lt;code&gt;webmcp-bridge-mcp&lt;/code&gt;) never touches the DOM directly itself.&lt;br&gt;
It sticks strictly to being a Bridge / Registry / Router between itself and the Extension, and leaves all DOM manipulation to the Extension side.&lt;br&gt;
It's a fairly unglamorous division of labor, but the responsibilities are clear, and I had few doubts while implementing it.&lt;/p&gt;

&lt;p&gt;The WebSocket binds to &lt;code&gt;ws://127.0.0.1:58787&lt;/code&gt;.&lt;br&gt;
I used 58787 instead of 8787 because 8787 collided with the default port for &lt;code&gt;wrangler dev&lt;/code&gt; (Cloudflare Workers).&lt;br&gt;
When I had Workers development running in parallel, the Extension would end up connecting to wrangler instead of my server, and I'd sit there wondering why nothing would connect — a fairly unglamorous bug I ran into early on.&lt;/p&gt;

&lt;p&gt;The Extension side is MV3-based and has a two-layer structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
    subgraph Page["Inside the web page"]
        direction TB
        Injected["injected.ts&amp;lt;br&amp;gt;(main world)"]
        Content["content.ts&amp;lt;br&amp;gt;(isolated world)"]
        Injected &amp;lt;--&amp;gt;|postMessage| Content
    end
    Background["background.ts&amp;lt;br&amp;gt;(Service Worker)"]
    WS["MCP Server"]
    Content &amp;lt;--&amp;gt;|chrome.runtime| Background
    Background &amp;lt;--&amp;gt;|WebSocket| WS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason &lt;code&gt;injected.ts&lt;/code&gt; needs to run in the main world is that accessing the page's &lt;code&gt;document.modelContext&lt;/code&gt; requires running in the main world.&lt;br&gt;
You can't touch it directly from the isolated world (a normal content script).&lt;br&gt;
When &lt;code&gt;document.modelContext&lt;/code&gt; isn't yet natively implemented in the browser, &lt;code&gt;injected.ts&lt;/code&gt; provides a minimal polyfill for &lt;code&gt;registerTool&lt;/code&gt; / &lt;code&gt;getTools&lt;/code&gt; / &lt;code&gt;executeTool&lt;/code&gt; / the &lt;code&gt;toolchange&lt;/code&gt; event.&lt;br&gt;
If a native implementation exists, it does nothing.&lt;br&gt;
By designing it as "quietly defer if a native implementation exists," I expect I won't need major code changes even as native implementations roll out.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tools visible to the Agent
&lt;/h2&gt;

&lt;p&gt;There are six tools visible to the Agent:&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_get_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the Extension's connection state, number of known tabs, and the active tab ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_list_tabs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the list of WebMCP-enabled tabs the Extension has captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_discover_tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discovers the WebMCP tools on a given tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_call_tool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes a tool on a given tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_submit_tool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Confirms a submission that's waiting on a human, from the Agent side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webmcp_ping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks connectivity with the Extension&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's what the input/output look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// webmcp_discover_tools input&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;span class="nl"&gt;"tabId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"forceRefresh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;span class="c1"&gt;// output&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;span class="nl"&gt;"tabId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&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;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;"reserve_hotel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reserve_hotel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"imperative"&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;span class="p"&gt;]&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// webmcp_call_tool input&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;span class="nl"&gt;"toolId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reserve_hotel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Osaka"&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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// output&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;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Osaka"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confirmationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RES-12345"&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;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;&lt;code&gt;webmcp_submit_tool&lt;/code&gt; is a bit unusual.&lt;br&gt;
Per the spec, a declarative form without &lt;code&gt;toolautosubmit&lt;/code&gt; is expected to stop by focusing the submit button, so that a human reviews the content and submits manually.&lt;br&gt;
This is a safety mechanism intentionally built into the WebMCP spec.&lt;br&gt;
&lt;code&gt;webmcp_submit_tool&lt;/code&gt; is a tool for explicitly overriding that from the Agent side.&lt;br&gt;
I've written a note in the README that this &lt;strong&gt;should only be used with the understanding that it bypasses the human confirmation the spec intends&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on real hardware, it was riddled with landmines
&lt;/h2&gt;

&lt;p&gt;This is the part I most wanted to write about in this article.&lt;br&gt;
Things I never would have noticed just from reading the docs kept popping up once I actually ran it on Chrome for Testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;getTools()&lt;/code&gt; turned out to be async
&lt;/h3&gt;

&lt;p&gt;From skimming the summary in Chrome's developer docs, my impression was that it was a synchronous function.&lt;br&gt;
But when I checked on real hardware (Chrome for Testing 150), &lt;code&gt;document.modelContext.getTools()&lt;/code&gt; returned a &lt;code&gt;Promise&amp;lt;ModelContextTool[]&amp;gt;&lt;/code&gt;.&lt;br&gt;
On top of that, &lt;code&gt;executeTool()&lt;/code&gt; doesn't take a tool-name string — it requires the actual tool object obtained from &lt;code&gt;getTools()&lt;/code&gt;.&lt;br&gt;
Pass it a string, and you get a &lt;code&gt;TypeError&lt;/code&gt;.&lt;br&gt;
This was a spot where implementing based only on a summary of the docs would trip you up, plain and simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  On &lt;code&gt;file://&lt;/code&gt;, the handshake never finishes, for some reason
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;postMessage&lt;/code&gt; between &lt;code&gt;content.ts&lt;/code&gt; and &lt;code&gt;injected.ts&lt;/code&gt;, I was using &lt;code&gt;window.location.origin&lt;/code&gt; as &lt;code&gt;targetOrigin&lt;/code&gt;.&lt;br&gt;
On a page opened via &lt;code&gt;file://&lt;/code&gt;, this becomes, for some reason, the literal string &lt;code&gt;"null"&lt;/code&gt;.&lt;br&gt;
As a result, the handshake never completed at all, and the overlay would never show up.&lt;br&gt;
I'm not sure whether this bug is fully reproducible, but thinking about it more, communication between the main world and isolated world within the same window shouldn't involve the concept of cross-origin at all in the first place.&lt;br&gt;
So I changed &lt;code&gt;targetOrigin&lt;/code&gt; to &lt;code&gt;"*"&lt;/code&gt;, and instead guarantee legitimacy via a random channel ID.&lt;br&gt;
This was the type of bug you only notice by trying it with &lt;code&gt;file://&lt;/code&gt;; if I hadn't verified the sample page by opening it directly via &lt;code&gt;file://&lt;/code&gt;, I probably would have missed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The native implementation registers declarative forms on its own
&lt;/h3&gt;

&lt;p&gt;When a native implementation is present, the browser itself may automatically register declarative forms.&lt;br&gt;
In that case, this Extension's own &lt;code&gt;registerTool()&lt;/code&gt; call fails as a "duplicate," but I'm treating that as expected behavior.&lt;br&gt;
Since &lt;code&gt;findAnnotatedFormByName()&lt;/code&gt; looks directly at the DOM to determine &lt;code&gt;source: "declarative"&lt;/code&gt;, it can report correctly regardless of who registered it.&lt;br&gt;
There were also cases where the natively-synthesized &lt;code&gt;inputSchema&lt;/code&gt; returned empty (&lt;code&gt;{ type: "object", properties: {} }&lt;/code&gt;) on this particular build.&lt;br&gt;
This is probably down to the state of the browser's implementation, not a bug in the Extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool execution results come back as strings, for some reason
&lt;/h3&gt;

&lt;p&gt;The WebMCP spec's &lt;code&gt;execute&lt;/code&gt; is originally meant to return "a string summary for the agent."&lt;br&gt;
Because of that, I confirmed on real hardware that even when the page returns an object like &lt;code&gt;{ ok: true, city }&lt;/code&gt;, the browser's native implementation JSON-stringifies it before returning.&lt;br&gt;
Neither the Extension nor the MCP Server touch &lt;code&gt;result&lt;/code&gt; — they pass it straight through.&lt;br&gt;
So the MCP client side needs to determine whether it got a string or structured data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Service Worker falls asleep on its own
&lt;/h3&gt;

&lt;p&gt;An MV3 Service Worker gets suspended when it goes idle.&lt;br&gt;
During that time, the WebSocket connection also drops.&lt;br&gt;
It wakes back up automatically when a &lt;code&gt;chrome.tabs&lt;/code&gt; event or similar fires, and the reconnection logic kicks in.&lt;br&gt;
But if nothing happens right after startup — no tab activity at all — it can stay stuck at &lt;code&gt;extensionConnected: false&lt;/code&gt; for a while.&lt;br&gt;
If &lt;code&gt;webmcp_get_status&lt;/code&gt; / &lt;code&gt;webmcp_ping&lt;/code&gt; return &lt;code&gt;false&lt;/code&gt;, doing something with the target tab (switching to it, reloading it, etc.) will bring it back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool registration finishes before the handshake, so the overlay never shows up
&lt;/h3&gt;

&lt;p&gt;On declarative-only pages (with no imperative JS at all), there was a race condition where tool registration could complete before the handshake with &lt;code&gt;content.ts&lt;/code&gt; finished, leaving the overlay permanently hidden.&lt;br&gt;
&lt;code&gt;injected.ts&lt;/code&gt; registers &lt;code&gt;&amp;lt;form toolname tooldescription&amp;gt;&lt;/code&gt; elements as soon as it finds them via &lt;code&gt;MutationObserver&lt;/code&gt;.&lt;br&gt;
So this race is more likely to occur on lightweight declarative-only pages that don't run any imperative script.&lt;br&gt;
The previous implementation recorded "the manifest was sent / attempted to be sent" before the handshake had actually completed.&lt;br&gt;
As a result, once the handshake did complete, a resend request would be misjudged as "no diff" and silently swallowed.&lt;br&gt;
I fixed &lt;code&gt;reportManifestIfChanged()&lt;/code&gt; so that it doesn't record or send anything at all until the channel is established.&lt;br&gt;
I wrote a test that deliberately reproduces the bad ordering (tool registration → delayed handshake), and confirmed it reproduces the bug before the fix and resolves it after.&lt;br&gt;
More than the bug itself, what I felt made the biggest difference was the approach of "write a test that deliberately reproduces the bad ordering, and use it to prove the fix works."&lt;/p&gt;

&lt;h3&gt;
  
  
  A tool that never comes back until a human clicks
&lt;/h3&gt;

&lt;p&gt;Per the spec, a form without &lt;code&gt;toolautosubmit&lt;/code&gt; stops by just focusing the submit button.&lt;br&gt;
This is meant so a human reviews the content and submits manually.&lt;br&gt;
At least some native implementations keep &lt;code&gt;executeTool()&lt;/code&gt; itself blocked during this "waiting for a human" period.&lt;br&gt;
This is not the same behavior as this Extension's polyfill, which immediately returns &lt;code&gt;{ pending: true, ... }&lt;/code&gt;.&lt;br&gt;
If you call &lt;code&gt;webmcp_call_tool&lt;/code&gt; on a tool without &lt;code&gt;toolautosubmit&lt;/code&gt; in an automated environment with no human interaction, you won't get a response back until it times out.&lt;br&gt;
Before calling it, you need to check whether &lt;code&gt;webmcp_discover_tools&lt;/code&gt;'s result has &lt;code&gt;requiresUserGesture: true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: two tiers, with and without a browser
&lt;/h2&gt;

&lt;p&gt;I verified the following four scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An imperative-only page (no &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A declarative-only page (no &lt;code&gt;registerTool()&lt;/code&gt; calls)&lt;/li&gt;
&lt;li&gt;A page with a normal mix of imperative and declarative&lt;/li&gt;
&lt;li&gt;A pattern where executing one tool changes whether another tool exists (calling &lt;code&gt;unlock&lt;/code&gt; makes a new tool dynamically appear, and &lt;code&gt;lock&lt;/code&gt; makes it disappear)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I actually loaded the Extension on Chrome for Testing and confirmed all patterns passed.&lt;br&gt;
The fourth one also serves as verification of dynamic detection via the &lt;code&gt;toolchange&lt;/code&gt; event and &lt;code&gt;MutationObserver&lt;/code&gt;.&lt;br&gt;
Since &lt;code&gt;webmcp_discover_tools&lt;/code&gt; returns a cached result by default, you need &lt;code&gt;forceRefresh: true&lt;/code&gt; to observe these dynamic changes.&lt;/p&gt;

&lt;p&gt;I'm using Chrome for Testing (the Chromium bundled with Playwright) because, from Chrome 137 onward, official Google Chrome builds have removed the &lt;code&gt;--load-extension&lt;/code&gt; flag for automation purposes.&lt;br&gt;
Loading an extension manually via &lt;code&gt;chrome://extensions&lt;/code&gt; works fine with regular Chrome.&lt;br&gt;
But if you want to load an extension in automated tests, you need Chrome for Testing or Chromium.&lt;/p&gt;

&lt;p&gt;On the MCP Server side, I use a mock class called &lt;code&gt;FakeExtension&lt;/code&gt;.&lt;br&gt;
It speaks the same WebSocket protocol as the real Extension, and lets me verify — without launching a browser — the connection state, the discover_tools cache/&lt;code&gt;forceRefresh&lt;/code&gt; behavior, concurrent tool call execution, and cleanup on disconnect.&lt;br&gt;
Since it doesn't launch a browser, it runs fast, so I split the work: bridge logic gets tested here, and verification that involves actual DOM manipulation goes through the Extension's Playwright tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on security (this is a prototype)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;code&gt;postMessage&lt;/code&gt; between the main world (&lt;code&gt;injected.ts&lt;/code&gt;) and the isolated world (&lt;code&gt;content.ts&lt;/code&gt;), a random channel ID is issued once per page load, shared through a one-time handshake, and attached to every subsequent message. This is to prevent unrelated page scripts from injecting a fake manifest or fake execution results.&lt;/li&gt;
&lt;li&gt;The MCP Server's WebSocket server binds only to &lt;code&gt;127.0.0.1&lt;/code&gt;, so it can't be connected to from anywhere other than the same machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is built with personal use and prototyping in mind, and doesn't include additional authorization such as token authentication.&lt;br&gt;
I stopped at the line of "good enough to run at the hands-on session" and haven't designed authorization with production use in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even so, there are real benefits
&lt;/h2&gt;

&lt;p&gt;Even though I put this together somewhat forcefully, there are benefits I genuinely felt after actually using it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less guessing&lt;/strong&gt;: the Agent doesn't have to infer the meaning of buttons or input fields on screen. The site itself can declare its intent as a tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shorter execution&lt;/strong&gt;: no need to replay a bunch of clicks and inputs — it can be handled directly as a tool call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visually verifiable&lt;/strong&gt;: since it runs in the browser, a human can watch the result happen in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  But personally, I don't think it's necessary
&lt;/h2&gt;

&lt;p&gt;From here on, this is entirely my personal opinion.&lt;br&gt;
Honestly, having implemented it and used it at the hands-on session, what I felt most strongly was, "do we really need this?"&lt;/p&gt;

&lt;p&gt;There are a few reasons.&lt;/p&gt;

&lt;p&gt;First, the motivation to use a browser-embedded Agent is still weak.&lt;br&gt;
Actually, I myself didn't even know Chrome had a built-in Agent until I did this investigation.&lt;br&gt;
I imagine there are quite a few people in the same boat.&lt;/p&gt;

&lt;p&gt;And WebMCP has the constraint that it only works while the page is open.&lt;br&gt;
That's subtly restrictive.&lt;br&gt;
Having a tool disappear the moment you switch tabs felt a bit inconvenient, coming from the mindset I'm used to with MCP.&lt;/p&gt;

&lt;p&gt;In actual use, I didn't feel a particularly large difference in perceived token usage or execution speed between Agent-driven browser operations and WebMCP-driven ones.&lt;br&gt;
For people who keep an existing Agent like Claude Code or Codex open at all times, I felt the appeal of going out of your way to open a browser-embedded Agent just to use this is pretty thin.&lt;/p&gt;

&lt;p&gt;And honestly, from a layperson's perspective, I also wondered if the target audience selection itself might be a bit off.&lt;br&gt;
The benefit itself — "fewer mistaken operations from the Agent" — is appealing.&lt;br&gt;
But it's a bit of a shame that the place where you can realize that benefit is limited to something as little-known as a "browser-embedded Agent."&lt;/p&gt;

&lt;h2&gt;
  
  
  That said, it will resonate with the right people
&lt;/h2&gt;

&lt;p&gt;That said, for people who use a browser-embedded Agent as part of their daily routine, the two points — "less guessing" and "shorter execution time" — should genuinely land hard.&lt;br&gt;
I think the reason it didn't land as hard for me this time is simply that my own workflow wasn't built around a browser-embedded Agent to begin with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I published this article anyway
&lt;/h2&gt;

&lt;p&gt;Given how harsh some of this has been, you might wonder why I'm publishing it at all.&lt;br&gt;
The reason is simple.&lt;br&gt;
It felt like a waste to build an Extension and an MCP Server and have them only get used on the day of the hands-on session and nothing more.&lt;br&gt;
Even though I have some skepticism about WebMCP as a spec, the technical insights I only gained by actually implementing it and getting my hands dirty — the async API, the origin issue, how I fixed the race condition, and so on — should be useful to someone on their own merits.&lt;br&gt;
Also, if people end up using the extension or the MCP server, that makes me happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;WebMCP is, directionally, an interesting spec: "the web page itself declares tools for an Agent."&lt;br&gt;
That said, as of August 2026 it's still at the draft stage, and its target is limited to browser-embedded Agents.&lt;br&gt;
So my personal conclusion is that, at this point, it doesn't land all that strongly for people who are already heavy users of existing MCP-capable Agents.&lt;br&gt;
On the other hand, the pitfalls I found while implementing it — the async API, the origin issue, the race condition — should be useful reference material as native implementations spread further going forward.&lt;/p&gt;

&lt;p&gt;Here's what I built and the related links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Codelab: &lt;a href="https://learn.gdgs.jp/webmcp-agent/" rel="noopener noreferrer"&gt;https://learn.gdgs.jp/webmcp-agent/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WebMCP Bridge MCP: &lt;a href="https://github.com/tanahiro2010/webmcp-bridge-mcp" rel="noopener noreferrer"&gt;https://github.com/tanahiro2010/webmcp-bridge-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WebMCP Bridge Extension: &lt;a href="https://github.com/tanahiro2010/webmcp-bridge-extension" rel="noopener noreferrer"&gt;https://github.com/tanahiro2010/webmcp-bridge-extension&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;connpass: &lt;a href="https://gdgkwansai.connpass.com/event/391029" rel="noopener noreferrer"&gt;https://gdgkwansai.connpass.com/event/391029&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original Article: &lt;a href="https://qiita.com/tanahiro2010/items/66b7822e2132ec4e0a45" rel="noopener noreferrer"&gt;https://qiita.com/tanahiro2010/items/66b7822e2132ec4e0a45&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to check out the repos and reach out with any questions.&lt;/p&gt;

</description>
      <category>webmcp</category>
      <category>bridge</category>
      <category>extensions</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
