CLI tool
onnx/tensorflow-onnx avatar
onnx/tensorflow-onnx

tf2onnx: converting TensorFlow, Keras, TFLite and TF.js graphs to ONNX

Convert TensorFlow, Keras, Tensorflow.js and Tflite models to ONNX

2,556 stars473 forksJupyter NotebookApache-2.0

At a glance

What is it?
tf2onnx is the ONNX project's own converter for moving TensorFlow family models into the ONNX format. It works well when you have a saved_model and a modern opset, and it is a poor fit when your graph depends on TensorFlow ops that have no ONNX mapping.
Who is it for?
Adopt tf2onnx if you have a TensorFlow 2.x saved_model and need an ONNX file at opset 14 to 18, since the converter handles inputs and outputs automatically in that case. Do not adopt it if your graph relies on TensorFlow ops outside the support_status.md mapping, if you are converting TF.js in production, or if you need a maintained project, because the README currently carries a maintainer-wanted notice.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly Jupyter Notebook, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap tf2onnx fills between two graph formats

TensorFlow and ONNX describe computation graphs with different operator sets, different attribute conventions and different layout expectations. A model trained and exported in TensorFlow cannot simply be renamed into an .onnx file. tf2onnx reads a TensorFlow graph, walks its nodes, and emits an equivalent ONNX graph using the operator mappings the project maintains. The README is direct about the consequence: TensorFlow has many more ops than ONNX, so some models will not map cleanly. The intended user is an engineer who has a trained TensorFlow, Keras, TFLite or TensorFlow.js artifact and needs it to run somewhere that consumes ONNX, most commonly ONNX Runtime, which the README lists as an optional install for running tests. This is not a training tool and it is not a runtime. It is a one-way translator, and the direction is fixed.

What the converter accepts and how the graph is rebuilt

The CLI takes one source selector at a time: --saved-model, --checkpoint, --graphdef (also spelled --input), --tflite, or --tfjs. For saved_model, tflite and tfjs inputs, inputs and outputs do not need to be specified, which means the converter can infer the graph boundary itself. For checkpoint and graphdef, you must supply --inputs and --outputs, with names that typically end in :0, for example --inputs input0:0,input1:0. The README points at TensorFlow's summarize_graph utility when you do not know those node names, and notes that summarize_graph must be downloaded and built from source. That is a real friction point: the easy path is saved_model, and the README says so outright, recommending you obtain the model in saved_model format from the provider if that option exists. The conversion itself is a graph rewrite plus an opset selection step, and the output opset is a parameter of that rewrite rather than something inferred from the source model.

Install and the first conversion command

Installation is a single pip command: pip install -U tf2onnx. The README also documents installing from GitHub with pip install git+https://github.com/onnx/tensorflow-onnx and building from a clone with python setup.py install or python setup.py develop. TensorFlow is not bundled: you install it separately, for example pip install tensorflow. The basic conversion is python -m tf2onnx.convert --saved-model tensorflow-model-path --output model.onnx. The default output opset is 15, and the README states the project supports and tests opset 14 through 18, with opset 6 through 13 expected to work but untested. To pin a different opset, add the flag: python -m tf2onnx.convert --saved-model tensorflow-model-path --opset 18 --output model.onnx. The CLI reference lists additional switches worth knowing about before you file a bug: --dequantize, --tag, --signature_def, --concrete_function, --target, --extra_opset, --custom-ops, --load_op_libraries, --large_model, --continue_on_error, --verbose, --output_frozen_graph, and the layout flags --inputs-as-nchw and --outputs-as-nchw. The presence of --continue_on_error and --verbose together suggests the expected workflow when a graph partially fails: get the full log, then decide whether to patch the graph or the mapping.

Version matrix and the shape problem on non saved_model inputs

The README's support table lists Python 3.10 to 3.12, TensorFlow 2.13 to 2.15, and ONNX opset 14 to 18 for both the basic and full unit test configurations on Linux and Windows. TensorFlow 1.x is not supported; the project targets tf-2.x and tests on tf-2.13 or better. ONNX itself is not pinned: tf2onnx uses whatever ONNX version is installed and installs the latest if none is present, requiring onnx-1.9 or better. That is a moving dependency, and it means two machines with the same tf2onnx version can produce different graphs if their ONNX versions differ. A second, more concrete constraint appears in the --inputs documentation: some models declare placeholders with unknown rank and dims, and those cannot be mapped to ONNX. The workaround is to append the shape after the input name inside brackets, for example --inputs X:0[1,28,28,3], with -1 marking an unknown dimension. If you are converting a graphdef or checkpoint, budget time for this step. If you have a saved_model, you likely will not touch it.

