Skip to content
GlyphPact
Menu

GlyphPact v1.0.1

Your icon codepoints should never move.

Compile a directory of SVG files into a validated OpenType font, a committed codepoint lock, and const Flutter IconData. Existing icons keep their codepoints when the pack changes.

uv tool install glyphpact

Installs the latest stable release from PyPI, currently v1.0.1. Already installed? Run uv tool upgrade glyphpact. Release notes.

  • Python 3.10+
  • Runs locally
  • MIT licensed
  • No Node toolchain
The glyph for arrows/back.svg on its em square, bound to codepoint U+E000 A left-pointing arrow drawn as a filled outline inside a measured em square, with registration marks at the corners and control points on the outline's vertices. A plate below the square reads U+E000 and marks the assignment as locked.arrows/back.svgU+E000LOCKED

What it produces

A universal core with a Flutter binding on top

Worth being precise about, because it decides whether GlyphPact fits your project. Two of the three outputs are framework-neutral. The third is Dart-specific, and there is no generated binding for any other platform.

Universal artifacts

Usable from any language

  • A validated OpenType/CFF font, reopened and checked after build against cmap, bounds, metrics, checksums, and required tables
  • iconfont.lock.json, a documented JSON registry mapping every source file to its permanent codepoint
  • A deterministic machine-readable build report
Flutter binding

Dart-specific, and only Dart

  • A const IconData provider class, annotated@staticIconProvider
  • Dart-shaped flags: --class-name,--font-package
  • No generated CSS, WOFF2, React, Android, or JavaScript output

So can you use it outside Flutter? The font and the lock file, yes. The high-level binding, no. A non-Flutter project reads codepoints from the lock file and writes its own integration layer. Turning the font into a web icon set means converting it and authoring the CSS yourself, because GlyphPact does neither.

The problem

Three ways generated icon fonts go wrong quietly

None of these fail loudly at build time. That is what makes them expensive: each one ships.

DriftCodepoints shift

Add an icon whose filename sorts early and every later assignment moves up one. The font is still internally consistent, so nothing errors. Your existing AppIcons.back constant now points at whatever landed on that codepoint instead, and renders a different picture.

SilenceFidelity loss goes unreported

An icon font glyph holds monochrome coverage, not RGB and not partial alpha. A generator without a stated policy can flatten a half-transparent fill or ignore a blur, and simply not mention it. You find out from a design review, not from the build.

StalenessCommitted output stops matching its sources

Someone edits an SVG and forgets to regenerate, or regenerates and forgets to commit. The generated font and Dart file are now a snapshot of an older pack, and no test in the repository can tell.

The answer

The lock file is the codepoint ABI

GlyphPact writes iconfont.lock.json into the output tree and reads it back on every later build. An assignment already recorded there is reused verbatim. Only genuinely new sources draw new codepoints.

iconfont.lock.jsongit diff, after adding four icons
--- a/lib/generated/app_icons/iconfont.lock.json+++ b/lib/generated/app_icons/iconfont.lock.json@@ -9,6 +9,30 @@   "unitsPerEm": 1000,   "glyphs": [     {+      "source": "actions/add.svg",+      "name": "actionsAdd",+      "codepoint": "0xE002",+      "sourceSha256": "a169819fd451...",+      "matchTextDirection": false,+      "geometrySha256": "2025c5f8dff2..."+    },+    {+      "source": "arrows/arrow_down.svg",+      "name": "arrowsArrowDown",+      "codepoint": "0xE004",+      "sourceSha256": "b6e87581e2f2...",+      "matchTextDirection": false,+      "geometrySha256": "48a927141186..."+    },+    {       "source": "arrows/back.svg",       "name": "back",       "codepoint": "0xE000",       "sourceSha256": "38121f4ed8a1...",       "matchTextDirection": true,     },     {       "source": "status/verified.svg",       "name": "verified",       "codepoint": "0xE001",     },+    {+      "source": "status/warning.svg",+      "name": "statusWarning",+      "codepoint": "0xE005",+    }   ],   "retired": []
+ addedunchanged assignment
A real diff after adding four icons to a two-icon pack. actions/add.svg now sorts before everything else, yet it took 0xE002 rather than 0xE000, and arrows/back.svg kept the 0xE000 it already had. Every line in this diff is an addition: not one existing assignment was modified.

