India's borders, drawn correctly on any web map.
Free map styles are built from OpenStreetMap, which shows Kashmir, Ladakh and Arunachal Pradesh differently from the Survey of India. bharatmap fixes that with one function call. No server, no API key, no dependencies.
About 40 kB gzipped. Apache-2.0. Works with MapLibre, Leaflet, OpenLayers and deck.gl.
Drag the handle to compare. Pan and zoom the map, or use the buttons. To zoom with the scroll wheel hold Ctrl; on a phone use two fingers.
- -
- Installs this month
- -
- Installs this week
- -
- Installs in total
- -
- GitHub stars
- -
- Latest version
Add it to your map
Pick the library you use. Three steps, about a minute.
Install the package
npm install bharatmapCall fixMap after you create the map
It fixes the style you have now and every later
setStyle, so switching between light and dark keeps working.import maplibregl from "maplibre-gl"; import { fixMap } from "bharatmap"; const map = new maplibregl.Map({ container: "map", style: STYLE_URL, center: [78, 22], zoom: 4 }); await fixMap(maplibregl, map);If you would rather fix the style before the map exists, use
install(maplibregl)once, thenawait fixStyle(STYLE_URL)and pass the result asstyle.Check that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Nothing to install
Add the script after MapLibre. It puts a
BharatMapobject on the page.Fix the style, then make the map
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bharatmap.iife.js"></script> <script> BharatMap.install(maplibregl); BharatMap.fixStyle("https://basemaps.cartocdn.com/gl/positron-gl-style/style.json").then((style) => { new maplibregl.Map({ container: "map", style, center: [78, 22], zoom: 4 }); }); </script>This is how the map at the top of this page is built.
Check that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Install the packages
Leaflet draws vector basemaps through maplibre-gl-leaflet, which needs MapLibre. bharatmap fixes the MapLibre style.
npm install bharatmap maplibre-glFix the style, then hand it to Leaflet
import maplibregl from "maplibre-gl"; import { install, fixStyle } from "bharatmap"; install(maplibregl); const style = await fixStyle("https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"); L.maplibreGL({ style }).addTo(map);If your Leaflet map uses plain
L.tileLayer("...png")tiles, bharatmap cannot cut the border. See the table below for what you can do.Check that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Install the packages
npm install bharatmap ol ol-mapbox-styleFix the style and set the tile loader
OpenLayers has its own tile loading, so you turn off the MapLibre protocol and give each vector tile source bharatmap's loader.
import { fixStyle, olTileLoadFunction } from "bharatmap"; import { apply } from "ol-mapbox-style"; import VectorTileSource from "ol/source/VectorTile"; await apply(map, await fixStyle(STYLE_URL, { protocol: false })); map.getAllLayers().forEach((layer) => { const source = layer.getSource && layer.getSource(); if (source instanceof VectorTileSource) { source.setTileLoadFunction(olTileLoadFunction); source.refresh(); } });Check that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Install the package
npm install bharatmapFix the basemap style, not deck.gl
deck.gl, react-map-gl and the Vue and Angular wrappers draw their basemap with MapLibre, so you fix the style you pass in. This is the same engine as the MapLibre tab, but it has not been tested separately for each wrapper.
import maplibregl from "maplibre-gl"; import { install, fixStyle } from "bharatmap"; install(maplibregl); // once, at startup const style = await fixStyle(STYLE_URL); // pass this as the basemap styleCheck that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Host the corrected tiles
Mapbox GL JS, Tangram and QGIS cannot change tile requests the way MapLibre can, so you run a small tile proxy that returns tiles with the disputed lines already cut out. This one is a Cloudflare Worker, and the same handler runs on Deno, Bun and Node 18 and later.
import { tileProxy } from "bharatmap"; export default { fetch: tileProxy("https://tiles.example.com/planet/{z}/{x}/{y}.pbf"), };Point your map at it and add the lines
The proxy serves
/{z}/{x}/{y}.pbf. Use that as the tile source. India's boundary itself is inboundary, a GeoJSON FeatureCollection you can add to any map as a line layer.On Mapbox's own styles, check their worldview setting first. It is meant for this and may already do what you need.
Check that it worked
- Open the map in light and dark if you support both, and look at these views.
- Whole India (zoom 2 to 3): one line around India, with no extra dashed lines over Ladakh or Kashmir.
- Arunachal Pradesh (zoom 5 to 7): one continuous border, and the Myanmar and Bhutan borders still drawn.
- Where the line meets Himachal Pradesh and Bhutan (zoom 9 to 12): no gaps and no doubled lines.
- Credits: the border data attribution shows in the map's corner.
Or let your AI assistant do it
It reads the project, finds your map library and basemap, makes the change and checks it at several zoom levels.
One command for your project
Writes the instructions where your assistant looks: Claude Code, Cursor, Copilot, Windsurf, Gemini CLI, or AGENTS.md for the rest. Then ask it to "fix the India borders on my map with bharatmap".
npx bharatmap initClaude Code plugin
/plugin marketplace add Life-Experimentalist/bharatmap
/plugin install bharatmap@bharatmapAny assistant that can open a link
Paste this. The file behind the link is always the current version, so you never have stale instructions.
Follow https://bharatmap.vkrishna04.me/skill.md to fix the India borders on my map.Crawlers and agents can also read skill.md and llms.txt directly.
What works with what
Any vector basemap in the OpenMapTiles schema: CARTO (Positron, Dark Matter, Voyager), OpenFreeMap, MapTiler, Stadia and others.
MapLibre GL JS
fixMap(maplibregl, map)Tested in a browser, light and dark, zoom 2 to 14.Leaflet, vector basemap
fixStyle, thenL.maplibreGL({ style }). Tested in a browser.OpenLayers
fixStylewithprotocol: falseandolTileLoadFunction. Tested in a browser.deck.gl, react-map-gl
Same as MapLibre, on the style you pass in. Same engine, not tested separately.
Mapbox GL JS, Tangram, QGIS
Use
tileProxy, a handler you host. Covered by unit tests only.Leaflet, raster tiles
Only an overlay is possible:
L.geoJSON(BharatMap.boundary). The raster's own line stays underneath, because a border baked into a picture cannot be cut.Google Maps
Nothing to do. Google already draws India's borders per local depiction.
Apple Maps
Not possible. The vendor decides what it draws.
Why a style filter is not enough
Free basemaps are built from OpenStreetMap, whose contributors draw disputed borders as the lines on the ground. So an OpenStreetMap-based map shows the Line of Actual Control, the Line of Control and the China side of Arunachal Pradesh as international borders, often as dashed fragments that are still visible at world zoom.
You cannot hide them with a style rule. The tile builder merges several disputed borders into one long line, so a filter removes all of it or none of it. A filter box big enough to catch the strays also deletes real borders, such as Myanmar and Bhutan in Arunachal Pradesh.
bharatmap cuts the geometry inside each vector tile as it loads, then draws India's boundary in its place, in the basemap's own line style. Tiles that miss the affected areas come back untouched, and the rest take a few milliseconds each. The technical details are in how it works, and custom schemas are covered in basemaps.
If something looks wrong, and other questions
Troubleshooting
Nothing changed on my map
Check three things. First, the basemap must be vector tiles. If your map uses .png or .jpg tiles the border is part of the picture and cannot be cut. Second, the style needs a boundary layer with admin_level 2, which is the OpenMapTiles schema. Look for "source-layer": "boundary" in the style JSON. Third, make sure the style you pass to the map is the one that came back from fixStyle, and that you waited for it.
It is right until I switch between light and dark
A new style replaces the fixed one. Use fixMap(maplibregl, map), which fixes every later setStyle for you. If you use fixStyle instead, run every style you load through it, including the dark one.
Where did the data credit go?
bharatmap adds the border data attribution to the map's credits on its own. Keep the attribution control on the map and it stays there.
I see a small gap at a high zoom
The coverage check reports no gaps from zoom 1 to 10 and a few tile-edge pieces at zoom 11 and 12. Zoom 13 and above was checked by eye. If you find one, open an issue on GitHub with the zoom level and the coordinates, and it can be fixed in the module's region data.
My basemap uses a different schema
You can pass a profile that names the tile layer and says which lines to cut. The three parts are described in basemaps, along with a command that checks a new basemap for gaps.
Questions
Is the line legally authoritative?
No. It follows a published depiction of the Survey of India line, checked against OpenStreetMap where OpenStreetMap independently draws the same line. If you publish the map, verify the line against the Survey of India itself.
Does it need a server or an API key?
No. It runs in the browser. Only the optional tile proxy for Mapbox GL JS and similar clients needs hosting.
Does it slow the map down?
Tiles that do not touch the affected areas are returned untouched. The others take a few milliseconds each. The library is about 40 kB gzipped and has no runtime dependencies.
What is the license?
The code is Apache-2.0. The border data is ODbL 1.0, and its attribution is added to the map for you. Both are described in the repository.
Does it work with TypeScript?
Yes. Types ship with the package.
Badges and banner
Using bharatmap on your site or in your project? Add a badge so others can find it.
"Uses bharatmap" badge
[](https://bharatmap.vkrishna04.me/)Project badges for your README
[](https://www.npmjs.com/package/bharatmap)
[](https://www.npmjs.com/package/bharatmap)
[](https://github.com/Life-Experimentalist/bharatmap)
[](https://bundlephobia.com/package/bharatmap)
[](https://www.npmjs.com/package/bharatmap)
[](https://github.com/Life-Experimentalist/bharatmap/blob/main/LICENSE)Banner
A 1200 by 630 image, the same one used when this page is shared.

[](https://bharatmap.vkrishna04.me/)