Skip to content

Upgrading to EdgarTools 6.0

This page lists every change in 6.0 that can alter what your code receives, one symbol at a time: what it did before, what it does now, and the rewrite where a mechanical one exists. It is written to be enough on its own — if you are an agent upgrading a dependent codebase, you should not need the CHANGELOG or the commit history as well.

6.0 is still in development. This page grows as each change lands, so a change absent here has not shipped. Items known to be coming are listed under Still to come so you can plan for them, but do not write code against them yet.

Two kinds of change

Loud ones rename, move or remove something: the cash-flow method names, the edgar.effect / edgar.form144, settings and date-helper moves, the access modes, and FilingHomepage(soup=...). Each of these warns in 5.x before it breaks in 6.0, so running your code on the latest 5.x with warnings visible finds them.

Quiet ones keep every import path and signature and change what values you get back. Your code will not fail to import, and in most cases it will not raise; it will return something different. A few of these warn in 5.x too (the error changes emit a FutureWarning), but most cannot, so read those sections closely.


Cash flow: one name, cash_flow_statement()

What changed. Cash flow had three names depending on which object you were holding — Company.cash_flow(), Financials.cashflow_statement() and xbrl.statements.cash_flow_statement(). Only Company accepted all three, and of the 16 classes offering income_statement(), EntityFacts and CurrentPeriodView offered no cash_flow_statement() at all. It is now cash_flow_statement() everywhere, matching income_statement() and balance_sheet().

cashflow_statement() and cash_flow() still work in 5.x and emit a DeprecationWarning. 6.0 removes them.

# Before — whichever spelling that particular object happened to accept
company.cash_flow()
financials.cashflow_statement()
xbrls.statements.cash_flow_statement()

# Now — the same name on every object
company.cash_flow_statement()
financials.cash_flow_statement()
xbrls.statements.cash_flow_statement()

The mechanical rewrite. Both old spellings map to the new one with no change in arguments or return type:

grep -rln 'cashflow_statement\|\.cash_flow(' . \
  | xargs sed -i '' -e 's/cashflow_statement/cash_flow_statement/g' \
                    -e 's/\.cash_flow(/.cash_flow_statement(/g'

Why it is worth a rename. income_statement, balance_sheet, cashflow_statement — one of those three is not like the others, and the cost landed on people least able to absorb it. Assistants suggested the wrong spelling, autocomplete could not help across objects, and a beginner got an AttributeError for a method that exists on the object next to it.

To find your call sites now, run with deprecation warnings visible:

python -W "default::DeprecationWarning" your_script.py

edgar.effect and edgar.form144 moved

What changed. Two single-form parsers moved to the subpackages their subject belongs to. Effect now lives in edgar.offerings.effect, beside the S-1/S-3/S-4, 424B and Form C/D parsers — a notice of effectiveness is a registration-lifecycle document. Form144 now lives in edgar.ownership.form144, beside the Forms 3/4/5 parsers — Form 144 is an affiliate's notice of a proposed sale.

Neither name was in edgar.__all__ nor re-exported at the top level, so the module path was their entire public surface. Both old paths still work in 5.x and emit a DeprecationWarning naming the new one. 6.0 removes them.

# Before
from edgar.effect import Effect
from edgar.form144 import Form144

# Now
from edgar.offerings.effect import Effect
from edgar.ownership.form144 import Form144

You may not need to do anything. filing.obj() routes to the new paths, so ordinary use never triggers the warning — this only affects code that imports the module directly.

The objects are the same objects, not copies. The shims forward by attribute rather than re-exporting a fixed list, so edgar.effect.Effect is edgar.offerings.effect.Effect holds, and isinstance checks written against either path agree. You can migrate one call site at a time without a mixed-import bug.

Mechanical rewrite:

sed -i '' -e 's/from edgar\.effect import/from edgar.offerings.effect import/' \
          -e 's/from edgar\.form144 import/from edgar.ownership.form144 import/' your_script.py

edgar.muniadvisors also became a package in the same release, but its import path is unchanged and all of its public names still resolve — nothing to do.


period_length on the EntityFacts statements

What changed. EntityFacts.income_statement() and .cash_flow_statement() took a period_length argument documented as "3=quarterly, 12=annual". It was never read — it appeared nowhere in either method body, and had not since the Facts API landed. Asking for period_length=3 returned the annual statement, with no warning.