What the lock guarantees

  • Adding or reordering source files never changes existing codepoints.
  • Removing an icon leaves a tombstone in retired rather than freeing its codepoint for reuse.
  • Restoring a removed icon reactivates its original slot.
  • A unique content-preserving rename keeps both its codepoint and its generated Dart name.
  • Identical inputs, config, lock, and compiler version produce byte-identical artifacts, across any worker count.

Tombstones are permanent on purpose. Deleteactions/archive.svg and its codepoint0xE003 moves toretired, leaving a gap in the active sequence. Recycling it would be the exact bug the lock exists to prevent: a codepoint that already shipped, pointing at a new picture.

Why codepoints move in the first place, in detail

The workflow

Three commands, all of them local

  1. Install the compiler

    A single Python command-line tool. There is no website step, no project file to keep in a browser, and no server that receives your artwork.

    uv tool install glyphpact
  2. Compile the pack

    Sources are discovered recursively and compiled with bounded worker processes. GlyphPact owns the output directory and refuses to overwrite a non-empty directory that lacks its ownership marker.

    bash
    glyphpact assets/icons \
      --output lib/generated/app_icons \
      --name AppIcons
  3. Commit the output, then let CI defend it

    Commit the font, the lock, the report, the attribution file, and the generated Dart. Then have CI rebuild a candidate set and compare, without rewriting anything. Exit code3 means the committed artifacts are stale.

    bashexit 3 = stale
    glyphpact --config icon_font.json --check
    Exit codes
    CodeMeaning
    0Build or check succeeded
    1Unexpected internal failure
    2Config, input, policy, geometry, or font contract failed
    3--check found stale committed output

Generated output

Everything it writes, and who owns it

The whole output is plain files you can read, diff, and review. Nothing is hidden in a cache or a binary project format.

lib/generated/app_icons/
fonts/AppIcons.otfframework-neutral Validated OpenType/CFF font. Framework-neutral.
iconfont.lock.jsonframework-neutral Codepoint registry and provenance. Schema version 1. Commit this.
app_icons.dartFlutter only const Flutter IconData provider. Dart-specific.
layer_fonts/layer_*.otfframework-neutral Optional solid-alpha paint-order layers, for icons that opt in.
iconfont.report.jsonbuild metadata Deterministic build report. Schema version 2.
ATTRIBUTION.mdbuild metadata Declared artwork authorship, licences, and unattributed count.
.glyphpact.jsonbuild metadata Ownership marker. GlyphPact refuses to overwrite a directory without it.
../.app_icons.glyphpact.lockPublication coordination file. A sibling of the output directory, not part of the owned tree. Leave it in place and ignore it in version control.

Fidelity policy

Unsupported SVG is classified, not guessed

A normal icon-font glyph stores monochrome alpha coverage, and Flutter supplies the colour at runtime. So source RGB is irrelevant, but surviving partial transparency and features like filters genuinely have nowhere to go. GlyphPact decides that with two independent policies rather than a heuristic.

Both axes default to error. Each axis is independent, so the four combinations are all reachable.
--unrepresentable error--unrepresentable skip
--lossy errorLossless onlydefaultAnything that would be approximated or omitted fails the build instead. This is the default.Omissions allowedSources outside the profile are dropped and reported. Anything that would be approximated still fails.
--lossy convertDocumented approximations allowedDeterministic approximations are emitted and reported. Sources with no faithful conversion still fail.Approximations and omissions allowedBoth are permitted, and every one is reported as a typed issue.

The same three icons, under both policies

back.svg is a plain filled path. ghost.svgcarries an opacity=".5" that survives to the final coverage.blurred.svg applies an feGaussianBlur, which a monochrome outline cannot represent at all.

exit 2Default: strict

