SharePoint limits you must know
A DBML schema that is perfectly ordinary as a relational design can silently
cross one of SharePoint Online's platform ceilings at runtime, after a clean
dbml-sharepoint build and a green deploy.js.txt run. Every DBML Ref
becomes a Lookup column, every person column is a lookup type for the
purposes below, and both cost the same platform budget a hand-authored
SharePoint list would.
This page collects the ceilings that matter for schema design, states where
each is documented, and (because a page that claims a check exists when it
does not is worse than no page) says plainly whether this build's validator
catches it, warns about it, or is silent. The audit below reads the
validator source directly (analysis/checks/_structure.py,
analysis/checks/_views.py, analysis/joins.py); it is not a guess at what
"probably" fires.
Read this before you design a schema, not after a deploy surprises you. Read DBML reference and mapping reference for the full, live-verified detail behind each ceiling. This page is the map, they are the territory.
The list view threshold: 5,000 items
The limit. A list view query that touches more than 5,000 items is throttled. Microsoft states the limit cannot be raised and that, past it, a query may not error at all. It can silently return a truncated answer (up to the newest 1,250 items) instead of the rows a filter actually matches.
How it maps here. Every declared views[].where clause and the
generated All Items view are queries against the list. A filter with no
selective index behind it is exactly the shape that gets truncated rather
than refused. Indexing a Lookup or Person column does not avert
it
Microsoft's own indexing guidance says so, and treats Person/Group
(single value) and Managed Metadata as lookup types for this purpose
too.
What the build does. Warns, never errors: unindexed_filter_columns
when a where clause filters only on columns no effective index covers. The
build cannot know how large a list will grow, so it reports the exposure
rather than refusing the build over it.
Mitigation. Add a bare DBML indexes entry on a selective scalar
filter column (Text, Number, Choice or Date, not Lookup or Person). See the
full mechanics, including the multi-value case that has no index remedy at
all, in mapping reference: views.
The lookup-and-person-column ceiling: 12 per view, not 8
The limit. A single view query can perform at most 12 join operations: one per rendered Lookup or Person column (Managed Metadata, too). Microsoft's Power Query SharePoint Online list connector documentation states this explicitly for SharePoint Online: "a maximum of 12 join operations per query… This issue manifests as SharePoint queries failing when more than 12 columns are accessed simultaneously," citing the same figure Microsoft's SharePoint Server 2016/2019 boundaries page documents as the List view lookup threshold.
An older figure of 8 circulates from a farm property
(MaxQueryLookupFields) that does not exist in SharePoint Online at all.
That is an on-premises upgrade story, not a SharePoint Online one, and it is
not what either citation above states. This repository also measured the
ceiling directly against a live tenant, at 6,000 items with the filter held
constant so the join count was the only variable: 12 rendered, 13
refused, SPQueryThrottledException code -2147024749 (2026-07-31,
test/manual/threshold-index-probe.js). The 12-join figure is corroborated
independently by measurement, not assumed from the citation alone.
How it maps here. Every DBML Ref (a real Lookup) and every person
column costs one join, whether or not it holds data. Author (Created By)
and Editor (Modified By) cost one each too. The generated All Items view
appends both unconditionally, so every All Items starts at 2: an
entity's real budget for its own lookup and person columns is 10, not
12.
What the build does. Silent at 10 or fewer, warns at 11 and 12
(join_threshold_approached), errors at 13+
(join_threshold_exceeded). The warning band exists because 12 held on the
tenant this repository measured, but the SharePoint Online citation, while
explicit, is not exhaustive platform documentation. A view in the 11 to 12
band may not travel to every tenant. The band once started at 9, because 8
was a real limit on some on-premises farms; that property does not exist in
SharePoint Online, so the band was narrowed in September 2026.
Mitigation. Fewer rendered lookup/person columns per view; on the
generated All Items view specifically, hide_from_all_items can drop
Author, Editor and other join-bearing columns that cost joins without
declaring you a view. See mapping reference: a view can only perform 12
joins for the full budget arithmetic and the
hide_from_all_items syntax.
The indexed-columns-per-list cap: 20
The limit. A list can carry at most 20 indexed columns. Microsoft states it directly: "You can add indexes on up to 20 columns on a list or library… While you can add up to 20 indexes per list or library, it's recommended you add indexes to only… the most commonly used columns." (Add an index to a list or library column). The same page's supported/unsupported table confirms which SharePoint column types can carry an index at all: Text, Number, Currency, Date/Time, Choice (single), Yes/No, Lookup and Person/Group (single) can; Multiple lines of text, Choice (multi-valued), Calculated, Hyperlink/Picture and Person/Group (multi-valued) cannot.
How it maps here. The DBML indexes {} block spends this budget one
entry at a time. Two more are spent without a corresponding indexes {}
entry: a [unique] column carries one implicitly, and a list a real Lookup
points at carries one on its display_column automatically (so its lookup
picker keeps working past 5,000 items). SharePoint also creates indexes of
its own when a view is sorted on an unindexed column. Those are invisible
to this build.
What the build does. Warns at 18–19 of 20 (index_limit_approaching),
errors above 20 (index_limit_exceeded). The message names the implicit
contributors, the ones an author cannot see just by counting indexes {}
entries.
Mitigation. Spend the budget on the columns actually used to filter
large lists; drop indexes that only sped up a Metadata Navigation view
you no longer use. See
DBML reference: indexes for the full
declaration rules, including which DBML types can carry [unique].
The calculated-column operand rules
The limit. A calculated formula can reference only its own row's
columns, and not every column type is a legal operand. Microsoft's own
formula reference
states plainly: "Calculated fields can only operate on their own row, so
you can't reference a value in another row, or columns contained in another
list or library. Lookup fields are not supported in a formula." This is
why a DBML Ref can never be a calculated-formula operand.
This repository's own live probe extends that matrix to every operand type
this tool can emit (test/manual/calculated-operand-probe.js, run against
SharePoint Online 2026-07-30): Person, Multiple lines of text, Rich text,
Hyperlink and Choice (multi-valued) are all refused the same way Lookup
is: HTTP 500, "One or more column references are not allowed, because the
columns are defined as a data type that is not supported in formulas." This
is a finding this repository verified rather than assumed, in keeping with this
project's rule against asserting SharePoint behaviour from
plausibility.
[Today], [Me] and any other bracketed token that is not a declared
column name fail for a related but distinct reason: they are not row
columns at all, and the same Microsoft formula reference's "own row" rule
above is why a calculated formula has no way to name them.
The one exception is [Created] in a document library's calculated_date
column. It is a column every item has without being declared, and a library
accepted =[Created]+14, read it back and rendered the result as a date
(measured on a live tenant, 2026-09-28). A generic list's [Created], a
calculated_text or calculated_number column reading it, and other built-in
columns such as [Modified] have not been measured in a calculated formula
and are still refused.
That measurement was made where the built-in column is titled Created, and
the reference resolves by display title. The deploy therefore reads the
library's own Created column and refuses the formula if its title is anything
else. For a library that already exists this happens in the preflight, before
any write. A library the deploy creates has no column to read until it exists,
so it is checked after the library is created and before the calculated column
is, and a refusal there stops the run with the column not created. A site in
another language may title the column differently, and no such site has been
measured.
How it maps here. Every calculated_text, calculated_number and
calculated_date column's formula, declared in mapping.yaml's
calculated_formulas, is checked against this matrix.
What the build does. Errors, always: before any script is
emitted. calculated_formula_unsupported_operand and
multi_value_operand_unsupported name the forbidden operand and column;
calculated_formula_unknown_column catches [Today], [Me] and any typo,
because none of them names a declared column of the entity; [Created] in a
library's calculated_date column is the one undeclared name it lets through.
The build refuses the formula at build time rather than letting SharePoint
refuse the field creation at HTTP 500, part-way through a deploy that has
already written earlier phases.
Mitigation. Compute from a supported operand type (text, number, date, choice, boolean or another calculated column) instead. See DBML reference: calculated-formula operand types for the full, live-verified operand table.
What this page leaves out, and why
No cross-site lookups. DBML Ref columns become same-site SharePoint
Lookup columns; a relationship into a list on a different site needs the
mapping's cross_site_reference_columns pattern (a Choice + URL pair)
instead of a real Lookup.
DBML reference and
mapping reference already state this, so the
rule is not repeated here. How far it is sourced is a separate question,
and the answer is only partly.
Microsoft does state the restriction, in a note of its own on "Create
list relationships by using lookup
columns",
which applies to Microsoft 365 and Microsoft Lists: "Lookup columns should
be created and used within the same site." That is advice rather than a
documented refusal, and it describes the column pane rather than the REST
field API this tool writes through. On that API Microsoft documents
nothing: FieldLookup.LookupList and FieldLookup.LookupWebId are
published with no constraint at all. The server object model documents the
opposite for its own surface, with an SPFieldCollection.AddLookup
overload that takes a target web id and is described as creating a lookup
"for another list in a different website".
So what Microsoft documents is a cross-web lookup being accepted, on a
surface this tool does not use, and what it does not document is
SharePoint Online refusing one on the surface this tool does.
Issue #184
stays open until the cross-web arm of projected-lookup-probe.js measures
what SharePoint Online does with a lookup created at a list in another
web. That arm measures one pair of webs per run, by default a subweb of the
site it is pasted on, while the build rule compares site roles that can be
deployed to unrelated site collections, so a run can bound the rule rather
than remove it. The build refuses the schema either way, because it fails
closed.
The two-level group_by ceiling. Documented in
mapping reference; not repeated here
because it was not part of the set of ceilings this page was written to
cover.
Any other limit not listed above is, as far as this page is concerned,
either not yet known to bite a DBML-shaped schema, or not yet sourced to
Microsoft documentation strongly enough to publish. If you hit one, please
open an issue, ideally with a test/manual/ transcript.