It is honoured now, and deprecated in the same release: period= is the supported spelling and the only one that can also say 'ttm'. 6.0 removes period_length.

# Before — silently annual, whatever you passed
facts.cash_flow_statement(period_length=3)

# Now — quarterly, with a DeprecationWarning naming the replacement
facts.cash_flow_statement(period_length=3)

# The spelling to move to
facts.cash_flow_statement(period='quarterly')

This one is a fix, not a break. Nobody can have depended on the old behaviour deliberately: there was no way to pass period_length and get what the docstring promised. If a script of yours passes period_length=3 and its numbers change, they were wrong before — it was reading annual figures under a quarterly label.

Contradictions now raise. Saying two different things at once used to be resolved silently in favour of whichever the code happened to read:

facts.income_statement(period='annual', period_length=3)   # ValidationError
facts.income_statement(period='quarterly', period_length=3)  # fine, they agree

period_length values other than 3 or 12 raise ValidationError as well, rather than being ignored.

The mechanical rewrite:

sed -i '' -e "s/period_length=3/period='quarterly'/g" \
          -e "s/period_length=12/period='annual'/g" your_script.py

edgar now declares __all__

What changed. The top-level package declares its public API in __all__. Until now there was no way to tell an API from an accident: everything reachable from edgar looked equally official, including Optional and partial (imported for annotations) and Document, which is the legacy edgar.files parser and a different class from edgar.documents.Document.

What breaks today: from edgar import * is narrower. It no longer supplies the names outside __all__. If you use star-import and something stops resolving, import it from where it lives:

# Before — arrived via `from edgar import *`
from edgar import *
doc = Document.parse(html)

# Now
from edgar.files.html import Document   # the legacy parser, removed in 6.0
# or, for the current one:
from edgar.documents import Document

Direct imports are unaffected. from edgar import <anything> works exactly as it did, listed or not.

What is coming in 6.0. Names outside __all__ become private. They fall into three groups: stdlib that leaked in through annotation imports; everything belonging to edgar.files, which is removed; and internal plumbing that already has a supported entry point in front of it — filesystem/config helpers, the cache-clearing functions behind clear_cache(), and lower-level variants such as get_by_accession_number_enriched.

To check whether you are affected, compare what you import against the list:

import edgar
mine = {"Company", "Filing", "Document"}          # whatever your code imports
print(sorted(mine - set(edgar.__all__)))          # -> ['Document']

Filing.cik and Filing.company on multi-filer filings

What changed. A filing with more than one filer now reports the issuer where it used to report whichever filer the quarterly index happened to list first, which was frequently the parent guarantor.

Resolving an accession — find(accession), get_by_accession_number(accession) — now goes through EDGAR full-text search, which orders a filing's filers issuer-first. The quarterly index ordered them differently.

from edgar import find

f = find("0001918704-25-005439")   # a BofA structured note

f.cik, f.company
# 5.x:  (70858,   'BANK OF AMERICA CORP /DE/')   <- the parent guarantor
# 6.0:  (1682472, 'BofA Finance LLC')            <- the issuer

What did not change. The set of filers is identical, and so is everything you can reach from the filing:

f.all_ciks       # [70858, 1682472] — same list, same order, both versions
f.all_entities   # still names both filers (the order differs)
f.text(), f.attachments, f.xbrl()   # identical; EDGAR serves the documents
                                    # under either CIK

Rewrite rule. If you read .cik or .company to identify the filer, decide which filer you actually want and say so:

# Before — implicitly "whichever filer the index put first"
issuer = filing.company

# After — say which one you mean
issuer = filing.company                       # the issuer, explicitly
every_filer = filing.all_entities             # all of them
if 70858 in filing.all_ciks:                  # membership, not identity
    ...

Single-filer filings — nearly all of them — are unaffected. Measured across 54 ground-truth accessions: 50 identical, 4 differing only in this way, none failing to resolve.

Scope. Filings from before 2001 are outside the full-text index and still resolve through the quarterly index unchanged, as does any lookup full-text search cannot answer.


TableNode.to_dataframe() row index

What changed. A table whose first column header spans several physical columns now returns a plain row index instead of None-padded tuples.

Filings routinely lay the row-label column across two or three physical columns that all carry the same header text, with only the first holding the label. 5.x moved every matching column into the index, so a label came back as a tuple.

