How a GraphQL Formatter Avoids Mistaking # in a String for a Comment
In GraphQL syntax, # marks the rest of the line as a comment. That rule is only supposed to apply in code regions — but if a formatter or minifier is built around a single sloppy regular expression, it ends up treating a # sitting inside a string literal exactly the same way, as the start of a comment. This guide walks through why that happens, and confirms at the code level how the GraphQL Formatter actually avoids it.
1. Where the problem starts: a regex doesn't know context
The simplest possible GraphQL comment stripper can be written in one line: src.replace(/#[^\n]*/g, ''). That regex deletes everything after a # on a line. The trouble starts when a user-supplied string value inside the query happens to contain a #, like filter: "product #1". The regex has no context to tell whether that # is inside a string or out in code, so it deletes everything from #1" onward — including the rest of the actual query syntax that follows. This is the exact same class of bug as a SQL minifier that strips -- without context and clips a string containing --, wiping out everything that comes after it.
2. What input actually triggers it
Consider a query like this one.
{ product(bio: "Loves #hashtags, {coffee}") { name price } }A formatter that ignores context keeps only
"Loves and treats #hashtags, {coffee}") { name price } } as one big comment, deleting it entirely. What's left is a truncated query with an unclosed parenthesis, and sending that to a server makes the GraphQL parser throw a Syntax Error.
In practice this isn't an edge case — product names commonly include hashtags, and search filter values commonly include ranking or numbering written as #1, so this bug is something you're guaranteed to hit sooner or later.
3. The safe processing order: isolate strings first
The fix is to reverse the order of operations. Before stripping any comments, first locate every string literal and swap it out for a temporary placeholder — "protecting" it — then run comment stripping, whitespace collapsing, and punctuation cleanup on what's left, and only at the end restore the placeholders back to their original string values. Done this way, there's never any ambiguity about whether a given # was inside a string or out in code region, because strings are excluded from processing in the first place.
4. Checking the actual tool code
Opening graphql-formatter.html behind the GraphQL Formatter shows this exact order implemented. The extractGqlStrings() function first finds every """...""" (block string) and "..." (regular string, including escaped characters) using the regex /"""[\s\S]*?"""|"(?:\\.|[^"\\])*"/g, stores them in an array, and drops in an index wrapped in null characters (in the form \u00000\u0000) in their place. After that, gqlFormat() and gqlMinify() only run comment stripping (#[^\n]*) and whitespace cleanup against this masked text, and once that's done, restoreGqlStrings() swaps the placeholders back for the real strings. Because the comment-stripping logic literally cannot see inside string contents, it has no way to misfire regardless of whether a # was inside a string or in code.
| Step | What happens |
|---|---|
| 1. Extract strings | Find every "..."/"""...""", store them, and swap them for placeholders |
| 2. Strip comments | Delete everything after # to end of line in the masked text (strings are already out of the way) |
| 3. Clean up whitespace/punctuation | Rebuild indentation, or minify |
| 4. Restore strings | Swap placeholders back for their original string values |
5. Verify it yourself
Paste the dangerous example above (bio: "Loves #hashtags, {coffee}") directly into the GraphQL Formatter and run format or minify — the string value comes through intact, and the following { name price } syntax also outputs correctly. Conversely, if you edit the tool's code to skip the extractGqlStrings call and apply replace(/#[^\n]*/g,'') directly instead (for testing purposes only), the same input immediately gets its syntax truncated — confirming that the string-protection logic is actually what determines the outcome.
Frequently Asked Questions
Q. Is it a problem if a string contains commas or braces?
No. Since the whole string literal is swapped for a placeholder before structural parsing happens, commas, braces, or colons inside the string can never be mistaken for GraphQL syntax structure.
Q. Are block strings (""") protected the same way?
Yes. The regex is written so that the """...""" pattern is matched before "...", so multi-line description strings are protected as a whole too.
Q. Does it correctly handle strings with escaped quotes (\")?
Yes. The (?:\\.|[^"\\])* part of the regex is written to consume backslash-escaped characters as part of the string, so a \" in the middle of a string doesn't cause the string to end early.
Q. Is this problem unique to GraphQL?
No. Any language where a comment marker can accidentally appear inside a string carries the same risk. SQL's --, shell scripts' #, and others can break the same way if comments are stripped with a context-blind regex.