ghost-cursor: human-like mouse movement for Puppeteer
🖱️ Generate human-like mouse movements with puppeteer or on any 2D plane
At a glance
- What is it?
- ghost-cursor generates realistic, human-like mouse movement data between two coordinates, or drives the mouse to elements in a Puppeteer page. The movement is a Bézier curve rather than a straight line, with overshoot, randomised landing points inside an element and randomised delays, and the same path function works on any two-dimensional plane, not only in a browser.
- Who is it for?
- Adopt ghost-cursor if you are testing your own application with Puppeteer and want the input to look less like a script, or if you need a plausible movement path in a different context such as a canvas or a game input layer. Do not adopt it to drive someone else's site, because making automated traffic look human is the behaviour that bot detection exists to catch, and the package metadata itself lists botting as a keyword.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two things in one package
ghost-cursor generates realistic, human-like mouse movement data between coordinates, or navigates between elements with Puppeteer, like the definitely-not-robot you are.
The two halves are separable and that is the most useful thing to know about the project before you install it. The first half is a pure function that takes two points and returns a list of intermediate points, with optional timestamps. It has no dependency on a browser at all, and the package description says it can be used on any 2D plane, which is a wider claim than the Puppeteer example in the README suggests.
The second half is a class that wraps a Puppeteer page and gives you move and click operations with realistic behaviour.
The project is written in TypeScript, published to npm as ghost-cursor, and the version in the package manifest is 1.4.2. There are no GitHub releases, the repository is not archived, and the last push was on 2026-10-13.
One discrepancy is worth flagging, because it is the kind of thing that matters if you vendor the code. The repository metadata reports the licence as MIT, while the package manifest declares ISC, and the README has no licence section. The MIT field in the manifest is the one that applies to the published package, but the mismatch between the two should be settled by the maintainer rather than by you.
The path function, on its own
The simplest use is generating movement data between two coordinates:
import { path } from "ghost-cursor"
const from = { x: 100, y: 100 }
const to = { x: 600, y: 700 }
const route = path(from, to)The result is an array of points that starts at the origin and eases toward the destination, and the example in the README shows the shape of the output with the fractional coordinates, which is the first hint that the curve is not a straight interpolation.
The same call with a third argument adds timestamps:
const route = path(from, to, { useTimestamps: true })and the output then carries a timestamp on each point, which is what you need if you are replaying the movement into a log or a simulation.
The underlying mechanism is visible in the dependencies. bezier-js is the curve library, and the keywords in the package manifest list bezier-curve and mouse-movement, so the path is a Bézier curve with the control points chosen to produce a plausible arc rather than a fixed shape.
The Puppeteer class and what it does differently
The Puppeteer usage wraps a page in a GhostCursor instance and then moves and clicks with it:
import { GhostCursor } from "ghost-cursor"
import puppeteer from "puppeteer"
const run = async (url) => {
const selector = "#sign-up button"
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage()
const cursor = new GhostCursor(page)
await page.goto(url)
await page.waitForSelector(selector)
await cursor.click(selector)
}The constructor takes the Puppeteer page plus four optional settings: the start position, defaulting to the origin, a flag for performing random movements initially, a set of default options for the click, move, moveTo and randomMove functions, and a visible flag that installs the mouse helper so you can see the cursor, defaulting to false.
The puppeteer-specific behaviour is listed separately and it is what makes the movement plausible rather than merely curved. A move will automatically overshoot or slightly miss and then re-adjust for elements that are too far away from the cursor's starting point. When moving over an object, a random coordinate within the element is selected instead of hovering over its exact centre. And the speed of the mouse takes both the distance and the size of the element being clicked into account.
That last one is the detail that matters most in practice, because a fixed movement speed is the most obvious give-away in automated input.
The option vocabulary, and the defaults that matter
The options are documented thoroughly, and the defaults are where the tuning happens.
The click options extend the options of the move, scrollIntoView and getElement functions, and they are hesitate, a delay before the click action in milliseconds defaulting to zero, waitForClick, the delay between mousedown and mouseup defaulting to zero, moveDelay, the delay after moving the mouse defaulting to 2000 milliseconds, which is randomised from zero when randomizeMoveDelay is set, button, the mouse button, defaulting to left, and clickCount, defaulting to one.
The move options are richer. paddingPercentage is the percentage of padding added inside the element when determining the target point, defaulting to zero, which may move anywhere within the element, with 100 always moving to the centre. The destination option is a vector relative to the top-left corner of the element, and when it is specified the padding percentage is not used. maxTries is the maximum number of attempts to move over the element, defaulting to ten, moveSpeed defaults to random, and overshootThreshold is the distance from the current location to the destination that triggers overshoot, defaulting to 500, below which no overshoot occurs.
The moveTo and moveBy functions take the same family of options, with a moveDelay defaulting to zero there, and randomizeMoveDelay defaulting to true, so the delays are jittered unless you turn that off. There is also a toggleRandomMove function to switch random movements on and off, and a scrollIntoView that does nothing if the element is already in view.
The number of knobs is large, but they fall into three groups: where you land, how fast you travel, and how long you pause.
The build, the tests and the packaging
The package is small and the tooling is modern.
The main entry point is lib/spoof.js with types from lib/spoof.d.ts, and the published files are everything under lib, so the source is compiled with tsc through the tsconfig.build.json configuration. The scripts are build, lint with ts-standard and the fix flag, test with jest, and validate, which runs the lint and the build together, so a single command covers the gate.
The test setup is the part worth describing, because it involves a real browser. The Jest configuration uses the jest-puppeteer preset, with @swc/jest doing the transformation instead of the slower default, and the reporters list includes default and github-actions, so the results appear in a pull request. The jest-puppeteer.config.js file at the root configures the browser environment, and the module path ignore patterns keep the compiled lib and the shared test file out of the way.
The dev dependencies say the rest. puppeteer is at 25.4.0, jest-puppeteer at 11, jest at 30, @swc/core for the fast transform, ts-node for the debug script that launches a browser, and TypeScript 6. The package manager is yarn at 4.18.0, with the .yarnrc.yml for the configuration, and husky with a prepare script for the git hooks.
The runtime dependencies are only three, bezier-js for the curve, debug for the logging namespace, and the bezier type definitions, which is a small surface for a library that does what it does.
Where this belongs, and where it does not
The package metadata is worth reading on this point, because it is candid in a way the README is not. The keyword list includes botting alongside bezier-curve and mouse-movement.
That word is the honest summary of what the tool can be used for, and it points at the line. Automating interactions with your own application, in a test suite, against a staging environment, or as part of a legitimate testing tool for a product you maintain, is ordinary engineering. Using it to drive traffic at somebody else's site, or to make automated activity pass for human, is the behaviour that bot detection exists to catch, and it will usually breach the terms of the site you are doing it to.
The tool itself does not take a position, and it is not unusual for a cursor simulation library to end up in both hands. The useful framing is that the more human your synthetic input looks, the more valuable it is for testing your own defences and the more it costs you elsewhere.
If you are using it in the first way, the practical advice is the same as for any browser automation: run it against a staging environment, keep the real credentials out of it, and make sure your own detection rules are tested against it, because a tool that can defeat naive heuristics is exactly what you want to point at your heuristics.
The project also publishes a Discord server and a community, and the screenshot in the README shows the cursor in action on a sign-up form.
Editorial conclusion
Adopt ghost-cursor if you are testing your own application with Puppeteer and want the input to look less like a script, or if you need a plausible movement path in a different context such as a canvas or a game input layer. Do not adopt it to drive someone else's site, because making automated traffic look human is the behaviour that bot detection exists to catch, and the package metadata itself lists botting as a keyword. Before you start, decide whether you need the path function or the Puppeteer class, since the first has no browser dependency at all, and check the licence, because the repository metadata says MIT while the package manifest declares ISC.
Frequently asked questions
What does ghost-cursor do?
It generates realistic, human-like mouse movement data between two coordinates, or navigates between elements in a Puppeteer page. The movement is a Bezier curve rather than a straight line, and the path function works on any 2D plane without needing a browser.
How do I install and use ghost-cursor?
Run yarn add ghost-cursor or npm install ghost-cursor, then import path from ghost-cursor and call it with a from and a to point to get an array of intermediate coordinates, or pass useTimestamps to get timestamps on each point.
What makes the Puppeteer movement look human?
A move will automatically overshoot or slightly miss and re-adjust for elements too far from the starting point, a random coordinate inside the element is chosen instead of its exact centre, and the movement speed takes both the distance and the size of the element into account. The overshoot is controlled by overshootThreshold, which defaults to 500.
Which options control the delays in ghost-cursor?
The click options include hesitate before the click, waitForClick between mousedown and mouseup, and moveDelay after the move, which defaults to 2000 milliseconds for a click and is randomised from zero when randomizeMoveDelay is set, with randomizeMoveDelay defaulting to true.
How is ghost-cursor tested?
The Jest configuration uses the jest-puppeteer preset with @swc/jest for the transform and a jest-puppeteer.config.js for the browser environment, and the dev dependencies include puppeteer 25.4.0 and jest-puppeteer 11. The package is built with tsc and validated with a lint and build script, and the version in the manifest is 1.4.2.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/xetera-ghost-cursor)Was this useful?