Place images inside Excel cells (not floating) using pure Python — no Excel required
Project description
Excel Place In Cell
Place Images Inside Excel Cells with Python · by koolz
Bulk-embed images inside cells in
.xlsxfiles — not floating shapes, not drawing objects — using Excel 365's native "Place in Cell" feature. Pure Python, zero dependencies, no Excel installation required.
If you have ever tried to programmatically place an image inside an Excel cell (like the Insert → Picture → Place in Cell option in Microsoft 365) and found that openpyxl, xlsxwriter, and the stable Office.js API all lack support — this library solves that problem.
Why this exists
Microsoft introduced in-cell images (LocalImageCellValue / WebImageCellValue) as part of the Excel 365 rich data types system. No mainstream Python Excel library supports it yet. This library reverse-engineers the underlying OOXML richData format and writes it directly, giving you full programmatic control.
Typical use cases:
- Product catalogs with a thumbnail per row
- Automated report generation with embedded charts or logos
- Data pipelines that output Excel files with visual content
- Apps that let users download Excel files with images already placed in cells
Requirements
- Python 3.9+
- Microsoft 365 (Excel for Windows, Mac, or Web) — this feature does not exist in Excel 2019 or earlier
- No pip dependencies for the core library
- Pillow only to run the built-in demo
Installation
pip install place_in_cell
Or directly from GitHub:
pip install git+https://github.com/superkoolz/place_in_cell
Quick start
from place_in_cell import create_excel_with_cell_images
create_excel_with_cell_images(
output_path="catalog.xlsx",
images=[
{"cell": "A1", "image": "photos/shoe_001.png"},
{"cell": "A2", "image": "photos/shoe_002.jpg"},
{"cell": "A3", "url": "https://example.com/shoe_003.webp"}, # Excel fetches at open time
]
)
Open catalog.xlsx in Excel 365 — each image sits inside its cell, resizes with the cell, and behaves like a native in-cell image inserted by hand.
API reference
create_excel_with_cell_images(
output_path, # str — path for the output .xlsx file
images, # list — one dict per image cell (see below)
sheet_name="Sheet1", # str — worksheet tab name
row_height=97.2, # float — row height in points (97.2 pt ≈ 130 px)
col_width=18.0, # float — column width in char units (18 ≈ 130 px)
)
Image entry keys:
| Key | Required | Description |
|---|---|---|
cell |
yes | Cell address, e.g. "B3" or "AA10" |
image |
one of | Path to a local image file |
url |
one of | HTTPS URL — Excel downloads the image on open |
alt_text |
no | Accessibility label shown in screen readers |
Supported local formats: PNG · JPEG · GIF · BMP · TIFF · WEBP
Building apps on top of this
Because create_excel_with_cell_images takes a plain list of dicts and writes a standard .xlsx, it fits naturally into any backend:
# Flask / FastAPI — serve a generated xlsx as a download
from flask import Flask, send_file
from place_in_cell import create_excel_with_cell_images
app = Flask(__name__)
@app.route("/export")
def export():
create_excel_with_cell_images(
output_path="/tmp/export.xlsx",
images=[
{"cell": f"A{i}", "image": f"products/{sku}.png"}
for i, sku in enumerate(get_skus(), start=1)
]
)
return send_file("/tmp/export.xlsx", as_attachment=True)
# Batch pipeline — process a folder of images into one spreadsheet
import os
from place_in_cell import create_excel_with_cell_images
images = [
{"cell": f"A{i}", "image": os.path.join("photos", fname)}
for i, fname in enumerate(sorted(os.listdir("photos")), start=1)
]
create_excel_with_cell_images("batch_output.xlsx", images)
How it works (OOXML internals)
Excel's "Place in Cell" images are stored via the OOXML rich data system — the same format used for linked data types and IMAGE() formula results. Inside the .xlsx ZIP:
xl/
├── metadata.xml # maps each cell's vm= index → rich value index
├── richData/
│ ├── rdrichvaluestructure.xml # declares _localImage / _webImage schemas
│ ├── rdrichvalue.xml # one <rv> entry per image cell
│ ├── richValueRel.xml # maps rich value index → relationship rId
│ └── _rels/richValueRel.xml.rels # rId → xl/media/imageN.ext
└── media/
├── image1.png
└── image2.jpg
Each image cell is written as t="e" vm="N" (error value type + value-metadata index). Excel follows the metadata chain at load time and renders the image inside the cell boundary.
The _localImage rich value structure:
<s t="_localImage">
<k n="_rvRel:LocalImageIdentifier" t="i"/> <!-- index into richValueRel.xml -->
<k n="CalcOrigin" t="i"/> <!-- always 5 for embedded images -->
</s>
Compatibility
| Environment | Status |
|---|---|
| Microsoft 365 — Excel for Windows | ✅ Supported |
| Microsoft 365 — Excel for Mac | ✅ Supported |
| Microsoft 365 — Excel for Web | ✅ Supported |
| Excel 2021 and earlier (perpetual licence) | ❌ No in-cell image feature |
| LibreOffice Calc | ❌ Not supported |
| Google Sheets | ❌ Not supported |
This is a Microsoft 365-exclusive feature. The
.xlsxfile opens without errors in older Excel versions, but image cells will show#VALUE!instead of the image.
Demo
pip install "place_in_cell[demo]"
python -c "from place_in_cell import __demo__; __demo__()"
Credits
Made by superkoolz
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file place_in_cell-1.0.1.tar.gz.
File metadata
- Download URL: place_in_cell-1.0.1.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5ed853e35d97ff79c617df75c034f983ba24d8572691db57b0e5b7422b43faf
|
|
| MD5 |
0b861bcd95e71bbe6a52a2936f79f79c
|
|
| BLAKE2b-256 |
be4be01aefd5738de1989dcc003cd52e4a148f239a67a16f55af62848cbe5ba7
|
Provenance
The following attestation bundles were made for place_in_cell-1.0.1.tar.gz:
Publisher:
publish.yml on superkoolz/place_in_cell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
place_in_cell-1.0.1.tar.gz -
Subject digest:
e5ed853e35d97ff79c617df75c034f983ba24d8572691db57b0e5b7422b43faf - Sigstore transparency entry: 1509041752
- Sigstore integration time:
-
Permalink:
superkoolz/place_in_cell@9e5a113ab19479c69a84476db9319984717b35cc -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/superkoolz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9e5a113ab19479c69a84476db9319984717b35cc -
Trigger Event:
release
-
Statement type:
File details
Details for the file place_in_cell-1.0.1-py3-none-any.whl.
File metadata
- Download URL: place_in_cell-1.0.1-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1c58743b393334b87087f94505ce7c7e417d30552f3a38cc451dbafcd97b8fa
|
|
| MD5 |
718d5c53a5ed638f55b7a1d8d22b7d66
|
|
| BLAKE2b-256 |
ac8ff4291254e79e005206e21c6846581cc3ae3328a0a742ff2e7b83a83eb24f
|
Provenance
The following attestation bundles were made for place_in_cell-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on superkoolz/place_in_cell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
place_in_cell-1.0.1-py3-none-any.whl -
Subject digest:
f1c58743b393334b87087f94505ce7c7e417d30552f3a38cc451dbafcd97b8fa - Sigstore transparency entry: 1509041851
- Sigstore integration time:
-
Permalink:
superkoolz/place_in_cell@9e5a113ab19479c69a84476db9319984717b35cc -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/superkoolz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9e5a113ab19479c69a84476db9319984717b35cc -
Trigger Event:
release
-
Statement type: