Skip to main content

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 .xlsx files — 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 .xlsx file 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

place_in_cell-1.0.1.tar.gz (21.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

place_in_cell-1.0.1-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

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

Hashes for place_in_cell-1.0.1.tar.gz
Algorithm Hash digest
SHA256 e5ed853e35d97ff79c617df75c034f983ba24d8572691db57b0e5b7422b43faf
MD5 0b861bcd95e71bbe6a52a2936f79f79c
BLAKE2b-256 be4be01aefd5738de1989dcc003cd52e4a148f239a67a16f55af62848cbe5ba7

See more details on using hashes here.

Provenance

The following attestation bundles were made for place_in_cell-1.0.1.tar.gz:

Publisher: publish.yml on superkoolz/place_in_cell

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for place_in_cell-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f1c58743b393334b87087f94505ce7c7e417d30552f3a38cc451dbafcd97b8fa
MD5 718d5c53a5ed638f55b7a1d8d22b7d66
BLAKE2b-256 ac8ff4291254e79e005206e21c6846581cc3ae3328a0a742ff2e7b83a83eb24f

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page