Gmaps Scraper
A useful npm library.
Summary
A lightweight, zero-dependency Node.js library to convert Google Maps URLs into standard embeddable iframe URLs. It works by parsing and extracting key details directly from shared map links, avoiding the need for paid APIs or external dependencies.
Beginning
When I was doing an internship at a company-that-shall-not-be-named, we had to build a feature where users could paste a Google Maps link and the location would automatically get embedded on their website.
Now, anyone who has worked with maps before is probably already thinking:
That’s easy. Just use the Google Maps API.
And honestly, that was my first thought too.
But the developer handling it at the time mentioned that the solution they were using depended on a paid API and was currently running on some sort of free trial that would eventually expire. Whether there was a misunderstanding there or not, I didn’t really question it too much at the time.
Instead, I looked at the problem itself and thought:
Wait… all the information is already inside the URL. Surely this can’t be that difficult.
Google Maps links already contain coordinates, place identifiers, redirect chains, and enough metadata to reconstruct an embeddable map URL. So instead of using an API, I started experimenting with scraping and parsing the public URL structure directly.
And that accidental curiosity eventually became this project.
Deep Dive
The library supports two types of Google Maps URLs:
Short URLs
https://maps.app.goo.gl/...These are the tiny shareable URLs Google generates. They do not directly contain location information and instead redirect multiple times before landing on the actual Google Maps page.
Long URLs
https://www.google.com/maps/place/...These contain most of the useful information directly inside the URL itself — coordinates, place names, zoom levels, etc.
The first step of the library is expanding short URLs into long URLs. Since Node.js 18+ includes native fetch, this could be done without any dependencies:
const response = await fetch(url, {
redirect: "follow"
});
const expandedUrl = response.url;Once expanded, the library parses details like latitude and longitude from the URL itself.
For example:
https://www.google.com/maps/place/Statue+Of+Unity/@21.8384759,73.7167201,17z/contains coordinates directly in this section:
@21.8384759,73.7167201which can be extracted using regex:
const coordMatch = expandedUrl.match(
/@(-?\d+\.\d+),(-?\d+\.\d+)/
);
const lat = coordMatch?.[1];
const lng = coordMatch?.[2];Place names are extracted from the /place/ section and decoded:
const placeMatch = expandedUrl.match(/\/place\/([^/]+)/);
const placeName = decodeURIComponent(
placeMatch?.[1]?.replace(/\+/g, " ")
);The final embed URL is then generated manually:
const embed = `https://maps.google.com/maps?q=${lat},${lng}&output=embed`;which can directly be used inside an iframe:
<iframe src={embed}></iframe>One important limitation I discovered was CORS.
This works perfectly on the backend, but browsers block scraping redirect chains from maps.app.goo.gl links directly from frontend apps. That is why the package is intended for backend usage only, with the frontend calling your own API instead.
The final package ended up being extremely lightweight because it uses native Node.js features instead of external dependencies.
Publishing it to npm was surprisingly straightforward too. It was basically -
npm login
npm publishHow To Use
A typical setup looks like this:
Frontend requests with user's URL as query.
It hits your Backend API.
Use google-maps-embed-scraper to process the embed URL through the getEmbedUrl function.
Send embed URL as response to Frontend.
The backend resolves and parses the Google Maps URL, while the frontend simply receives the final embeddable iframe URL.
Backend Example
import express from "express";
import { getEmbedUrl } from "google-maps-embed-scraper";
const app = express();
app.get("/api/embed", async (req, res) => {
try {
const { url } = req.query;
if (!url) {
return res.status(400).json({
error: "URL parameter is required"
});
}
const response = await getEmbedUrl(url);
res.json(response);
} catch (error) {
console.error("Error fetching embed URL:", error);
res.status(500).json({
error: "Failed to fetch embed URL"
});
}
});The response looks something like this:
{
"original": "https://maps.app.goo.gl/...",
"expanded": "https://www.google.com/...",
"embed": "https://maps.google.com/...",
"details": {
"placeName": "Statue Of Unity",
"lat": "21.8384759",
"lng": "73.7167201"
}
}Frontend Example
Once the backend returns the embed URL, the frontend can directly use it inside an iframe. Or can use the other details provided in the response any way they like.
import { useState, useEffect } from "react";
const MapPreview = ({ url }) => {
const [embedSrc, setEmbedSrc] = useState(null);
useEffect(() => {
fetch(`/api/embed?url=${encodeURIComponent(url)}`)
.then(res => res.json())
.then(data => setEmbedSrc(data.embed));
}, [url]);
if (!embedSrc) return null;
return (
<iframe
src={embedSrc}
width="100%"
height="450"
style={{ border: 0 }}
loading="lazy"
/>
);
};This approach keeps all the scraping and redirect handling on the backend while the frontend only handles rendering.
Thoughts
Later on I found out that Google technically provides the Maps Embed API free of charge with unlimited usage for normal embeds.
The catch though is that you still need to set up a Google Cloud project, enable billing, generate an API key, and go through the whole dashboard setup process even if the actual embed requests cost nothing.
So in hindsight, the original problem was not really “avoiding payment” as much as it was avoiding the entire setup overhead for a very small feature.
But honestly, I still do not regret making it.
The process itself was fun. It ended up becoming the first package I ever published on npm, and there is a very specific kind of satisfaction in being able to type:
npm install "google-maps-embed-scraper"and realizing that it is your package.
This package reduced the entire workflow into a tiny utility function without needing Google Cloud configuration at all.
What surprised me the most though was seeing the package cross over 1,000 downloads. I genuinely expected maybe a few people to use it and then forget it existed.
So yeah, even if the original premise came from a misunderstanding, I think it still turned into something worthwhile.
Maybe next time I will accidentally overengineer something even more complicated.