table = document.tables[10]
df = table.to_dataframe()

list(df.index)[:1]
# 5.x:  [('Gross written premiums by line of business:', None, None)]
# 6.0:  ['Gross written premiums by line of business:']

df.loc['Gross written premiums by line of business:']
# 5.x:  KeyError
# 6.0:  works

Where the match came back two-dimensional, 5.x raised ValueError: Index data must be 1-dimensional outright and you got no frame at all.

Rewrite rule. Drop any tuple unpacking around index labels:

# Before
for label, row in df.iterrows():
    name = label[0] if isinstance(label, tuple) else label

# After
for name, row in df.iterrows():
    ...

Column count. The empty columns left behind by the spanning header are dropped, so most tables have the width they had in 5.x. A same-headed column that holds real data — a percentage or a rate — is kept as data rather than absorbed into the index, so a small number of tables are wider.

Measured across 1,940 tables in the benchmark corpus: 1,228 unchanged, 711 with a scalar index in place of tuples, 31 wider, one that used to raise and now converts, none returning less data. The corpus exposes the identical 1,026,693 non-blank cells before and after.


Document.to_dataframe()

What changed. It works. In 5.x it raised on essentially every real annual or quarterly report, from inside pandas or numpy, with one of:

ValueError: cannot join with no overlapping index names
TypeError:  Cannot cast array data from dtype('float64') to dtype('int64')
ValueError: Index data must be 1-dimensional

A filing's tables do not share a schema — one 10-K in the benchmark corpus has 71 tables whose column indexes are 1, 2, 3, 4, 10 and 17 levels deep — and pandas cannot align those. 6.0 flattens each table to single-level string column names before stacking them.

df = document.to_dataframe()

df.columns.nlevels          # always 1; every name is a str
df['_table_index']          # which table each row came from
df['_table_type']           # the table's classified type
df['_table_caption']        # its caption, or None

Rewrite rule. Remove any try/except you wrapped around this call, and drop MultiIndex column handling — df[('Revenue', '2024')] becomes df['Revenue 2024']. Header levels are joined with a space, with blanks dropped and repeats collapsed, so ('Revenue', '') and ('Revenue', 'Revenue') both become 'Revenue'.

When you want the real header structure, use the per-table API, which still preserves it:

document.tables[i].to_dataframe()   # keeps MultiIndex columns

FactQuery.to_dataframe() — the XBRL one

Two classes share this name

This applies to edgar.xbrl.facts.FactQuery, reached via filing.xbrl().facts.query(). It does not apply to edgar.entity.query.FactQuery, reached via company.get_facts().query(), which is unchanged in 6.0. Check which one you hold before changing code.

What changed. The column set now follows the query's configuration — the include_* flags and any names passed to to_dataframe() — and no longer varies with which rows matched.

q = filing.xbrl().facts.query()

q.limit(5).to_dataframe()
# 5.x: could return fewer columns than the same query unlimited, because
#      columns that happened to be null in those 5 rows were dropped
# 6.0: same columns either way; unpopulated ones come back null

q.by_period_type('instant').to_dataframe()
# 5.x: period_start / period_end disappeared
# 6.0: present and null

empty_result.to_dataframe()['decimals']
# 5.x: KeyError
# 6.0: an empty column

Rewrite rule. Remove defensive column checks:

# Before
if 'decimals' in df.columns:
    use(df['decimals'])

# After
use(df['decimals'])

Dtypes are unchanged for populated columns. A column with no data at all now takes its declared dtype rather than whatever inference landed on.


10-K items on filings with a Cross Reference Index

What changed. TenK.__getitem__ returned raw HTML on filings that route through a Cross Reference Index — Citigroup, GE and Henry Schein among them — while every other filing returned text. It now returns text everywhere.

tenk = filing.obj()
tenk['Item 1']
# 5.x (Citigroup): 1,685,461 chars starting '<div style="min-height:36pt...'
# 6.0:               218,550 chars of text, opening on the Business overview

Rewrite rule. If you were parsing the returned markup, stop:

# Before
from bs4 import BeautifulSoup
text = BeautifulSoup(tenk['Item 1'], 'html.parser').get_text()

# After
text = tenk['Item 1']

