Should I choose v1 or v2?

Install both.

The launcher included with v2 enables the use of v1 and v2 scripts on one system, almost transparently. If a script’s version requirement isn’t explicitly declared with #Requires, the launcher can usually detect the required version based on syntax.

If you install v2 and try to run a v1 script without first installing v1, in many cases the launcher will offer you to automatically install the required version. So if you don’t specifically intend to run v1 scripts, just install v2.

SciTE4AutoHotkey supports both versions out of the box, while VS Code can support both with the installation of appropriate extensions. See Tools for details.


Both versions make it easy to carry out a range of tasks with just a few lines of code. Where they differ most is the syntax - the rules and patterns that you must learn in order to write code that works.

For historical reasons, v1 has two kinds of syntax: legacy syntax and expressions. The legacy syntax gives a false impression of simplicity which sometimes appeals to non-programmers, but actually results in greater complexity and is the root cause of frequent confusion.

v2 uses expressions throughout, and several common causes of confusion have been removed. If you are a beginner and only need to carry out simple tasks, it is still sufficient to learn just a few simple patterns. When you need more, the learning curve is gentler as there is no need to shift to a different syntax.

v2 has more thorough error-detection and reporting. Sometimes this means writing extra code to avoid doing something the program considers invalid, but in return, troubleshooting is much easier and the code you write is more reliable. Stricter error-detection also tends to limit the damage that mistakes can cause.

It is unfortunately true that there are fewer resources available for learning v2 than for v1, but that will change over time. v2 itself has simpler syntax and is easier to learn, so you may find that the relative lack of resources isn’t a problem, if you give it a try.

Even if you aren’t a beginner, you may - I dare say you will - benefit from simplification of the syntax and the removal of many common causes of confusion. v2 also includes hundreds of individual improvements, representing over a decade of work.

v1 development will cease; see End of v1. Perhaps you’ve invested a lot of time learning v1 and building your collection of scripts, but sticking with v1 means investing more time in a product that is going nowhere. Someone else could pick up development of v1, but unless that someone is you, don’t count on it.

Note that switching doesn’t mean immediately letting go of v1, or having to convert all of your scripts. You can use both.

It has been said that v2 has a completely different language; that v1 users switching to v2 will need to learn a lot of new syntax or concepts, giving up their existing language skills. This is at best oversimplified and exaggerated.

v2 builds upon the same basic concepts as v1, but centers its syntax around expressions. If you have learned expressions for v1, you are most of the way toward understanding v2 code. The major differences are removal of obsolete syntax and concepts, and more robust error-detection and reporting. New syntax and concepts generally aren’t mandatory learning; and they are there to help you.

As a v1 user, you’ve probably memorized many details, such as the names of commands, the syntax of each parameter and various special rules within the language. It is true that many small details have changed and will require some relearning, but there are also details and pitfalls that you no longer need to account for while writing code.

In the process of converting code, you may find opportunities for improvement due to new language features or behaviour, or perhaps because your skills have grown since writing the original code. Improvements to error-detection may allow you to find bugs that were previously undetected - I’ll admit to finding such errors while updating my own code.

But maybe you shouldn’t. If converting v1 code doesn’t seem like the best use of your time, or doesn’t make good business sense, you’re free to continue running old scripts with v1, while writing new code in v2 if you so choose. The installer and support scripts for v2 are designed to make this easy.

My recommendation is to start small; write new scripts in v2 and learn a bit before attempting to convert existing scripts.

There are plans for more tools to assist with analysis and conversion of scripts, but they have not been implemented yet as the priority has been on getting v2 to a stable release. Some user-created converters exist, although with significant limitations.

If converting isn’t yet a practical option, you might consider loading v1 libraries with AutoHotkey.dll and calling them from v2. import_v1lib and v1_func/v1_class_lib demonstrate this. There are also plans for a DLL based on the main AutoHotkey branch, with a cleaner interface to facilitate this use case.


This is written by Steve Gray (Lexikos), the same person who made all of the decisions (but not all of the ideas) for v2 and implemented most of them. v2 represents many hours of hard work and internal debate over more than 10 years, so the answers here may be somewhat biased. Nearly every change (honestly not all) was made with the conviction that it objectively makes AutoHotkey better, so naturally my point of view is that v2 is vastly superior to v1.


