It's an ambiguous situation. If you look at the low-level implementation, STORED is not parsed as a keyword. In other words, the "`sqlite3KeywordCode()`" routine will say that "STORED" is not a keyword. The SQL language grammar rules used for generated columns in SQLite looks like this: ~~~~~ ccons ::= GENERATED ALWAYS AS generated. ccons ::= AS generated. generated ::= LP expr(E) RP. {sqlite3AddGenerated(pParse,E,0);} generated ::= LP expr(E) RP ID(TYPE). {sqlite3AddGenerated(pParse,E,&TYPE);} ~~~~~ The optional "VIRTUAL" or "STORED" that occurs after the generated column definition is parsed as a normal identifier and is passed into the "`sqlite3AddGenerated()`" routine. Then inside of sqlite3AddGenerated() (on [lines 1674 to 1682][1]) there is logic to error-out if the identifier is anything other than "VIRTUAL" or "STORED". So, from the point of view of SQLite itself, "STORED" is not a keyword. On the other hand, from the user's perspective, "STORED" does act like a keyword, even if it is not. So maybe the documentation should be changed to call it a keyword even though it is not. I'm not sure... [1]: https://www.sqlite.org/src/info/912d6ec338311?ln=1674-1682