glyphpact icons -o out -n AppIcons
[SVG_ATTRIBUTE_UNREPRESENTABLE] blurred.svg: Attribute 'filter' has no supported static monochrome conversion.
[SVG_ELEMENT_UNREPRESENTABLE] blurred.svg: <filter> has no supported static monochrome outline conversion.
[SVG_PARTIAL_ALPHA_APPROXIMATED] ghost.svg: Surviving fractional alpha was flattened to opaque coverage.

Nothing is published. The previous valid output is left untouched.

exit 0Explicitly permissive

... --lossy convert --unrepresentable skip
Built 2 of 3 discovered icon(s): 1 lossless, 1 approximated, 1 skipped.
[SVG_ATTRIBUTE_UNREPRESENTABLE] blurred.svg: Attribute 'filter' has no supported static monochrome conversion. (unrepresentable, skipped)
[SVG_PARTIAL_ALPHA_APPROXIMATED] ghost.svg: Surviving fractional alpha was flattened to opaque coverage. (lossy, converted)

Every deviation is a typed record in the JSON result and the report, with a code, a classification, an action, and the feature responsible.

--unrepresentable skip is not an error-suppression switch. These stay fatal under every policy: scripts and event handlers, external references, malformed or non-finite values, unknown semantics, exhausted work limits, compiler or font-contract failures. And if every source would be skipped, the build fails and publishes nothing rather than shipping an empty font. A failed build never replaces the last valid output.

The complete versioned SVG profile documents every classification.

Flutter

The generated Dart is ordinary, const, and reviewable

Register the font

Asset paths are relative to pubspec.yaml. This step is yours: GlyphPact writes the font, it does not edit your pubspec.

pubspec.yaml
flutter:
  fonts:
    - family: AppIcons
      fonts:
        - asset: lib/generated/app_icons/fonts/AppIcons.otf

Use the constants

Generated IconData carries no label, so supply a localized semanticLabel, or exclude decorative icons from semantics.

dart
import 'package:my_app/generated/app_icons/app_icons.dart';

Icon(
  AppIcons.back,
  semanticLabel: 'Back',
)

What the generator actually emits

lib/generated/app_icons/app_icons.dart
// GENERATED CODE - DO NOT MODIFY BY HAND.
// Generated by GlyphPact 1.0.1.

import 'package:flutter/widgets.dart' as flutter;

@flutter.staticIconProvider
abstract final class AppIcons {
  static const String _fontFamily = 'AppIcons';
  static const String? _fontPackage = null;

  /// Source: arrows/back.svg
  /// License: MIT
  static const flutter.IconData back = flutter.IconData(
    0xE000,
    fontFamily: _fontFamily,
    fontPackage: _fontPackage,
    matchTextDirection: true,
  );
}

Font packages, layered icons, tree shaking, and CI for Flutter projects

Agent workflows

Claude Code and Codex can drive it directly

An optional plugin bundles the release wheel and starts a local stdio MCP server. No source checkout and no global install, and the audit and report tools are annotated read-only.

read onlyaudit_icon_pack

Audit a file or directory once, then page a stable local snapshot of the findings instead of recompiling.

writesbuild_icon_font

Build the output declared by a checked-in config.

writescheck_icon_font

Check committed output for staleness without rewriting generated artifacts.

read onlyread_icon_report

Page through large machine-readable reports.

Claude Code
claude plugin marketplace add omar-hanafy/glyphpact
claude plugin install glyphpact@glyphpact
Codex
codex plugin marketplace add omar-hanafy/glyphpact
codex plugin add glyphpact@glyphpact

MCP installation, tools, and safety boundaries

Alternatives

Where GlyphPact fits, and where it does not

GlyphPact is narrow on purpose. It has no icon browser, no bundled icon packs, and no web output. If you want a web icon font with a stylesheet today, three of the tools below already do that and it does not.