This affected filings unevenly — on Citigroup, Item 1A, Item 7 and Item 8 already returned text while Item 1 did not — so code that looked correct on one item could break on another.

Related. An item whose Cross Reference Index row reads "Not Applicable", or carries only an incorporation-by-reference marker, returns None rather than an empty string. There is no such disclosure in the document, and an empty section would present absence as content.


Section keys and section content

What changed. Section extraction was substantially corrected in 6.0. No API changed shape, but document.sections and tenk.items return different keys and different content on filings that were previously mis-parsed.

If you have hardcoded section keys, lengths, or offsets, re-check them. The changes that move the most:

  • Filings that group items out of numeric order (Morgan Stanley) had sections that ran to the wrong end or vanished entirely; they are now bounded correctly and the missing ones are present.
  • Filings using a Cross Reference Index (Citigroup) now expose canonical keys such as part_ii_item_7, where 5.x returned non-canonical keys like mda and gave nothing for the canonical name.
  • Filings writing item headings as single-row tables (Wells Fargo) went from one section of wrong content to all 23 items.

Rewrite rule. Prefer canonical keys and check for absence:

# Before — a key that only existed on some filings
mda = document.sections['mda'].text()

# After — the canonical key, absence handled
section = document.sections.get('part_ii_item_7')
mda = section.text() if section else None

Underwriter extraction on registration statements

What changed. RegistrationS1.underwriting returned wrong rosters in two directions, both now fixed. No API changed; the values did.

  • A firm whose legal name contains "division of" — EF Hutton, division of Benchmark Investments, LLC, ThinkEquity, a division of Fordham Financial Management, Inc. — was rejected as parser junk, so lead_manager was None on every filing it led.
  • A filing's beneficial-ownership table could be read as its underwriting syndicate, so lead_manager came back as the column header 'Before Offering' and the roster listed individual directors.

Rewrite rule. Remove None fallbacks and name filters you added to work around either:

# Before
lead = uw.lead_manager or guess_from_cover(filing)
firms = [u.name for u in uw.underwriters if looks_like_a_firm(u.name)]

# After
lead = uw.lead_manager
firms = [u.name for u in uw.underwriters]

Errors: one vocabulary, and None never means failure

There were 27 exception classes across ten packages with no shared base, of which two were reachable from the top level — so except had to name a type from whichever module happened to raise. There is now a root and four branches, in edgar.exceptions:

EdgarError
├── TransportError          we could not get an answer from SEC
├── NotFoundError           you named a thing and it does not exist
├── ParsingError            we got the bytes and could not build the object
└── ValidationError         your input was wrong before we ever asked

Nothing you catch today stops working. Every old name is either re-based into the tree or kept as a deprecated alias for the same object, and the branches inherit the builtin they replace — ValidationError IS-A ValueError, NotFoundError IS-A LookupError — so except ValueError: written against our raises keeps catching them. See Handling Errors for what to catch and why.

Deprecated names, removed in 6.0

Old New
StatementNotFound StatementNotFoundError
NoCompanyFactsFound CompanyFactsNotFoundError
SECFilingNotFoundError FilingNotFoundError
InvalidDateException InvalidDateError
IdentityNotSetException IdentityNotSetError
TooManyRequestsException TooManyRequestsError
DataObjectException DataObjectError

What 6.0 changes

Each of these emits a FutureWarning in 5.x naming what it becomes. Set EDGARTOOLS_STRICT_ERRORS=1 to get the 6.0 behaviour today — including in your test suite, which is the cheapest way to find out what breaks while there is still a 5.x release to fix it in.

Call 5.x 6.0
an httpx failure escaping the network layer propagates as httpx.ReadTimeout and friends raises TransportError, with the original as __cause__
tenk["Item 99"] — no such item warns, returns None raises SectionNotFoundError
find("123456-99") — malformed accession warns, returns None raises ValidationError
filing.obj() on a modelled form whose data will not read warns, returns None raises DataObjectError
tenk.document when the parser fails warns, returns None raises the parser's ParsingError
get_financials() on a filing with no XBRL warns, returns a Financials whose accessors all answer None raises XBRLFilingWithNoXbrlData

filing.obj() on a form edgartools does not model still returns None, and so does filing.xbrl() on a filing without XBRL. filing.html() also keeps returning None when the filing has no HTML primary document, which is common: the primary document is a PDF, a text file or XML, or, since 5.59.0, the index page lists no primary document at all, where it used to raise AttributeError. Those are answers, not failures, and that distinction is the point of the whole change.

