Calculate charts in your own process.
@zodiacs/engine is the chart engine this site runs. It takes an instant and a pair of coordinates and returns bodies, angles, house cusps and aspects. The calculation is synchronous and makes no network request: nothing about the birth details it is given leaves the process it runs in.
ESM, TypeScript declarations included, for Node.js 20.19 or a later 20.x, Node.js 22.7 and newer, or any browser with a bundler. The code is MIT; the package is MIT and CC BY 4.0 together, because its ΔT module carries 32 values from Stephenson, Morrison and Hohenkerk's Table S15, which are CC BY 4.0. It also carries time data: the IERS leap-second list and a UT1 − UTC table from IERS, and the IANA time zone history before 1970; its NOTICE names every source. One runtime dependency — Astronomy Engine by Don Cross, also MIT, which supplies the planetary and lunar series. Zodiacs did not write those models; what is ours is the chart on top of them — houses, angles, aspects, the record format and the limits.
A worked example
This is the whole of it. The output below is what the program prints — a test in this repository runs this exact source against the archive below and fails if the two stop matching.
import { natalChart } from '@zodiacs/engine';
// 15 June 1990, 13:30 UTC, London. No API key and no network call.
const chart = natalChart({
utc: '1990-06-15T13:30:00Z',
latitude: 51.5074,
longitude: -0.1278,
houseSystem: 'placidus',
});
for (const p of chart.bodies) {
const mark = p.retrograde ? ' retrograde' : '';
console.log(`${p.body.padEnd(10)} ${p.degree.toFixed(2).padStart(6)}° ${p.sign}${mark}`);
}
console.log(`ascendant ${chart.angles.asc.toFixed(2)}° engine ${chart.engineVersion}`);
Sun 24.19° gemini
Moon 16.19° pisces
Mercury 5.80° gemini
Venus 18.85° taurus
Mars 11.09° aries
Jupiter 15.90° cancer
Saturn 24.03° capricorn retrograde
Uranus 8.16° capricorn retrograde
Neptune 13.72° capricorn retrograde
Pluto 15.40° scorpio retrograde
North Node 8.12° aquarius
South Node 8.12° leo
ascendant 191.24° engine 0.1.1-rc.17
Install it
This revision vendors 0.1.1-rc.17, a release candidate that is not on npm. To install it, paste this into a POSIX shell, in your project's directory: it downloads the archive from its immutable carrier in the engine repository and refuses to install anything whose digest does not match. It also stops if that directory has no package.json, because npm would otherwise install into the nearest directory above it that has one.
( set -eu
# POSIX shell — macOS, Linux or WSL. Not PowerShell. It runs in a subshell, so
# a failure stops the install without closing your terminal.
BASE=$(pwd)
FILE='zodiacs-engine-0.1.1-rc.17.tgz'
# Run it in your project's directory. Without a package.json here, npm would
# install into the nearest parent directory that has one.
if test ! -f package.json; then
echo "Stop: there is no package.json here. Run this in your project's directory, or create one first with: npm init -y" >&2
exit 1
fi
if test -e "$FILE" || test -L "$FILE"; then
echo "Stop: $FILE already exists here. Move or delete it, then run this again." >&2
exit 1
fi
# A download that fails verification is removed, so the guard above does not
# then block the retry. Once npm takes over the archive stays: npm reports its
# own failures, and the file is what you would retry with.
trap 'status=$?; if test "$status" -ne 0; then rm -f "$BASE/$FILE"; fi; exit $status' EXIT
curl --disable --fail --silent --show-error --location --proto '=https' --max-time 120 \
'https://raw.githubusercontent.com/zodiacs-org/engine/b080217c1b5b0b36c931e9c237bcff0308ec1efc/artifacts/zodiacs-engine-0.1.1-rc.17.tgz' -o "$FILE"
node --input-type=module <<'JS'
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
const file = 'zodiacs-engine-0.1.1-rc.17.tgz';
const expected = '9cd24c788863424ef614aaadec580db5a0dfc529303d385274db48a092a5299a';
const bytes = readFileSync(file);
const actual = createHash('sha256').update(bytes).digest('hex');
if (actual !== expected) {
console.error(`Stop: this is not the published archive.\n expected ${expected}\n got ${actual}`);
console.error('Nothing was installed.');
process.exit(1);
}
console.log(`Archive verified: ${actual}`);
JS
trap - EXIT
npm install --ignore-scripts "./$FILE"
echo "Installed @zodiacs/engine@0.1.1-rc.17 from the verified archive."
)SHA-256: 9cd24c788863424ef614aaadec580db5a0dfc529303d385274db48a092a5299a
On npm, the verified registry read of 2026-10-06 records 0.1.1-rc.16 under next and 0.1.1-rc.15 under latest. npm install @zodiacs/engine@0.1.1-rc.16 installs 0.1.1-rc.16, the version before this candidate, which refuses the sidereal zodiac in @zodiacs/engine/calc. It was published from the engine repository's release.yml workflow with SLSA provenance, which names the source repository, the workflow and the commit the package was built from; npm audit signatures verifies it.
Prefer a project that already runs? The starter has natal, transit and widget examples wired up, and the MCP adapter puts the same engine behind an assistant over stdio.
What it computes
Read the 0.1.1-rc.17 API reference for public exports, parameter and return types, and the release’s documentation comments.
- positions(date)
- Apparent geocentric tropical longitudes for an instant: Sun through Pluto plus the true nodes, with ecliptic latitude, daily speed, retrograde state and sign.
- natalChart(birth)
- A full chart from a resolved UTC instant and coordinates: bodies, angles, house cusps and major aspects, plus the flags that say what was assumed. Without a known time it returns positions and leaves angles and houses null.
- transits(natal, date)
- Current positions and their major aspects to a natal chart.
- synastry(first, second)
- Inter-chart aspects between two charts with element and modality balances.
- moonPhase(date)
- Phase angle, illumination and name, from the same positions a chart uses.
- saturnReturn(birth)
- Natal Saturn and every return season to roughly age 92, each with its exact crossings.
- progressedBodies(birth, target)
- Secondary progressions: the positions one day after birth for each tropical year of life lived to the target date. This site's progressed lens uses it.
- findConfiguredAspects(bodies, policy)
- Aspects under a policy you set with createAspectPolicy: major, minor or custom angles, and orbs per aspect, per body and for applying or separating motion.
- chartDeclinations(chart)
- Right ascension and declination of each body, parallels and contraparallels, and each body's signed margin to the out-of-bounds limit, in arcseconds.
- @zodiacs/engine/timing
- Hellenistic timing, a separate import: annual profections, firdaria, zodiacal releasing and solar arc directions. This site uses none of it.
- @zodiacs/engine/vedic
- The sidereal zodiac, a separate import: ayanamsas, sidereal charts, nakshatras, vargas, KP sub-lords and the Vimshottari, Yogini and Ashtottari dashas. This site uses none of it.
It computes thirteen house systems, each as Swiss Ephemeris defines it: whole sign, Placidus, Koch, Porphyry, Regiomontanus, Campanus, Topocentric (Polich–Page), Alcabitius, Equal (Ascendant), Equal-MC, Vehlow, Meridian (axial rotation) and Morinus. Aspects are the five major ones, with separate luminary orbs. A configured aspect policy, a separate function, adds minor and custom angles. Optional entry points ship beside the core: @zodiacs/engine/geo resolves a local wall time and IANA zone to an instant, @zodiacs/engine/receipt validates a portable chart record, and /timing and /vedic hold the techniques listed above. Each is a separate import, so none costs anything if you do not use it. Before 1970, /geo reads a wall time on the IANA time zone history the package ships, in 16 files that prepareLocalTime loads first; from 1970 on, on the time zone data of the host.
One thing to know about @zodiacs/engine/geo before you import it: the time resolver itself is offline, but the same entry point also exports a GeoNames place-lookup client, and that one makes HTTP requests to wherever you point it. Import resolveBirth by name and a bundler drops the client; import the namespace and you ship it. The core entry point contains no fetch at all.
What it costs
Bundled for a browser with esbuild 0.28.1, minified, entry import { natalChart } from '@zodiacs/engine' — its ephemeris included, because that is what actually ships:
| Entry | Minified | gzip | brotli |
|---|---|---|---|
| Natal charts | 77.8 KB | 35.8 KB | 29.7 KB |
+ resolveBirth, local-time resolution alone | 92 KB | 42.5 KB | 35.6 KB |
+ the whole /geo entry point, GeoNames client and the 16 zone-history files included | 280.9 KB | 81 KB | 61.6 KB |
Most of the first row is the ephemeris series, and it is the price of not calling a server. The last row is mostly the time zone history before 1970: bundled into one file, as here, it comes along whole, while a bundler that splits code ships each of its 16 files as a separate chunk that prepareLocalTime loads only for a date that needs it. There are no data files to host or download at runtime, no key, and no account. This site keeps the engine out of its own homepage bundle and loads it only on pages that cast a chart; a bundler will do the same for you if you import it behind a dynamic import.
How close it is
Every change runs a comparison against NASA JPL Horizons reference data; at the two dates it checks, 1907 and 2020, the largest difference is 14.8 arcseconds (Neptune, 2020). Separately, the engine was measured against Swiss Ephemeris 2.10.03 over a stratified set of dates written down beforehand, on 20 September 2026 and again on 25 September: across the 160 measurements from 1801 to 2026 the median disagreement in ecliptic longitude is 1.6 arcseconds and the largest is 18.6 arcseconds. Compared every tenth day from 1800 to 2026 instead, for the ten bodies and the North Node, the median is 2.0 arcseconds and the largest 22.9 arcseconds(Venus, 1878).
Three caveats that matter more than the number. Swiss Ephemeris and this engine's planetary series both descend from JPL development ephemerides, and the engine's Moon comes from an older lunar theory, so that is two implementations agreeing rather than a check against observation.
Up to the present the clock is observed. Since 0.1.1-rc.15 the engine reads an instant from 1972 to 2 October 2027 as UTC: atomic time comes from the IERS leap seconds, and the Earth's rotation from the IERS values of UT1 − UTC, which its table reproduces within 0.79 milliseconds on every day. Other instants it reads as UT1 and takes ΔT, the gap between atomic time and the Earth's rotation, from its model, as it has since 0.1.1-rc.8: Stephenson, Morrison and Hohenkerk's 2016 reconstruction before 1941, US Naval Observatory and IERS records after it, and the IERS predictions; from 1962 on it is within 0.09 seconds of the IERS value on every day. Every chart returns the ΔT it used with its one-sigma uncertainty, and says which way it read its instant. Past the predictions both programs extrapolate: from 2100 to 2199 their ΔT values differ by 14.3 to 36.7 seconds, which alone moves the Moon by 7.0 to 23.4 arcseconds; at the same instant of Terrestrial Time, which takes the clock out, it stays within 7.2 arcseconds from 2150 to 2199. Until rc.8 the clock was a fixed formula: on 22 September 2026 it read 75.5 seconds where the IERS value is 69.2, which on its own moved the Moon about 3.5 arcseconds.
And the engine bounds its input date only where its ephemeris ends: since rc.14 an instant outside Terrestrial Time 0001-04-30 to 3998-09-03 throws a RangeError. It will compute year 900 or year 3500 and return a chart with no error. Nothing above says anything about either, and since rc.8 the chart says so itself: outside 1800–2200 it carries the outside-reference-span flag. zodiacs.org clamps its own forms to 1800–2199; if you are taking dates from users, pick a bound and enforce it yourself.
One report covers all of it — positions, angles and houses, local time, event search and runtime support — with the measured residual for each, the oracle it came from, and what it does not establish. It also carries the event-search contract that is still failing, which is the weakest thing anyone evaluating this should know.
What it does not do
- The core is tropical. The sidereal zodiac, with its ayanamsas, is in the separate
/vedicand/calcentry points, which this site does not use. The core has no topocentric parallax;/calcoffers a topocentric center. - Sun through Pluto and the true nodes. No Chiron, no asteroids.
- Placidus and Koch are undefined inside the polar circle; where the absolute latitude is 90° minus the obliquity or more (66.53° to 66.59° over 1800–2200) they fall back to whole sign and flag it. The other eleven systems are defined there.
- Event searches sample the motion at a fixed step, so they can miss an event between samples. The solver is tested, not proven complete.
- No input-date bound inside the span the ephemeris covers, Terrestrial Time 0001-04-30 to 3998-09-03; outside it a calculation throws. Outside 1800–2200, the range the engine has been checked over, a chart carries the
outside-reference-spanflag; see the caveat under accuracy. - The manifest declares Node 20.19 or a later 20.x, and 22.7 and newer. What has actually been executed for this candidate is Node 22.22.2 and Node 24.21.0 here, with the site's Chromium browser checks, and Node 20.19.0, 22.7.0, 22.22.2 and 24.21.0 in the engine's own packed-archive check — narrower than the declaration.
- No interpretation. The engine returns geometry; what it means is your problem, and not an astronomical claim.
The support matrix states each of these precisely, along with the runtimes actually tested, the input rules, and the licensing obligations that come with the optional GeoNames-derived place index.
Source and licence
The code is MIT, in the Zodiacs engine repository at commit aae419c05b77; the package's licence expression is MIT AND CC-BY-4.0, for the ΔT values above. Keep the packaged LICENSE, LICENSING.md and NOTICE when you redistribute: the NOTICE carries Astronomy Engine's MIT notice, the attribution those ΔT values require, the sources of the time zone, leap-second and UT1 data and of the star values in /vedic and /calc, and the CC BY 4.0 attribution that GeoNames-derived place data requires; LICENSING.md gives the terms of each and three questions about them that are still open. Report a problem.











