When writing Medium blogs, Data scientists and developers often find themselves copy and pasting code chunks, importing images and tables to the Medium editor. This can be quite a tedious process and most importantly it gets in the way of the actual goal: writing.
In this post, I am presenting nb2medium
, a package I have been developing in my spare time for the last few weeks which enables users to upload Medium drafts directly from a Jupyter Notebook. Here, I will be taking you through the features nb2medium
comes with.

TLDR
nb2medium represents a simple yet sufficient framework to upload Jupyter notebooks to Medium.
The most prominent features of nb2medium
are:
- Written using
nbdev
and therefore easy to maintain - Simple pythonic implementation of Medium API
- Makes use of
nbconvert
and custom preprocessors to turn notebooks to Markdown documents - Supports uploading blocks as GitHub gists with simple in-cell tags
- Supports hiding of cells sources, cells outputs or cells
- Comes with Jupyter extension and CLI for ease-of-use
Docs, GitHub repo, Example notebooks
Article Overview
I will be covering how to install and setup nb2medium
and then I will be walking through its functionality.
Install
pip install nb2medium
The extension should be enabled by default but if it is not, enable it manually by running:
jupyter notebook install nb2medium --py
jupyter notebook enable nb2medium --py
Add --user
to these commands, if you want to activate the extension only for the current user.
Add --sys-prefix
to these commands, if you want to activate the extension only in the current virtual environment.
Setup
You will need a Medium Integration token to be able to upload your articles to Medium. If you wish to upload some code blocks as gists, you will need a GitHub token too. Both tokens should be available as environment variables, hence we recommend you add these 2 lines to your shell configuration file (~/.bashrc
or ~/.zshrc
are the most common ones):
export MEDIUM_TOKEN=<your-medium-token>
export GITHUB_TOKEN=<your-github-token>
Obtain a Medium Integration Token from your Medium settings page Obtain a GitHub token by following GitHub’s docs
How to use
You may choose to use nb2medium
as a command-line tool, directly from python or using the Jupyter notebook extension button or menu.
Note:
nb2medium
uploads Jupyter notebooks as they are, the notebooks do not get executed before being rendered.
From the Jupyter notebook
You may choose to use the nb2medium
under the file menu or the button on the top toolbar:

From the CLI
From the shell (bash, zsh, sh):
nb2medium "My article" path/to/notebook.ipynb
Use nb2medium --help
to see all the different options
From python
Images, code cells and tables
Images
Images usually come from a local file, online site or are the result of a plot. nb2medium
can handle these 3 situations. If an image corresponds to a local file in a markdown cell (e.g. 
)
Local images are detected automatically and then uploaded to the Medium endpoint and the path to the image is swapped for the newly generated image URL. If the image is the result of a plot, such as:


The result of the image is uploaded to the Medium endpoint (without being written to memory) and the corresponding plot is replaced by the plot’s URL.
If the image is already online, nothing changes to it, as Medium can access it directly from the internet when loading the article
Code cells as GitHub gists
By default, a code cell is rendered without syntax highlighting or anything fancy like:
import pandas as pd
pd.DataFrame({'a': [1,2,3], 'b': [2,4,6]})
Though if a GitHub token is available and the user includes the following header in a cell:
# %gist gistname: pandas.py
import pandas as pd
pd.DataFrame({'a': [1,2,3], 'b': [2,4,6]})
The code block will be uploaded to the user’s GitHub as a private gist (by default – can be changed to be made public) and the respective code cell will be replaced by the gist URL.
The gists you see in this article were uploaded using this functionality
The #%gist
tag takes the following "arguments":
gistname:
name of script with file extension (critical for correct syntax highlighting, e.g script.py)description:
[optional] string (no quotes are necessary) describing the gistpublic:
[optional – default False] whether gist should be public or notupload:
[optional – default source] whether cell’s source, output or both should be uploaded as gists (e.g.upload: both
,upload: source
)
Hiding cells
It is often convenient to hide either a cell’s source (i.e. the code), a cell’s output (the result of evaluating the code) or the whole-cell altogether. To achieve this, the user can place the following header at the start of the relevant cells.
- To hide a cell’s source:
- To hide a cell’s output:
- To hide a cell completely (source and output):
Note: all tags (
%hide-*
and%gist
) were not designed with the idea to be combined so such usage has not been tested. In general, there should be no need for such behaviour.
Note 2: It is hard to display some of the functionality __ of the hiding tags in this article because I need to show the nb2medium
syntax. I just want to say that the #%hide
tags work as you would expect.
Tables
Medium does not have good support for HTML nor Markdown tables. My preferred existing option for tables are once again gists. If a cell outputs a pandas data frame and you use the #%gist
option with the value of the upload:
flag set to both
or output
, nb2medium
will detect your table and upload it as a CSV to your GitHub gist repo.
Example header: #%gist gistname: pandas.py upload: both
Example output (2 gists below):
Documentation
The docs are available at https://lucharo.github.io/nb2medium and are rendered automatically from the nbdev
notebooks so they are always up to date with the package source code.
Contributing & Conclusion
If you find a bug or think of an enhancement feel free to raise issues or submit pull requests. If you want to contribute to open-source projects such as this one have a look at the issues with the label/tag help needed
in particular. I plan to actively maintain this package and to make it the mainstream way for Jupyter users to upload articles.
This article was indeed written in a Jupyter notebook and uploaded as a draft to Medium. I have built this package to suit my needs and preferences and I always prefer being careful about publishing that is why I only provide the option to submit the notebook as a draft instead of publishing it directly.