To understand the purpose of v2, it may help to know some of the history of AutoHotkey.

AutoHotkey v1.0.00 was released in 2004. This version had only (what we now call) legacy syntax, and almost complete backward compatibility with AutoIt v2. AutoIt v2 users could easily switch to AutoHotkey and immediately take advantage of enhanced syntax and features. It could be said that AutoHotkey owes a large part of its popularity to backward-compatibility.

For math, it supported only basic operations (add, sub, mult, div), and only one per line. Other math functions were soon added via the Transform command, which is even more verbose.

By 2005, the capabilities of the program were vastly improved with the addition of expressions. However, the emphasis on backward-compatibility meant that all of the pre-existing commands continued to prioritize the legacy syntax, resulting in duality that even now causes confusion for new (and sometimes even experienced) users.

In 2006 (or perhaps earlier), the original developer (Chris Mallett) started gathering ideas for v2, later published as AutoHotkey v2. While seemingly acknowledging the issues with v1, Chris continued to develop it for a few years until losing interest in 2009, without having started work on v2.

I (Lexikos) started developing v2 in 2011, using the draft document left by Chris as a guide for the initial alpha releases, but incorporating my own ideas and suggestions from others. With the scope of the project being rather vague and generally only myself working on it (when it interests me), there was never anything like a release schedule. Ideas changed over time and the syntax went through multiple evolutions.

Eventually I decided there had to be a cutoff for compatibility-breaking changes or there would never be a stable release, and soon after that I released v2.0-beta.1 (in July 2021).


“Stability” can mean a couple of things: 1. How likely it is that a user might encounter bugs. 2. Whether the syntax or features are going to change in ways that prevent previously-written scripts from working as intended.

A stable release can be taken as a promise that scripts you write now will work on future versions, or at the very least, any change that prevents this is accompanied by an increment to the major version number. In other words, all v2.0.x and v2.x releases should be able to run scripts written for earlier v2.0 releases (starting with v2.0-beta.1). Compatibility-breaking changes must be deferred until v3.0.

AutoHotkey v2 alpha releases weren’t expected to be compatible with previous releases, by definition. Changes that were backward-compatible could be (and sometimes were) implemented in the v1.x branch. However, the shift from alpha to beta (in July 2021) signified a major change in priorities, from making breaking changes (alpha) to avoiding them (beta):

Any further changes prior to the final v2.0.0 release should be of a minor nature (such as improvements to the error and warning dialogs), and should not affect the functionality of scripts. In other words, future releases are expected to be backward-compatible with this one.

Some people are scared off by the “beta” tag or make false assumptions about its meaning, but that no longer matters, as v2.0.0 has been released.

v1 uses older code that hasn’t undergone nearly as much change, and has been more widely used over a longer period, so it is only natural that any remaining bugs are of a kind that you aren’t very likely to encounter. However, this is only considering the unintentional bugs, not the many issues that are by design, and can only be fixed by a major update such as v2.

In some cases, old bugs have been fixed while reworking code to have new behaviour for v2; or changes to the way the code works have enabled bug-fixes that weren’t feasible before.

The release policy for v1.x was basically this: bump the fourth version number component for releases that only contain bug-fixes, and the third component if there are new features or other changes. Sometimes new releases with features were available as a test build before-hand, but these generally didn’t get enough testing. For instance, v1.1.34.00 contained major bugs relating to hotkeys despite a test build having been available for almost an entire year before the release.

v2 will be following a more robust release cycle, with a stable branch accepting only bug fixes, and an alpha branch with new features. The current feature set of v2.0 is basically how it will be for its lifetime, with new features coming in v2.1, v2.2 and so on. v2.1-alpha will have enough in it to attract quite a bit of use, and will go through development and testing for much longer than a typical v1.x release (but much shorter than v2.0!).


boiler says: Regarding ease of use, v2 removes the primary source of confusion that has plagued new users more than any other: the dual syntax. Sometimes “command” or “legacy” syntax is used, and sometimes expression syntax is used. It can be very frustrating learning when and how to use both. That is what made v1 confusing for even experienced programmers coming from other languages. With v2, expression syntax is used throughout.