Capabilities relevant to a repository-driven icon pipeline.
GlyphPactLocal CLI compilerIcoMoonBrowser appFlutterIcon.comHosted Fontello forkicon_font_generatorDart CLIFantasticonNode CLI
Existing codepoints survive a pack changeIf an assignment moves, shipped application code renders the wrong glyph.Supported: Committed lock file`iconfont.lock.json` is generated into the output tree and read on every later build. Removed icons leave a tombstone in `retired` instead of freeing the codepoint.Conditional: If you re-import selection.jsonThe FAQ states the codes of previously selected glyphs will not change when the previous `selection.json` is imported, and that "newly imported SVGs do not have any codes assigned to them", so re-importing and reselecting everything "would most likely result in different codes". Stability depends on keeping that file and importing it every time.Conditional: If you re-import config.jsonInherits the Fontello session model, where the downloaded `config.json` records each glyph code and can be imported again. Stability depends on retaining and re-importing that file.Not documented: No documented mechanismNeither the pub.dev page nor the README documents a lock file or persistence mechanism. Published examples show codepoints running sequentially from `0xe000` in glyph order.Manual: Hand-pinned in configA `codepoints` map in the config assigns fixed values, so stability is achievable. The map is maintained by hand rather than generated and updated by the tool.
CI can prove committed output is currentGenerated artifacts drift from sources silently unless something fails the build.Supported: `--check` exits 3 when staleRebuilds a candidate artifact set and compares it without rewriting the owned output tree. Verified: exit 0 on current output, exit 3 after a source edit.Not supported: No CLI to run in CIThe documentation describes a browser application, with no command-line interface.Not supported: No first-party staleness checkThe third-party `fontello-cli` can install a config against a host, but neither it nor the site offers a check that fails on stale committed output.Not supported: Not offeredThe CLI generates output. No check or verify mode is documented.Not supported: Not offeredNo check or verify mode is documented.
Runs entirely on your machineUnreleased or licensed artwork often cannot leave the building.Supported: Local CLI, no network stepA Python command-line compiler. No website step, no project service, no server that receives artwork.Supported: In-browser, not uploadedThe FAQ states that "when you import SVG files or when you generate a font, everything happens in your browser", and that SVGs are not uploaded unless you opt into paid project storage. It also documents working offline after preloading the generate pages.Not supported: Hosted service sessionA hosted web application in the Fontello lineage, where the font configuration is posted to the service and held in a server-side session.Supported: Local CLIRuns locally. Described as written fully in Dart with no external dependency.Supported: Local CLIRuns locally on Node.
Byte-identical rebuildsNon-reproducible artifacts turn every rebuild into an unreviewable diff.Supported: Same bytes across worker countsSame inputs, config, lock, and compiler version produce byte-identical artifacts. Verified: identical SHA-256 across default, 1, and 4 worker processes.Not documented: Not documentedNot documented: Not documentedNot documented: Not documentedNot documented: Not documented
Explicit policy for SVG a font cannot holdAn icon font stores monochrome coverage. Something has to decide what happens to a blur or a half-transparent fill.Supported: Two policy axes, both strict by default`lossy` and `unrepresentable` each default to `error`. Approximations and omissions require explicit opt-in and are reported as typed, coded issues.Not documented: No published policyNot documented: No published policyNot documented: No published policyExposes `--[no-]normalize` and `--[no-]ignore-shapes` flags, but documents no classification of unsupported input.Not documented: No published policy
Generates a Flutter IconData APIHand-written IconData constants are the place codepoint drift actually bites.Supported: Const class, tree-shakeableEmits a `@staticIconProvider` class of `const IconData` values, with `fontPackage` handling and optional layered-icon widgets.Not supported: No Dart outputDocumented export targets are Font, SVG, SVG sprite, Elm, React, Vue, Tiles, PNG, Favicon, and CSH.Supported: Dart icon classPurpose-built for Flutter: exports a font plus a Dart icon class.Supported: Dart icon classGenerates an OTF font and a Flutter-compatible class.Not supported: Web asset types onlyGenerates CSS, SCSS, SASS, HTML, JSON, and TypeScript. No Dart or Flutter output.
Web assets: WOFF2 and CSSThe honest place GlyphPact is behind. If you need a web icon font today, these tools already do it.Not supported: OpenType/CFF onlyEmits a `.otf` font and the lock registry. No WOFF, no WOFF2, no generated CSS. Web use means converting the font and writing the CSS yourself.Supported: Fonts plus CSSDocumented export targets include a web font pack and further asset types.Supported: Web font packFontello lineage produces web font formats alongside the Flutter output.Not supported: OTF onlySupported: WOFF2, WOFF, TTF, EOT, SVG, CSSDocumented font formats are EOT, WOFF2, WOFF, TTF, and SVG, with CSS, SCSS, SASS, HTML, JSON, and TS asset types.
Generates an attribution recordThird-party icon licences usually require attribution, and audits ask for it.Supported: `ATTRIBUTION.md` per buildCarries declared author, licence, and source URL per glyph, and counts emitted icons with no declared provenance. It does not verify ownership.Not documented: Not documentedConditional: Bundled-pack licencesShips licence information for the open-source packs it bundles. Not an attribution file generated from your own artwork metadata.Not supported: Not offeredNot supported: Not offered
Required runtimeA toolchain you do not already run is a toolchain you have to maintain.Supported: Python 3.10+Dart 3+ only to consume the generated provider.Supported: A browserSupported: A browserOptional third-party CLI needs Node.Supported: DartSupported: Node 16+
Coding-agent integrationIcon work is increasingly delegated, and an agent needs typed results rather than screenshots.Supported: Claude Code and Codex pluginA local stdio MCP server exposes `audit_icon_pack`, `build_icon_font`, `check_icon_font`, and `read_icon_report`, plus the config, report, and CLI-result schemas as resources.Not supported: Not offeredNot supported: Not offeredNot supported: Not offeredNot supported: Not offered
Verified on 2026-07-25.Checked against current first-party documentation and, for GlyphPact, against observed output. Sources:GlyphPact README, SVG profile; IcoMoon IcoMoon docs, IcoMoon FAQ; FlutterIcon.com FlutterIcon, polyicon source, Fontello API; icon_font_generator pub.dev page, Source; Fantasticon README.Behaviour changes. If a cell has gone out of date, it is worth reporting.