The get_financials() row is on the other side of exactly that line, which is worth spelling out because the two look alike. filing.xbrl() is asking whether there is XBRL, so "there is none" is an answer. get_financials() is asking for statements, and it did not answer — it returned an object that passes if financials is not None: and then yields None from income_statement(), balance_sheet() and cash_flow_statement(). A company whose latest annual report predates SEC's 2009-2011 XBRL phase-in reaches this; Company(104599) — Circuit City, last 10-K filed 2008 — is one.

Nothing about that object changes in 5.x. You get the warning, and the code you have keeps working; 6.0 raises instead.

Where the raising form is the only form, 5.x ships a non-raising one beside it: report.get(item, default) is to report[item] what dict.get is to dict[...], and it never warns.


Settings and identity moved to edgar.settings

What changed. Connection settings and SEC identity now live in edgar.settings: EdgarSettings, set_identity, get_identity, get_edgar_data_directory, and the access modes (NORMAL, CAUTION, CRAWL).

The access modes moved, but they are also deprecated and removed in 6.0 — they never affected how edgartools talks to SEC. See Access modes are deprecated below.

They used to live in edgar.core, alongside quarter math, HTML sniffing, a pager, thread helpers and the logger — only about a third of that 697-line module was settings, and everything you are told to configure was in that third.

Nothing breaks today. edgar.core re-exports every one of those names, and they are the same objects, so is comparisons and isinstance checks behave identically:

from edgar.core import get_identity      # works today, removed in 6.0
from edgar.settings import get_identity  # preferred

Most code needs no change at all. set_identity is re-exported on the top-level namespace, which is how the documentation has always shown it:

from edgar import set_identity          # unaffected, now and in 6.0
from edgar import CAUTION               # deprecated, warns today, gone in 6.0

What 6.0 changes. The edgar.core re-exports go. If you import any of these from edgar.core directly, repoint at edgar.settings — or at edgar, for the names it exposes. The access modes go entirely, from every path.

The rest of edgar.core — the logger, listify, the extension tables, HTML sniffing, the pager and the thread helpers — stays where it is. The quarter helpers have since moved too; see the next section.


Access modes are deprecated

What changed. EDGAR_ACCESS_MODE and the NORMAL / CAUTION / CRAWL objects are deprecated. Reading any of them, or setting the environment variable, raises a DeprecationWarning in 5.x. They are removed in 6.0.

Why. The modes advertised a timeout, connection-limit and retry policy that was never wired into the HTTP client. Nothing in the package read edgar_mode, so all three modes behaved identically and selecting one never changed how edgartools talks to SEC.

What to use instead. The two settings that do work:

export EDGAR_RATE_LIMIT_PER_SEC="5"   # replaces CAUTION / CRAWL: slow the request rate
export EDGAR_HTTP_TIMEOUT="25"        # per-request timeout in seconds

Both are documented in Configuration.

EdgarSettings.limits is removed in the same change. It ignored self.max_connections and returned the module default, so every mode reported max_connections=10 regardless of what it declared. Nothing read it. The module-level limits object is a different name and is unaffected.


FilingHomepage takes an lxml tree, not BeautifulSoup

What changed. The filing index page is parsed with lxml instead of BeautifulSoup since 5.53.0. Two call shapes that handed edgartools a soup object still work in 5.x and emit a DeprecationWarning. 6.0 removes both.

5.x call (warns) 6.0
FilingHomepage(url, soup=soup) the soup= keyword is gone; pass the tree as the second argument, root
a BeautifulSoup passed as root to FilingHomepage(...) or Attachments.load(...) raises; pass an lxml tree
# Before
soup = BeautifulSoup(html, "lxml")
homepage = FilingHomepage(url, soup=soup)

# Now
from edgar.attachments import parse_homepage_html
homepage = FilingHomepage(url, parse_homepage_html(html))

In 5.x a soup is re-parsed with lxml for you, which costs a second parse. parse_homepage_html accepts str or bytes; prefer bytes, since lxml refuses a str that opens with an encoding declaration, which SEC markup often does.

You probably do not need to do anything. filing.homepage and FilingHomepage.load(url) build the tree themselves and never warn. This only affects code that constructs a FilingHomepage or calls Attachments.load directly.


