google-webfonts-helper
Hassle-Free Way to Self-Host Google Fonts. Get eot, ttf, svg, woff and woff2 files + CSS snippets.
Hassle-Free Way to Self-Host Google Fonts. Get eot, ttf, svg, woff and woff2 files + CSS snippets.
# google-webfonts-helper [](https://gwfh.mranftl.com) [](https://gwfh.mranftl.com) [](https://github.com/sponsors/majodev)
> A Hassle-Free Way to Self-Host Google Fonts
✅ **[https://gwfh.mranftl.com](https://gwfh.mranftl.com)**
## Current Sponsors
> *Help me keep this service alive by [sponsoring me](https://github.com/sponsors/majodev). Thank you. ❤️*
[<img src="https://sponsors.mranftl.com/avatar/0" width="35">](https://sponsors.mranftl.com/profile/0)
[<img src="https://sponsors.mranftl.com/avatar/1" width="35">](https://sponsors.mranftl.com/profile/1)
[<img src="https://sponsors.mranftl.com/avatar/2" width="35">](https://sponsors.mranftl.com/profile/2)
[<img src="https://sponsors.mranftl.com/avatar/3" width="35">](https://sponsors.mranftl.com/profile/3)
[<img src="https://sponsors.mranftl.com/avatar/4" width="35">](https://sponsors.mranftl.com/profile/4)
[<img src="https://sponsors.mranftl.com/avatar/5" width="35">](https://sponsors.mranftl.com/profile/5)
[<img src="https://sponsors.mranftl.com/avatar/6" width="35">](https://sponsors.mranftl.com/profile/6)
[<img src="https://sponsors.mranftl.com/avatar/7" width="35">](https://sponsors.mranftl.com/profile/7)
[<img src="https://sponsors.mranftl.com/avatar/8" width="35">](https://sponsors.mranftl.com/profile/8)
[<img src="https://sponsors.mranftl.com/avatar/9" width="35">](https://sponsors.mranftl.com/profile/9)
[<img src="https://sponsors.mranftl.com/avatar/10" width="35">](https://sponsors.mranftl.com/profile/10)
[<img src="https://sponsors.mranftl.com/avatar/11" width="35">](https://sponsors.mranftl.com/profile/11)
[<img src="https://sponsors.mranftl.com/avatar/12" width="35">](https://sponsors.mranftl.com/profile/12)
[<img src="https://sponsors.mranftl.com/avatar/13" width="35">](https://sponsors.mranftl.com/profile/13)
[<img src="https://sponsors.mranftl.com/avatar/14" width="35">](https://sponsors.mranftl.com/profile/14)
[<img src="https://sponsors.mranftl.com/avatar/15" width="35">](https://sponsors.mranftl.com/profile/15)
[<img src="https://sponsors.mranftl.com/avatar/16" width="35">](https://sponsors.mranftl.com/profile/16)
[<img src="https://sponsors.mranftl.com/avatar/17" width="35">](https://sponsors.mranftl.com/profile/17)
[<img src="https://sponsors.mranftl.com/avatar/18" width="35">](https://sponsors.mranftl.com/profile/18)
[<img src="https://sponsors.mranftl.com/avatar/19" width="35">](https://sponsors.mranftl.com/profile/19)
[<img src="https://sponsors.mranftl.com/avatar/20" width="35">](https://sponsors.mranftl.com/profile/20)
[<img src="https://sponsors.mranftl.com/avatar/21" width="35">](https://sponsors.mranftl.com/profile/21)
[<img src="https://sponsors.mranftl.com/avatar/22" width="35">](https://sponsors.mranftl.com/profile/22)
[<img src="https://sponsors.mranftl.com/avatar/23" width="35">](https://sponsors.mranftl.com/profile/23)
[<img src="https://sponsors.mranftl.com/avatar/24" width="35">](https://sponsors.mranftl.com/profile/24)
[<img src="https://sponsors.mranftl.com/avatar/25" width="35">](https://sponsors.mranftl.com/profile/25)
[<img src="https://sponsors.mranftl.com/avatar/26" width="35">](https://sponsors.mranftl.com/profile/26)
[<img src="https://sponsors.mranftl.com/avatar/27" width="35">](https://sponsors.mranftl.com/profile/27)
[<img src="https://sponsors.mranftl.com/avatar/28" width="35">](https://sponsors.mranftl.com/profile/28)
[<img src="https://sponsors.mranftl.com/avatar/29" width="35">](https://sponsors.mranftl.com/profile/29)
[<img src="https://sponsors.mranftl.com/avatar/30" width="35">](https://sponsors.mranftl.com/profile/30)
[<img src="https://sponsors.mranftl.com/avatar/31" width="35">](https://sponsors.mranftl.com/profile/31)
[<img src="https://sponsors.mranftl.com/avatar/32" width="35">](https://sponsors.mranftl.com/profile/32)
[<img src="https://sponsors.mranftl.com/avatar/33" width="35">](https://sponsors.mranftl.com/profile/33)
[<img src="https://sponsors.mranftl.com/avatar/34" width="35">](https://sponsors.mranftl.com/profile/34)
[<img src="https://sponsors.mranftl.com/avatar/35" width="35">](https://sponsors.mranftl.com/profile/35)
[<img src="https://sponsors.mranftl.com/avatar/36" width="35">](https://sponsors.mranftl.com/profile/36)
[<img src="https://sponsors.mranftl.com/avatar/37" width="35">](https://sponsors.mranftl.com/profile/37)
[<img src="https://sponsors.mranftl.com/avatar/38" width="35">](https://sponsors.mranftl.com/profile/38)
[<img src="https://sponsors.mranftl.com/avatar/39" width="35">](https://sponsors.mranftl.com/profile/39)
[<img src="https://sponsors.mranftl.com/avatar/40" width="35">](https://sponsors.mranftl.com/profile/40)
[<img src="https://sponsors.mranftl.com/avatar/41" width="35">](https://sponsors.mranftl.com/profile/41)
[<img src="https://sponsors.mranftl.com/avatar/42" width="35">](https://sponsors.mranftl.com/profile/42)
[<img src="https://sponsors.mranftl.com/avatar/43" width="35">](https://sponsors.mranftl.com/profile/43)
[<img src="https://sponsors.mranftl.com/avatar/44" width="35">](https://sponsors.mranftl.com/profile/44)
[<img src="https://sponsors.mranftl.com/avatar/45" width="35">](https://sponsors.mranftl.com/profile/45)
[<img src="https://sponsors.mranftl.com/avatar/46" width="35">](https://sponsors.mranftl.com/profile/46)
[<img src="https://sponsors.mranftl.com/avatar/47" width="35">](https://sponsors.mranftl.com/profile/47)
[<img src="https://sponsors.mranftl.com/avatar/48" width="35">](https://sponsors.mranftl.com/profile/48)
[<img src="https://sponsors.mranftl.com/avatar/49" width="35">](https://sponsors.mranftl.com/profile/49)
[<img src="https://sponsors.mranftl.com/avatar/50" width="35">](https://sponsors.mranftl.com/profile/50)
[<img src="https://sponsors.mranftl.com/avatar/51" width="35">](https://sponsors.mranftl.com/profile/51)
[<img src="https://sponsors.mranftl.com/avatar/52" width="35">](https://sponsors.mranftl.com/profile/52)
[<img src="https://sponsors.mranftl.com/avatar/53" width="35">](https://sponsors.mranftl.com/profile/53)
[<img src="https://sponsors.mranftl.com/avatar/54" width="35">](https://sponsors.mranftl.com/profile/54)
[<img src="https://sponsors.mranftl.com/avatar/55" width="35">](https://sponsors.mranftl.com/profile/55)
[<img src="https://sponsors.mranftl.com/avatar/56" width="35">](https://sponsors.mranftl.com/profile/56)
[<img src="https://sponsors.mranftl.com/avatar/57" width="35">](https://sponsors.mranftl.com/profile/57)
[<img src="https://sponsors.mranftl.com/avatar/58" width="35">](https://sponsors.mranftl.com/profile/58)
[<img src="https://sponsors.mranftl.com/avatar/59" width="35">](https://sponsors.mranftl.com/profile/59)
[<img src="https://sponsors.mranftl.com/avatar/60" width="35">](https://sponsors.mranftl.com/profile/60)
[<img src="https://sponsors.mranftl.com/avatar/61" width="35">](https://sponsors.mranftl.com/profile/61)
[<img src="https://sponsors.mranftl.com/avatar/62" width="35">](https://sponsors.mranftl.com/profile/62)
[<img src="https://sponsors.mranftl.com/avatar/63" width="35">](https://sponsors.mranftl.com/profile/63)
[<img src="https://sponsors.mranftl.com/avatar/64" width="35">](https://sponsors.mranftl.com/profile/64)
[<img src="https://sponsors.mranftl.com/avatar/65" width="35">](https://sponsors.mranftl.com/profile/65)
[<img src="https://sponsors.mranftl.com/avatar/66" width="35">](https://sponsors.mranftl.com/profile/66)
[<img src="https://sponsors.mranftl.com/avatar/67" width="35">](https://sponsors.mranftl.com/profile/67)
[<img src="https://sponsors.mranftl.com/avatar/68" width="35">](https://sponsors.mranftl.com/profile/68)
## ToC
- [google-webfonts-helper ](#google-webfonts-helper---)
- [Current Sponsors](#current-sponsors)
- [ToC](#toc)
- [Give it a try: https://gwfh.mranftl.com](#give-it-a-try-httpsgwfhmranftlcom)
- [Running gwfh on your own server](#running-gwfh-on-your-own-server)
- [Development](#development)
- [Quickstart](#quickstart)
- [Production build](#production-build)
- [JSON API](#json-api)
- [GET `/api/fonts`](#get-apifonts)
- [GET `/api/fonts/[id]?subsets=latin,latin-ext`](#get-apifontsidsubsetslatinlatin-ext)
- [GET `/api/fonts/[id]?download=zip&subsets=latin&formats=woff,woff2&variants=regular`](#get-apifontsiddownloadzipsubsetslatinformatswoffwoff2variantsregular)
- [History](#history)
- [License](#license)
## Give it a try: [https://gwfh.mranftl.com](https://gwfh.mranftl.com)
This service might be handy if you want to host a specific [Google font](https://fonts.google.com/) on your **own** server:
* font style and charset customization
* CSS snippets
* `.eot`, `.woff`, `.woff2`, `.svg`, `.ttf` font file formats download (zipped).
[](https://gwfh.mranftl.com)
## Running gwfh on your own server
I provide prebuilt Docker images via [GitHub Packages](https://github.com/majodev/google-webfonts-helper/pkgs/container/google-webfonts-helper). You can use them as follows:
```bash
# See https://developers.google.com/fonts/docs/developer_api for creating your own API-Key.
docker run -e GOOGLE_FONTS_API_KEY=<YOUR-API-KEY> -p 8080:8080 ghcr.io/majodev/google-webfonts-helper:<TAG>
# Express server listening on 8080, in production mode
```
## Development
### Quickstart
Do this to setup a development environment:
```bash
# Ensure to set the GOOGLE_FONTS_API_KEY env var inside your own gitignored .env file
# See https://developers.google.com/fonts/docs/developer_api for creating your own API-Key.
echo "GOOGLE_FONTS_API_KEY=<YOUR-API-KEY>" > .env
# Start up the development docker container (multistage Dockerfile, stage 1 only)
./docker-helper.sh --up
# [+] Running 1/0
# ⠿ Container gwfh-service-1 Running
# node@3b506a285f7f:/app$
# within this development container:
node$ yarn --pure-lockfile
node$ ./node_modules/.bin/bower install
# start development server
node$ grunt serve
# [...]
# Express server listening on 9000, in development mode
# The application is now available at http://127.0.0.1:9000 (watching for code changes)
# start production server (same command as within the final docker multistage build)
node$ grunt build
node$ NODE_ENV=production node dist/server/app.js
# Express server listening on 8080, in production mode
```
### Production build
If you want to build and run your own **production** container locally:
```bash
# Build the production docker container (final stage)
docker build . -t <your-image-tag>
# Run it (if you have previously started the development container, halt it!)
./docker-helper.sh --halt
docker run -e GOOGLE_FONTS_API_KEY=<YOUR-API-KEY> -p 8080:8080 <your-image-tag>
# Express server listening on 8080, in production mode
```
To mitigate security issues especially with the projects' deprecated dependencies, the final image is based on a minimal container image. It runs rootless and has no development dependencies.
## JSON API
The API is public, feel free to use it directly (rate-limits may apply).
### GET `/api/fonts`
Returns a list of all fonts, sorted by popularity. E.g. `curl https://gwfh.mranftl.com/api/fonts`:
```json
[{
"id": "open-sans",
"family": "Open Sans",
"variants": ["300", "300italic", "regular", "italic", "600", "600italic", "700", "700italic", "800", "800italic"],
"subsets": ["devanagari", "greek", "latin", "cyrillic-ext", "cyrillic", "greek-ext", "vietnamese", "latin-ext"],
"category": "sans-serif",
"version": "v10",
"lastModified": "2014-10-17",
"popularity": 1,
"defSubset": "latin",
"defVariant": "regular"
} [...]
]
```
### GET `/api/fonts/[id]?subsets=latin,latin-ext`
Returns a font with urls to the actual font files google's servers. `subsets` is optional (will serve the `defSubset` if unspecified). E.g. `curl "https://gwfh.mranftl.com/api/fonts/modern-antiqua?subsets=latin,latin-ext"` (the double quotes are important as query parameters may else be stripped!):
```json
{
"id": "modern-antiqua",
"family": "Modern Antiqua",
"variants": [{
"id": "regular",
"eot": "https://fonts.gstatic.com/s/modernantiqua/v6/8qX_tr6Xzy4t9fvZDXPkhzThM-TJeMvVB0dIsYy4U7E.eot",
"fontFamily": "'Modern Antiqua'",
"fontStyle": "normal",
"fontWeight": "400",
"woff": "https://fonts.gstatic.com/s/modernantiqua/v6/8qX_tr6Xzy4t9fvZDXPkh1bbnkJREviNM815YSrb1io.woff",
"local": ["Modern Antiqua Regular", "ModernAntiqua-Regular"],
"ttf": "https://fonts.gstatic.com/s/modernantiqua/v6/8qX_tr6Xzy4t9fvZDXPkhxr_S_FdaWWVbb1LgBbjq4o.ttf",
"svg": "https://fonts.gstatic.com/l/font?kit=8qX_tr6Xzy4t9fvZDXPkh0sAoW0rAsWAgyWthbXBUKs#ModernAntiqua",
"woff2": "https://fonts.gstatic.com/s/modernantiqua/v6/8qX_tr6Xzy4t9fvZDXPkh08GHjg64nS_BBLu6wRo0k8.woff2"
}],
"subsets": ["latin", "latin-ext"],
"category": "display",
"version": "v6",
"lastModified": "2014-08-28",
"popularity": 522,
"defSubset": "latin",
"defVariant": "regular",
"subsetMap": {
"latin": true,
"latin-ext": true
},
"storeID": "latin-ext_latin"
}
```
### GET `/api/fonts/[id]?download=zip&subsets=latin&formats=woff,woff2&variants=regular`
Download a zipped archive with all `.eot`, `.woff`, `.woff2`, `.svg`, `.ttf` files of a specified font. The query parameters `formats` and `variants` are optional (includes everything if no filtering is applied). is E.g. `curl -o fontfiles.zip "https://gwfh.mranftl.com/api/fonts/lato?download=zip&subsets=latin,latin-ext&variants=regular,700&formats=woff"` (the double quotes are important as query parameters may else be stripped!)
## History
> 2025:
* Switch to `node:22` for the final image.
* Adds support for linux/arm64 architecture (patches [imagemin/optipng-bin](https://github.com/imagemin/optipng-bin/pull/128))
> 2024:
* Switch to `node:20` for the final image.
> 2023:
* Project upgraded to be compatible with Node.js v18+.
* Automated prebuilt Docker images via [GitHub Actions](https://github.com/majodev/google-webfonts-helper/actions).
* `/server` was fully refactored/modernized (async/await) and now compiles with TypeScript.
* Switch to `node:18` for the final image.
* `/client` can still be considered very legacy Angular code.
> 2022:
This service was mostly on life-support, most of its code and dependencies can be considered deprecated. The current docker image wrapping `node@v0.10.44` runs rootless and is hopefully enough to keep the bandits out. API attack surface should be minimal anyways.
> 2014:
This service was originally a prototype I've created to get familiar with Angular and Express. All magic by [generator-angular-fullstack](https://github.com/DaftMonk/generator-angular-fullstack). See [my note here](http://mranftl.com/2014/12/23/self-hosting-google-web-fonts/).
Idea originally by Clemens Lang who created an [awesome bash script](https://neverpanic.de/blog/2014/03/19/downloading-google-web-fonts-for-local-hosting/) to download Google fonts in all formats.
## License
(c) Mario Ranftl
[MIT License](http://majodev.mit-license.org/)
[Google Fonts Open Source Font Attribution](https://fonts.google.com/attribution) A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever.
Send release notifications for starred Github repos.
URL to PNG utility featuring parallel rendering using Playwright for screenshots and with storage caching via Local, S3, or CouchDB.