FAQ

Common questions

Why do icon-font codepoints change when icons are added?

Most icon-font generators assign codepoints by walking the current set of source files and handing out values in order, starting from the beginning of a private use area such as U+E000. The assignment is a function of the file list, not a stored decision. Insert an icon whose name sorts early, rename a file, or reorder a selection, and every icon after that point shifts by one.

The font itself is then internally consistent, so nothing fails at build time. The breakage appears in application code that still refers to the old value: a constant for a back arrow keeps pointing at U+E001 while U+E001 now contains a different glyph. The icon renders, it is simply the wrong picture.

How does GlyphPact keep existing codepoints stable?

GlyphPact writes a lock file, iconfont.lock.json, into the generated output directory and reads it back on every later build. Each entry records the source path, the Dart name, the assigned codepoint, and content hashes. An assignment already in the lock is reused as-is; only genuinely new sources draw new codepoints from the end of the allocated range.

Removing an icon does not free its codepoint. The entry moves to a "retired" list in the same file, leaving a permanent tombstone so the value is never handed to a different picture. Restoring the source later reactivates the original slot, and a unique content-preserving rename keeps both its codepoint and its generated Dart name.

How the lock file works

Should iconfont.lock.json be committed to Git?

Yes. The lock file is the codepoint ABI for the generated font, and it only protects existing assignments if the next build can read the same file the previous build wrote. Committing it alongside the generated font, Dart provider, report, and attribution file is the intended workflow.

If the lock is deleted or lost, the next build has no record of prior assignments and will allocate codepoints from scratch, which can reassign icons even when no source file changed.

How can CI detect stale generated icon output?

Run the same GlyphPact build with the --check flag: glyphpact --config icon_font.json --check. It rebuilds a candidate artifact set from the current sources and compares it against the committed output without rewriting the owned output directory.