Quarter and date helpers moved to edgar.dates

What changed. filing_date_to_year_quarters, current_year_and_quarter, is_start_of_quarter and parse_acceptance_datetime now live in edgar.dates, beside extract_dates, which was already the module for turning your dates into something the SEC APIs accept.

Same reasoning as the settings move above, applied to the next coherent piece of edgar.core: the module that already owns the concern gets the code, rather than core.py being renamed around whichever third of it is largest.

Nothing breaks today. edgar.core re-exports all four, and they are the same objects:

from edgar.core import filing_date_to_year_quarters   # works today, removed in 6.0
from edgar.dates import filing_date_to_year_quarters  # preferred

Most code needs no change at all. None of these four is exported on the top-level edgar namespace, and they are helpers rather than part of the documented surface — if you have never imported them by name, this does not affect you.

What 6.0 changes. The edgar.core re-exports go. Repoint any direct import at edgar.dates.


Still to come

Planned for the 6.0 breaking window and not yet shipped. Do not write code against these; each will get a section above when it lands.

Change What to expect
edgar.files removal The legacy parser package and chunked_document go. Use edgar.documents. edgar.files.markdown, edgar.files.tables and edgar.files.text already warn in 5.x — they were the user-reachable modules with no deprecation, so without it your code would have met the removal as a bare ImportError. edgar.files.styles is deliberately left unwarned: parse_style runs thousands of times per document, and its entry points (Document, SECHTMLParser) already warn.
Filing.sections() backend Not a break — behaviour you may notice. It is chunked by edgar.documents now rather than the legacy parser being removed, so Filing.search() result boundaries shift slightly. The two chunkers index the same words to within 0.10% across 41 era-stratified fixtures, and mean recall@5 over 375 phrase queries is unchanged at 99.2%.
CurrentReport.doc (8-K) Already deprecated in 5.x. It returns the legacy ChunkedDocument, unlike .doc on every other report class, which returns the edgar.documents document — one attribute name, two unrelated types, and only the 8-K one stands on the package being removed. Use .document for the parsed document, or .items / report[item] for item access. In 6.0 .doc becomes the modern document on 8-K too, so code that reads ChunkedDocument-specific attributes off it needs changing, not just re-pointing.
detect_page_breaks(), mark_page_breaks() Already deprecated in 5.x, and importable from the top level (from edgar import detect_page_breaks), which is why they warn on their own rather than only going out with the package. No replacement — see the row below for why page breaks are not part of the new document model.
include_page_breaks on markdown() Already deprecated in 5.x: Filing.markdown() and Attachment.markdown() render through edgar.documents, but include_page_breaks=True still falls back to the legacy renderer — which drops images. Both it and start_page_number go with edgar.files. There is no replacement: the new builder treats page-break <hr>s and page-number containers as print chrome and discards them.
httpx → httpx2 Not in 6.0 (decided 2026-09-30). It is waiting on an upstream vcrpy fix and may land in a later 6.x. It is no longer a breaking change for you either way: TransportError now owns the exception types at the network boundary, so the HTTP client underneath is ours to change. Catch edgar.exceptions types, not httpx ones, and the swap will not reach your code.
Public API enforcement __all__ is already declared (see below) — 6.0 makes the names outside it private, and some currently-importable names will move.
Source tree reorganisation Module locations change; import paths follow. The first moves have landed and have their own section above — edgar.effect and edgar.form144. Further moves will each get a section as they ship.
FactQuery.to_dataframe() sentinels preferred_sign and fiscal_year move to nullable Int64.
Schedule 13D/G figures a filing does not report ReportingPerson's six ownership figures, total_voting_power, total_dispositive_power, Schedule13D/Schedule13G total_shares and total_percent, and OwnershipComparison.shares_change, percent_change and the share and percent values of get_summary() become None where the filing does not report the figure, instead of reading it as 0. 5.x already has the None-returning form: ReportingPerson.reported(figure), ReportingPerson.unreported_fields, and OwnershipComparison.reported_shares_change / reported_percent_change; shares_change and percent_change emit a FutureWarning when they read an unreported figure as 0. A reported 0 stays 0.

Getting help

If something changed that is not on this page, that is a gap in this document rather than an intended surprise — please open an issue so it can be added.