Search engine for open-source SVG icons. Aggregates multiple icon libraries into a single searchable catalog with platform-specific identifiers for iOS, Android, React, Vue, and Svelte.
Live: icons.pureadmin.io
8 aggregated sets, ~39k icons total. Styles use a unified canonical vocabulary (outline, filled, thin, light, regular, bold, rounded, sharp, duotone, color, brands). Native source-library names are preserved per-set in const.icon_set.native_style_names.
| Set | Icons | Styles | Sizes | License |
|---|---|---|---|---|
| FluentUI System Icons | ~5400 | outline, filled, color, light | 16, 20, 24, 28, 32, 48 | MIT |
| Font Awesome Free | ~2850 | filled, outline, brands | scalable | CC BY 4.0 / MIT |
| Heroicons | ~650 | outline, filled | 16, 20, 24 | MIT |
| Lucide | ~1500 | outline | scalable | ISC |
| Material Symbols | ~10800 | filled, outline, rounded, sharp, duotone | scalable | Apache 2.0 |
| Phosphor Icons | ~9000 | thin, light, regular, bold, filled, duotone | scalable | MIT |
| Remix Icon | ~3000 | outline, filled | scalable | Apache 2.0 |
| Tabler Icons | ~5800 | outline, filled | scalable | MIT |
See icons.pureadmin.io/docs/icon-sets for the live list with per-set notes, color methods, and source links.
All endpoints return JSON. No authentication required (except maintenance).
# Search icons
curl 'https://icons.pureadmin.io/api/icons/search?q=calendar&limit=5'
# Filter by icon set and style
curl 'https://icons.pureadmin.io/api/icons/search?q=arrow&set=heroicons&style=outline'
# Get icon detail
curl 'https://icons.pureadmin.io/api/icons/5403'
# List all icon sets
curl 'https://icons.pureadmin.io/api/icon-sets'
# Plain text format (token-efficient for AI)
curl 'https://icons.pureadmin.io/api/icons/search?q=pen&format=text'
Endpoints:
GET /api/icons/search — search with filters (q, set, size, style, limit, format)GET /api/icons/:id — icon detail with filenames, identifiers, categories, phrasesGET /api/icon-sets — all sets with styles, sizes, color methods, icon countGET /api/health — health checkGET /icons/:set/:style/:filename — SVG file servingPOST /api/maintenance/:task — sync, clean, cube (requires X-API-Key)POST /api/maintenance/sync/:icon_set — sync a specific icon setDocs: icons.pureadmin.io/docs/api · llms.txt
The icon detail modal includes a Download Designer for generating customized icon exports:
Ideal for generating app icons, favicons, or presentation-ready icon assets.
{
"mcpServers": {
"icons": {
"command": "npx",
"args": ["-y", "@keenmate/pure-admin-icons-mcp"]
}
}
}
Provides 5 tools: get_usage_guide, search_icons, get_icon_detail, get_icon_svg, list_icon_sets. Source: ../pure-admin-icons-mcp.
lib/pure_admin_icons/sync/adapters/ — one module per icon set implementing the PureAdminIcons.Sync.Adapter behaviour (download, parse, move, cleanup)lib/pure_admin_icons/icon_sets/ — one module per icon set implementing the PureAdminIcons.IconSets.Formatter behaviour (React/Vue/Svelte/CSS class identifiers and packages)lib/database/ — auto-generated by db-gen from PostgreSQL stored procedures (run make db-gen to regenerate)priv/preview_presets.json — single source of truth for built-in color presets, loaded at compile time and shared between server and JS via PresetManagerpriv/static/assets/vendor/ — floating-ui (popovers), JSZip (PNG ZIP export), loaded via <script defer> in root layoutAdding a new icon set requires:
lib/pure_admin_icons/sync/adapters/lib/pure_admin_icons/icon_sets/@adapters / @formatters mapsconst.icon_set (see docs/ for details)See docs/ for internal documentation on presets, Copy/Import CSS, and per-set platform prefs.
UI strings are pulled from public.translation via public.get_group_translations(lang, 'frontend', 'text', tenant) and cached per-locale in :persistent_term. English defaults live in lib/pure_admin_icons/translations.ex as @defaults and are used as fallback when the DB has no row for a key/locale.
t/2 in templatesimport PureAdminIcons.Translations, only: [t: 1, t: 2]
t("common.buttons.copy") # => "Copy"
t("iconSearch.messages.resultsRange",
%{from: 1, to: 30, total: 392}) # => "Showing 1-30 of 392 icons"
[domain].[specifier].[identifier]
common for shared strings (iconSearch, iconSets, iconDetail, docsIndex, apiDocs, mcpDocs, llmsDocs, stats, syncDiscrepancies, nav, common)labels, headers, tableHeaders, placeholders, buttons, tooltips, messages, empty, links, pagination, platforms, periods, filters, cards)iconSet, browseIcons, copyCss)Interpolation uses %{param} placeholders — applied by Translations.interpolate/2 after DB lookup.
Per request, PureAdminIconsWeb.Plugs.Locale picks the locale in this order:
?lang=xx query param (explicit opt-in)Accept-Language header's first acceptable tagdefault_locale from config (default "en")Only whitelisted tags in config :pure_admin_icons, :supported_locales are honored; everything else falls back to default.
LiveView processes inherit the resolved locale via the live_session :default, on_mount: {Plugs.Locale, :default} hook in the router, which reads the value stashed in the session by the plug.
public.translation with (language_code, data_group='frontend', data_object_code=<full key>, context='text', value=<translated>). See ../pure-admin-icons-database/upsert_frontend_translations.sql for the template / canonical source of all frontend translations.SELECT internal.refresh_translation_cache();config :pure_admin_icons, :supported_locales.PureAdminIcons.Translations.DbProvider.refresh().t("domain.specifier.identifier") in your template.@defaults in lib/pure_admin_icons/translations.ex (fallback when DB has no translation).public.translation for en — and any other active language.Use the SP writers (public.create_translation, public.update_translation, public.delete_translation, public.copy_translations) — they handle normalization, auditing, and automatic mat-view refresh. After any out-of-band change, call PureAdminIcons.Translations.DbProvider.refresh() to drop the in-app cache.
mix setup # Install deps, build assets
iex -S mix phx.server # Start dev server at localhost:4020
mix test # Run tests
mix format # Format code
make db-gen # Regenerate DB context from stored procedures
Sources live in assets/js/ (entry: assets/js/app.js) and assets/css/app.css. esbuild bundles JS into priv/static/assets/js/app.js; Tailwind compiles CSS into priv/static/assets/css/app.css. Both build outputs are gitignored — the Dockerfile regenerates them via mix assets.deploy. Vendor files under priv/static/assets/vendor/ (floating-ui, jszip) and priv/static/assets/default.css are checked in.
The dev bundle is large (1.3MB for 939KB, app.js) because esbuild emits an inline base64 source map (71% of the file) and ships Phoenix + LiveView unminified (382KB):
| Component | Approx. size (dev, unminified) |
|---|---|
| Inline source map (base64) | ~939KB |
phoenix_live_view |
~225KB |
phoenix |
~52KB |
| App + theme + lang switcher + topbar + esbuild shim | ~105KB |
Production (mix assets.deploy) minifies, writes the source map to a separate .js.map file, and mix phx.digest gzips — end users download roughly 110KB minified, ~35KB gzipped.
docker build -t pure-admin-icons:latest .
docker run -p 8888:8888 \
-e SECRET_KEY_BASE=$(mix phx.gen.secret) \
-e DB_USERNAME=... -e DB_PASSWORD=... \
-e DB_HOSTNAME=... -e DB_DATABASE=pure_admin_icons \
-e PHX_HOST=icons.pureadmin.io \
pure-admin-icons:latest
This app sits behind Traefik in production. Two non-obvious requirements — without these, LiveView falls back to long-polling (slow back-nav, occasional page reloads):
1. The websecure entrypoint must allow encoded characters in the request URL.
Phoenix LiveView's WebSocket connect URL embeds asset URLs in the _track_static query parameter (e.g. _track_static[0]=https%3A%2F%2F...), which contains %2F. Traefik 3.6.4+ rejects requests with %2F, %5C, %23, %3F, %3B, %00 in the URL by default (see Traefik docs). The WS handshake silently 400s → browser falls back to long-poll.
In traefik.yml:
entryPoints:
websecure:
address: ":443"
http:
encodedCharacters:
allowEncodedBackSlash: true
allowEncodedNullCharacter: true
allowEncodedQuestionMark: true
allowEncodedSemicolon: true
allowEncodedSlash: true
allowEncodedHash: true
2. The icons router only listens on the websecure entrypoint (HTTPS-only at the proxy layer). Because of this, Phoenix force_ssl is intentionally not configured — Traefik already enforces HTTPS, and force_ssl would 301-loop WebSocket Upgrade requests when Traefik doesn't forward X-Forwarded-Proto for them. Phoenix uses Plug.RewriteOn instead (in lib/pure_admin_icons_web/endpoint.ex) to trust the proxy headers for URL generation without redirecting.
Minimal Traefik labels for the icons service:
labels:
- traefik.enable=true
- traefik.http.routers.pureadmin-io-icons.rule=Host(`icons.pureadmin.io`)
- traefik.http.routers.pureadmin-io-icons.entrypoints=websecure
- traefik.http.routers.pureadmin-io-icons.tls=true
- traefik.http.routers.pureadmin-io-icons.tls.certresolver=letsencrypt
- traefik.http.routers.pureadmin-io-icons.service=pureadmin-io-icons
- traefik.http.services.pureadmin-io-icons.loadbalancer.server.port=4000
(PORT=4000 env var on the container makes Phoenix listen on :4000 to match the loadbalancer port.)
Application code: MIT. Icon SVGs retain their original licenses (see icon set table above).
Built by KeenMate.