There seems to be a common impression among those who have already learned v1 that v1 might be easier to learn for beginners, or that it is easier or has simpler syntax than v2.

v1 literally and objectively has more complex syntax than v2. I say this for two reasons: 1. Both versions have expressions, but v1 also has legacy syntax that is unlike expressions, less flexible and yet cannot be completely avoided. Because v1 has both but prioritizes the legacy syntax, there are even more rules defining how it selects between the two. 2. v1 has a bunch of special cases (exceptions to rules) within the syntax. This is usually due to prioritizing backward-compatibility, but sometimes because it tries to be helpful, making things work but at the same time introducing inconsistencies and complexity.

For example, the = symbol in v1 (a is a variable in all cases, but the meaning of b varies):

a = b           ; Assignment - legacy syntax, 'b' is literal
a = b + 1       ; Common mistake - literally assigns the text "b + 1"
local a = b     ; Assignment - expression, b is a variable!
if a = b        ; Comparison - legacy syntax, 'b' is literal
if (a = b)      ; Comparison - expression, b is a variable
if !a = b       ; Comparison - expression, b is a variable
func(a = 1) {   ; Declares a default value for parameter 'a' (it's like an assignment)
a := b, c = d   ; All four are variables, and '=' and ':=' are both assignment in this context
if (a == b)     ; Case-sensitive comparison - expression, both are variables
if a == b       ; Case-insensitive comparison - the value is literally "= b" (common pitfall)

Although = is used for both comparison and assignment, the bigger pitfall is that sometimes the right-hand side is an expression and sometimes it is text.

This is just a small set of examples, and by no means complete.

v2 greatly simplifies the syntax, using expressions everywhere except within hotstrings and some directives.

So, what gives v1 an impression of simplicity?

ANY programming language, no matter how large and complicated, can be regarded as easy if you start with just the basics of the language and focus on a small subset of it.

The legacy syntax allows a command to be written with fewer “meta-characters”; often just the command and literally whatever textual parameter the command requires. For example:

ifWinNotExist ahk_class Notepad
    Run Notepad
    WinWait ahk_class Notepad
Send {Text}Hello, world!

Specifically, compared to expressions, it avoids the trivial need for quote marks around textual values.

However, the need for quote marks should be intuitive to anyone who is reading this (unless you’re relying on machine translation, I guess), since it’s basically the same as in written English. For instance, an English translation of the last command above could be Send "Hello, world!" to the active window.

Because variable names would be interpreted as literal text by default, including the value of a variable requires marking it with extra symbols. So instead of surrounding the literal text with quote marks, you must surround the variables with % percent signs. For example, perhaps we want to modify the code above to wait specifically for a window belonging to the new Notepad process:

Run Notepad,,, PID
WinWait ahk_pid %PID%

Since Run’s fourth parameter expects an output variable (or in other words, the name of a variable), no percent signs are used for it.

Although most characters are literal by default, another notable exception is that comma is used as a delimiter between parameters. When one needs a literal percent sign or comma, an escape character must be used to mark it as literal; e.g. `, or `%. There are cases where escaping a comma isn’t necessary, but relying on that means more rules to remember - more complexity.

The example above uses ifWinNotExist, which is one of several legacy if-statements that check only a single condition. It is often preferred to use the newer syntax even in v1, such as to add a second condition. For example:

if not (WinExist("ahk_class Notepad") or WinExist("ahk_class WordPadClass"))
    Run Notepad,,, PID
    WinWait ahk_pid %PID%
Send {Text}Hello, world!

So now we have a mixture of legacy syntax and expressions. Rather than trying to keep track of where to quote literal text and where to enclose variables in percent signs (or perhaps just for uniformity or style), some users prefer to use expressions throughout.

Since v1 commands use legacy syntax by default (although some also accept a subset of expressions by default), the language requires us to mark the parameter in some way if we want it to be an expression. The marker chosen for v1 is the percent-space prefix:

if not (WinExist("ahk_class Notepad") or WinExist("ahk_class WordPadClass"))
    Run % "Notepad",,, PID
    WinWait % "ahk_pid " PID
Send % "{Text}Hello, world!"

So now we have more consistency - literal text enclosed in quote marks, and variables as plain words - but we need a percent sign in front of each parameter of a command (and not with functions).

Numeric parameters generally allow the percent sign to be omitted, but not for every parameter that one might expect, so relying on it requires more memorization. There are also exceptions such as %var% and %var%00 not being interpreted as expressions.

By contrast, v2 strips away the legacy syntax, leaving only expressions. For example:

if not (WinExist("ahk_class Notepad") or WinExist("ahk_class WordPadClass"))
    Run "Notepad",,, &PID
    WinWait "ahk_pid " PID
Send "{Text}Hello, world!"

This isn’t very different; I just removed the percent signs and added a single ampersand. (Ampersand in v2 is the reference operator; it produces a reference to the variable. Making it explicit allows greater flexibility and indicates to the reader that the parameter may be assigned a value by the function.)

In short, the legacy syntax itself is not simpler than the equivalent subset of expressions. To the contrary, retaining the legacy syntax causes greater complexity and a number of pitfalls. There are more rules to learn, and they aren’t always clear.

It has been said that v1 has a lower learning curve at the beginning. So, what’s the beginning?

Others have said that AutoHotkey (v1) has a steep or inconsistent learning curve, or that v2 was easier to learn - or to teach.

A great thing about AutoHotkey is that it is easy to make something useful happen with a few lines of simple code. Most users probably start with basic things like defining a hotkey to open a program, activate a window, send some template text or similar. Often a useful hotkey can be created with a single line, simply writing a command name and some text that the command uses.

#n::Run Notepad

Presumably whoever wrote about the learning curve of v1 has experienced it themself. What kind of experience could cause them to think that the legacy syntax made it easier?

A new user might typically start with the simplest, most commonly useful commands, and work their way up. Since v1 commands default to legacy syntax, that’s what they learn first. When they find a reason to learn expressions, it’s probably to do something that can’t be done easily with legacy syntax.

Aside from the task itself, the user now needs to attach new syntax to concepts that were already learned with the legacy syntax, remember where to use each form, and how to insert an expression where an expression isn’t normally expected. This probably makes the legacy syntax seem comparatively simple.

Legacy syntax on its own is simple, but it is only one very small part of a larger whole. In the absence of legacy syntax, the replacement syntax is just as simple, but is also more consistent with the rest of the language as a whole. This should mean that starting simple and incremently building on that will be even easier.

A new user of v2 doesn’t need to learn all of the expression operators, or even what “expressions” are, to begin writing code. As with v1, they just need to learn some simple patterns, like how to pass some literal text to a function.

#n::Run "Notepad"

v2 changes the rules for names and scope to promote cleaner, more robust code, while also removing the need for repetitive global declarations in many cases. These changes greatly facilitate functional programming by allowing consistent syntax for calling and referencing both function objects and formally defined functions.

In v2, hotkeys and hotstrings that are associated with multiple lines of code (excluding auto-replace hotstrings) require braces to denote the start and end of an implicit function. Visually they look very similar to before, but have the following differences: - Just like formal function definitions, a hotkey does not prevent the execution of code that is written outside of and below it. This eliminates a common cause of confusion present in v1, and allows the v1 “auto-execute section” concept to be expanded to the entire script, as global code. - There is no risk of forgetting to terminate a hotkey and having it unintentionally overlap with the next hotkey or subroutine. - Hotkeys can now utilize local or static variables, for more robust code. - Each hotkey has a hidden parameter/variable ThisHotkey. Unlike A_ThisHotkey, it retains its value even if the hotkey is interrupted by the user pressing another hotkey. A_ThisHotkey can still be used (perhaps after a delay) to determine whether some other hotkey has executed.

v1 has various commands that have “sub-commands”; essentially multiple functions packed into one, where one of the parameters specifies which action is being taken or which value is being retrieved. There isn’t any apparent logic behind the division between sub-commands (such as ControlGet Choice) and commands (such as ControlGetText) other than frequency of use.

v2 splits most of these sub-commands into individual functions. This is arguably not particularly interesting or significant, but the result is greater consistency. It is possible to supplement or override the individual functions (with user-defined functions) while retaining similar usage. Parameters previously unused by specific v1 sub-commands are completely omitted from the corresponding v2 function, making them more intuitive and convenient to use.

The monolithic v1 Gui command is replaced with the v2 Gui class/object, generally considered to be a significant improvement, though not by all. Menu is similarly replaced. Note that it is not necessary to learn all of the ins and outs of “object oriented programming” just to create a Gui in v2, any more than it was necessary to learn all of the ins and outs of “procedural programming” in v1.

v2 supports new methods of line continuation, allowing expressions to span multiple lines more naturally, improving flexibility and readability. Continuation sections have also been improved with more intelligent handling of leading whitespace and embedded quote marks, and in other ways.

Various other changes have been made to the syntax in v2, with a general trend of improving consistency, flexibility or error-detection, or removing counter-intuitive behaviour. Although it may mean many small changes for v1 users to learn, they are to your benefit. See Changes from v1.1 to v2.0 for full details.


v1 has a tendency to tolerate or ignore errors. When something goes wrong, often it can be difficult to identify where the cause is. It may be that the script just doesn’t work, and the user is left guessing as to why. Other times, the script continuing execution after encountering an error might risk causing damage, such as by writing invalid data to files or typing into the wrong window.

By contrast, v2 detects a greater variety of conditions that are likely to indicate an error or mistake in the code, and reports them to the user or transfers control to an exception handler. (The exception-handling mechanism is not new, but is used more consistently in v2.) This greatly facilitates troubleshooting and identifying problems, saving time and perhaps reducing frustration.

For beginners especially, error messages can be disconcerting; but ignoring errors by default is not the solution. It is much better to get feedback about a problem than to have to search for it without any clues. Error messages are not the problem, but a step toward a solution.

Learning to code can be a frustrating endeavor because you are destined to encounter many red errors along the way. What makes a programmer successful isn’t avoiding errors—no programmer can avoid them. Great programmers understand that errors are part of the process, and they know how to find the solution to each while learning something new from them. (
A common anti-pattern for learners with the attitude that ‘errors = bad’ is to navigate away from the error as quickly as possible without reading it. They’ll pore over their code instead, trying to reverse-engineer what the error message is already telling them. (

Updating old code sometimes requires changing error-handling patterns or adding checks, which draws attention to the (usually minimal) cost of having stricter error-detection, rather than the benefits.

For instance, WinActivate now raises an error if the window does not exist. This could alert the user to a typo in the window title, or prevent the script from proceeding to send keystrokes to the wrong window (or whatever action comes next), perhaps averting disaster.


There are a great many resources available for learning AutoHotkey v1, such as tutorials, videos, blog posts and ready-made scripts. Even forum topics for past help requests are a valuable learning resource. All of this has been accumulating since AutoHotkey’s release over 18 years ago.

By comparison, there are much fewer resources available for learning v2. Although the first alpha was released in 2011, the syntax underwent multiple evolutions and lesser changes, so much of the code written prior to the release of v2.0-beta.1 in 2021 needs adjustment before it can be used.

Whether the relative lack of resources would be an issue for you depends on your learning style and how able you are to learn from the documentation. The documentation for both versions is comprehensive.

The only way to truly rectify the lack of resources is to increase the number of people generating resources for v2; writing code, tutorials and blog posts, making videos, and so on. Even just asking questions on the forums will help to generate content. If nothing else, more users means a larger audience for potential content authors.

Consider also the following: - Older resources may help you learn v1, but v1 itself may not be worth learning due to the obsolete parts of its syntax, its hidden complexity and its development reaching a dead-end. It also utilizes some antiquated patterns that teach bad habits. - In broad strokes, v2 syntax is a subset of v1 syntax, and similar to many other languages. What you learn in v2 will help you read v1 code, and perhaps even other languages. By contrast, v1 has more unique syntax and special rules that don’t transfer to v2 or other languages. - It is easier to learn from existing code than it is to write it correctly, even if the existing code is in a different language than the one you use. If you are reading v1 code but writing v2 code, you may be able to learn from the code without necessarily learning all of the rules, special cases and pitfalls unique to v1. - v1 is more “tolerant” of errors in an inconsistent way, having a tendency to ignore them, replace invalid values with zero or blank values, or “report” errors silently in a way that many authors fail to account for. Existing v1 code is thus likely to contain a greater volume of undiscovered errors, and generally be less robust. - In some cases, v2 has better ways of doing things, so learning from v1 code may be counter-productive. Even some recently written v1.1 code relies on methods that have been superseded in v1.1, because the older methods have also been retained, and are perpetuated by new users learning from old code. - Scripts, examples and (in particular) answers to help topics are often posted and forgotten or abandoned. By contrast, the official documentation is continually improved and updated. - Multiple forum software conversions and other issues have broken quite a lot of older code samples on the archive forum, such as by inserting bbcode tags that shouldn’t be there. Code there can’t be updated even if the authors are still active in the community.


Tools make writing scripts easier, especially editors with syntax highlighting, auto-completion, debugging and other shortcuts tailored for AutoHotkey. More such tools are available for v1, since it has been around and in a “stable” state for much longer.

Some popular tools for v1 have not yet been updated by their authors to support v2 syntax. If you’re familiar with those, you’ll already know how much value they hold for you. Otherwise, let me assure you that other tools which support v2 (or both versions) are more than adequate.

SciTE4AutoHotkey supports both versions without any special configuration required.

VS Code can support both versions with the installation of specific extensions. vscode-autohotkey-debug provides extensive debugging features for both v1 and v2, while AutoHotkey v2 Language Support offers syntax highlighting, real-time error detection, auto-completion, code hints, and more (for v2).

The complexity of the v1 syntax means that it is difficult to write good tools for. Syntax highlighting shows how the code should be interpreted (by colour-coding it); accurate highlighting makes it easier to read code and interpret the meaning, whereas inaccurate highlighting can be misleading, especially for beginners. Most syntax highlighters for v1 are inaccurate.

Future development

Unlike v1.x, v2.x and future versions will follow semantic versioning. In other words, the feature set for v2.0 is locked in for all v2.0.x releases, with new features coming in v2.1, v2.2 and so on. As it was during the development of v2.0-alpha, new features will be progressively added to v2.1-alpha, allowing for changes up until the release of v2.1. To get an idea of what v2.1 might contain, see Potential priorities for v2.1.

v2.x releases will be spaced apart (but nothing like the development of v2.0-alpha). Unlike the abrupt v1.1.x releases and sporadic test builds (that I assume most users ignored), this will hopefully ensure that new features will be put through more vigourous testing and actual use, avoiding major bugs on release day (as happened several times for v1.1.x releases).

As an example, v2.1-alpha will be expected to be compatible with v2.0, but features added in one alpha might be altered or removed in a later alpha, or might be deferred until v2.2 or a later release. This will hopefully promote progress, while still providing a stable platform for script authors to build on.

The additions in each release will be fairly significant, so the alpha branch should see plenty of usage. The foundations set by v2.0.0 will ensure that script-breaking changes won’t be nearly as drastic as some that were made during the decade-long development of v2.0-alpha.

Development of v1.x is expected to stagnate (or remain stagnant). The v2 code base has diverged quite a bit from v1, with over 2300 individual changes committed to the v2 branch (as of December 2022). This makes working on the v1 code unpleasant, and even fixing minor bugs across both versions requires tedious resolution of merge conflicts. Differences in how the two branches work has been a source of bugs.

v1.x is not expected to receive any new features, except possibly some that are planned to facilitate the transition to v2, or that might be utilized by external tools that support both versions (such as Ahk2Exe, editors and debuggers). Assume that any feature requests will not be implemented.

Pull requests for v1.x may be considered, but reviewing and/or merging them will not be a priority.

Of course, anyone with sufficient interest and motivation can develop their own fork of AutoHotkey v1 or v2.

There are some possible strategies for solving some of the problems of v1 without necessarily breaking compatibility with old scripts wholesale. For instance, opting in to certain changes, enabling expressions by default, or similar. However, simple changes would be insufficient, resulting in code that still isn’t compatible with v2, essentially making a third version of the language. In some ways, it would be more work than the equivalent changes in v2 because of the need to retain old behaviour but allow it to be “switched off”. I do not plan to go down that road.