An unofficial Python wrapper for the Figma REST API.
Every endpoint and every response model is generated from Figma's official OpenAPI spec, so the wrapper tracks the API instead of chasing it. All 54 operations are covered, sync and async, with type hints.
import figmapy
figma = figmapy.Figma(token="figd_...") # or set FIGMA_TOKEN
file = figma.get_file("aBc123XyZ")
for text in figmapy.find_all(file.document, type="TEXT"):
print(text.characters)pip install FigmaPy
Python 3.10+. Depends on httpx and pydantic.
A personal access token from Figma account settings is enough for most scripts:
figma = figmapy.Figma("figd_...")
figma = figmapy.Figma() # reads FIGMA_TOKEN from the environment
figma = figmapy.Figma(oauth_token, oauth2=True) # OAuth2 access token insteadMethod names come straight from the spec's operation ids, converted to snake_case:
get_file, get_file_nodes, get_images, get_team_projects, post_comment,
get_local_variables, get_dev_resources, and so on. Your editor will autocomplete
them, and each one carries the endpoint's own documentation as a docstring.
figma.get_file_nodes("aBc123XyZ", ids=["1:2", "1:3"])
figma.get_images("aBc123XyZ", ids=["1:2"], format="svg", scale=2)
figma.post_comment("aBc123XyZ", message="ship it")
figma.get_team_projects("1234567890")List arguments are joined with commas for you. Arguments you leave out are not sent.
The async client is the same surface with await in front of it. It is generated from
the same template as the sync one, so the two can never drift apart.
import asyncio, figmapy
async def main():
async with figmapy.AsyncFigma() as figma:
keys = ["aBc", "dEf", "gHi"]
files = await asyncio.gather(*(figma.get_file(k) for k in keys))
return [f.name for f in files]
asyncio.run(main())Use it when you are fetching many files at once. For a single request it buys nothing —
use Figma.
Figma files are deeply nested trees. These helpers are plain functions, so they keep working across regenerations:
from figmapy import file_key_from_url, find, find_all, pages, page, walk, image_urls
key = file_key_from_url("https://www.figma.com/design/aBc123XyZ/My-File?node-id=1-2")
file = figma.get_file(key)
pages(file) # the CANVAS nodes
page(file, "Icons") # one page by name
find(file.document, name="Button/Primary") # first match, or None
find_all(file.document, type="COMPONENT") # every match
find_all(file.document, where=lambda n: n.name.startswith("btn_"))
for node in walk(file.document): # everything, depth first
print(node.type, node.name)
# One request for the whole batch, not one per node.
urls = image_urls(figma, key, find_all(file.document, type="COMPONENT"), format="svg")from figmapy import FigmaAuthError, FigmaNotFoundError, FigmaRateLimitError, FigmaHTTPError
try:
figma.get_file(key)
except FigmaNotFoundError:
... # 404
except FigmaAuthError:
... # 401 / 403, bad token or missing scope
except FigmaRateLimitError as e:
... # 429, and the retries were already exhausted429s and 5xx are retried automatically, honouring Retry-After and otherwise backing off
exponentially with jitter. max_retries=0 turns that off.
Figma ships API changes whenever it likes, and this package will sometimes be a release behind. That is an inconvenience, never a wall:
| Situation | What happens | Escape hatch |
|---|---|---|
| Response has a field the models do not know | It is kept, and readable as an attribute | none needed |
| Response no longer matches the spec | Warns FigmaSpecWarning, returns the raw dict |
strict=True to raise instead |
| You would rather have dicts everywhere | — | Figma(parse=False) |
| Endpoint not in your installed version | — | figma.request("GET", "/v1/whatever", params={...}) |
| Whole client is in your way | — | figma.http is the underlying httpx.Client |
# An endpoint that shipped after your version of this package
data = figma.request("POST", "/v1/files/aBc/some_new_thing", json_body={"x": 1})If you hit one of these, open an issue — but only after you have unblocked yourself with the row above.
The package version follows date-based versioning (YEAR.RELEASE.PATCH), continuing the scheme from FigmaPy 2018.1.0 on PyPI.
figmapy.__version__ # '2026.1.0'
figmapy.FIGMA_SPEC_VERSION # '0.42.0' - the Figma OpenAPI spec this was generated fromSee docs/versioning.md for the full rules.
See MIGRATION.md. The short version: FigmaPy is now Figma, method
names match the spec, and responses are pydantic models. The old names still work.
figmapy/models.py and figmapy/_endpoints.py are generated — do not edit them, edit
tools/generate.py and re-run it. Everything else is fair game. See
docs/maintenance.md, and
docs/review-and-plan.md for why the repository is shaped this
way.
pip install -e ".[dev]"
pytest # no network needed, every request is mocked
ruff check .
Apache-2.0. Originally by Greg Amato, with the generated rewrite contributed later.