Exit code 0 means the committed artifacts are current, and exit code 3 means they are stale, which fails the job. Adding --json produces a stable machine-readable result at schema version 2 so a workflow can inspect glyph counts, the active policy, and typed issues rather than treating every successful run as equivalent.

CI setup for Flutter projects

Does GlyphPact work offline?

Yes. GlyphPact is a command-line compiler that runs locally. Compiling a pack involves no network access: there is no website step, no project service, and no server that receives your artwork. Installation itself needs the network once, to fetch the package.

Does GlyphPact upload my SVG files anywhere?

No. Source SVGs are read from disk and all compilation happens in the local process. Nothing is transmitted, so unreleased or licence-restricted artwork never leaves the machine or the build runner.

Can I use the generated font outside Flutter?

Partly, and the boundary is worth stating precisely. Two of the three outputs are framework-neutral: the compiled OpenType/CFF font is a standard .otf file that any OpenType consumer can use, and iconfont.lock.json is a documented JSON registry mapping each source file to its codepoint, which any language can read.

The third output is not neutral. The generated high-level binding is a Dart file containing const Flutter IconData constants, and the flags that shape it are Dart-specific. There is no generated binding for any other platform.

So a non-Flutter project can consume the font and read codepoints from the lock file, but it will be writing its own integration layer. GlyphPact does not currently ship a ready-made web, React, Android, or JavaScript integration.

Does GlyphPact generate CSS or WOFF2?

No. GlyphPact emits an OpenType/CFF .otf font, the lock registry, a machine-readable report, an attribution file, and a Dart provider. It does not produce WOFF, WOFF2, or any stylesheet, and it has no browser integration.

Using the font on the web is possible but manual: convert the .otf to WOFF2 with a separate tool and write the @font-face rule and class names yourself. Codepoints can be read from the lock file. If a turnkey web icon-font pipeline is what you need, a web-first generator is the better fit today.

What happens when an SVG contains features an icon font cannot represent?

The source is classified rather than silently approximated. An icon-font glyph stores monochrome alpha coverage, so features like a Gaussian blur filter or surviving fractional transparency have no faithful representation in it.

GlyphPact sorts each source into one of three outcomes. Lossless sources are normalized and emitted automatically. Deterministically approximable sources are emitted only when the lossy policy is set to convert, and are reported as a typed lossy issue. Sources with no faithful conversion are omitted only when the unrepresentable policy is set to skip, and are reported as a typed unrepresentable issue.

Both policies default to error, so by default an affected build fails and prints the diagnostic code, the file, and the feature instead of shipping a wrong-looking icon. Malformed, unsafe, or unknown input, and exhausted resource limits, always fail regardless of policy.

The versioned SVG profile

Is GlyphPact an IcoMoon alternative?

For a repository-driven workflow, yes. For a general web icon workflow, not entirely.

GlyphPact overlaps with IcoMoon in turning SVG files into an icon font, and it adds things a browser app cannot: a committed lock file that fixes codepoints, a --check mode that fails CI when generated output goes stale, byte-identical rebuilds, and a generated Flutter IconData API.

IcoMoon does things GlyphPact does not. It exports web font formats and CSS along with React, Vue, and Elm components, it offers a visual editor for browsing and editing glyphs, and it processes artwork in the browser without uploading it. If the goal is a web icon font with a stylesheet, IcoMoon covers ground GlyphPact currently does not.

Full IcoMoon comparison

Can Claude Code or Codex drive GlyphPact directly?

Yes. The repository ships an optional plugin for Claude Code and Codex that bundles the release wheel and starts a local stdio MCP server, without needing a source checkout or a global install.

It exposes four tools: audit an SVG file or directory and page a stable local snapshot of the findings, build the output declared by a checked-in config, check committed output for staleness without rewriting artifacts, and page through large machine-readable reports. The config, report, and CLI-result JSON schemas are exposed as MCP resources so an agent can validate what it reads. The audit and report tools are annotated read-only, and the server runs locally over stdio.

Plugin guide