TFLite and TF.js: two different levels of confidence

TFLite conversion is a first-class CLI path: python -m tf2onnx.convert --opset 16 --tflite tflite-file --output model.onnx. Note that the example pins opset 16 rather than relying on the default, and that inputs and outputs are inferred. TensorFlow.js is labelled experimental in the README, with the caveat that while the team tested it against many tfjs models from tfhub, not all models may convert correctly. Those two statements are not equivalent. TFLite is presented as supported functionality with a documented flag; TF.js is presented as a best-effort path. If your pipeline depends on TF.js conversion, treat every model as a separate verification task rather than assuming the converter's coverage carries over from the TFLite path. The troubleshooting guide and the support_status.md op table are the two documents to consult when a conversion fails, and the README points to both.

The maintainer notice is the most important line in the README

The README opens with a maintainer-wanted notice asking for someone to help support and evolve the project, with interest expressed by opening an issue. That sits above everything else in the document and it changes how you should read the rest. A converter is only as useful as its operator coverage stays current, and TensorFlow keeps adding ops. The release history shows v1.16.1 in January 2024, then v1.17.0rc1 in February 2026 and v1.17.0 in March 2026, so there was a long gap between the 1.16 line and the 1.17 line. The project is not archived, and the last push is recent, but a project actively soliciting a maintainer is telling you that the bus factor is the thing to evaluate, not the feature list. For a one-off conversion this matters little. For a build pipeline that must keep converting new models for years, it matters a great deal.

When to reach for something else

The clearest alternative is to keep the model in its native framework and serve it with TensorFlow Serving or the TensorFlow runtime rather than converting at all. The difference in approach is fundamental: conversion produces a static ONNX graph that you then optimize and deploy against ONNX Runtime, while native serving keeps the original graph, its custom ops and its TensorFlow-specific kernels intact. If your model uses ops that appear in support_status.md as unmapped, or relies on custom ops that would require --custom-ops and --load_op_libraries to resolve, native serving avoids the mapping problem entirely at the cost of running a heavier runtime. The same logic applies if you need a TensorFlow.js model in production today: the README's experimental label means the conversion path is not the dependable one, and shipping the TF.js model in a browser or Node runtime sidesteps the uncertainty. Conversion is worth it when the ONNX runtime target is fixed by your deployment environment, not when it is merely convenient.

Licence, maintenance and what to check before you commit

The repository is Apache-2.0, the same licence family as the ONNX project itself, and the source headers carry the SPDX identifier Apache-2.0. That is permissive and standard for this ecosystem, but it is not legal advice and your own distribution obligations depend on how you ship the converted model and the runtime. On maintenance cost, the practical items are: the ONNX version floats with your environment, so pin it if you need reproducible graphs; the tested matrix is Python 3.10 to 3.12 against TensorFlow 2.13 to 2.15, so a TensorFlow upgrade outside that window is untested territory; and the maintainer search means you should be prepared to carry a fork or pin a known-good version if the project stalls. Before adopting, run the conversion on your actual model and inspect the resulting graph rather than trusting the op table, check that your target opset falls in 14 to 18, and confirm whether your model can be obtained as a saved_model, since that single fact determines whether you spend the afternoon on shape annotations or on something else.

Editorial conclusion

Adopt tf2onnx if you have a TensorFlow 2.x saved_model and need an ONNX file at opset 14 to 18, since the converter handles inputs and outputs automatically in that case. Do not adopt it if your graph relies on TensorFlow ops outside the support_status.md mapping, if you are converting TF.js in production, or if you need a maintained project, because the README currently carries a maintainer-wanted notice. Verify first that your model loads as a saved_model rather than a bare graphdef, that your target opset is in the tested 14 to 18 range, and that every op in your graph appears in the support table.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. onnx/tensorflow-onnx on GitHub
  4. README
  5. Releases
Community notes

Community notes