src/types/ARCHITECTURE.md
How TypeScript types are organized in plotly.js.
┌──────────────────────────────────────────────────────────────┐
│ Consumer surface (what `npm install plotly.js` exposes) │
│ lib/index.d.ts — wired via package.json#types │
│ `export type *` for generated schema types + explicit │
│ re-exports of hand-written types + `export as namespace │
│ Plotly` for global/namespace usage. │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Internal authoring surface │
│ src/types/index.d.ts — re-exports everything (internal) │
└──────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────────┐
│ Hand-written types │ │ Generated types │
│ src/types/core/*.d.ts │ │ src/types/generated/... │
│ src/types/lib/*.d.ts │ │ │
│ │ │ schema.d.ts — common enums, │
│ │ │ traces, layout, animation, │
│ │ │ config, _internal namespace │
└──────────────────────────┘ └────────────────────────────────┘
lib/index.d.ts deliberately omits internal types (FullLayout,
FullData, GraphDiv, the AttributeMap machinery, etc.) so consumers
see a clean public API. Internal types live in .internal.d.ts files and
remain accessible to plotly.js's own code through the src/types/
re-exports.
The split:
src/types/generated) are the authoritative TypeScript representation of
the runtime schema. The schema itself is produced from Plotly's JS
attribute files (src/.../attributes.js), which remain the source of
truth: chain is attribute files → plot-schema.json → generated
types.
src/types/generated/schema.d.ts contains:
PlotType alias for back-compat).Data discriminated union over all
of them (Partial<BarData> | Partial<ScatterData> | …) — narrowed via
the type field.schema.animation, schema.frames, and schema.config.edits._internal namespace wrapping types whose direct names would
mislead consumers (Marker is scatter-only, Line is the marker
outline) or are schema-internal helpers (AutoRangeOptions,
Lighting, ErrorY). Reachable as _internal.Marker etc.
but not at the top level.Generated from plot-schema.json by tasks/generate_schema_types.mjs.
Run npm run schema to regenerate.
src/types/ besides the generated/ directory)
cover everything the schema doesn't describe:
events, internal runtime state, public API function signatures,
utility types (Color, Datum, MarkerSymbol, ErrorBar), behavioral types
(ModeBarButton, Icon, etc.).Plotly's runtime stores two kinds of state on graph elements:
Plotly.newPlot(gd, data, layout) accepts_ prefix) — fully-resolved versions Plotly computes after
applying defaults, defined modules, and so onThis split is reflected in the types:
| User-facing | Internal | Where defined |
|---|---|---|
Layout | FullLayout | Layout in generated/schema.d.ts; FullLayout in core/layout.internal.d.ts |
Data (union over type) | FullData | Data in generated/schema.d.ts (union of schema *Data interfaces); FullData = Data & FullDataInternals in core/data.internal.d.ts |
| (n/a) | GraphDiv (the gd param) | core/graph-div.internal.d.ts — DOM element with _fullLayout, _fullData, calcdata, etc. |
FullData is the discriminated union of schema trace types intersected with
the internal _-prefixed fields. Internal code that narrows on trace.type
gets trace-specific fields plus the internal state in the same expression.
Internal types use index signatures ([key: string]: any) liberally to
allow incremental migration without blocking. As _ properties get
discovered during JS→TS conversion, add them to FullLayout or
FullDataInternals (the file-local intersection target inside
data.internal.d.ts).
src/types/
├── index.d.ts # main re-export hub (public + internal)
├── core/ # hand-written types for the core API
│ ├── api.d.ts # public API function signatures (newPlot, etc.)
│ ├── config.d.ts # Config, ToImgopts (Edits re-exported from generated)
│ ├── data.internal.d.ts # CalcData, FullData
│ ├── events.d.ts # PlotMouseEvent, PlotlyHTMLElement, etc.
│ ├── graph-div.internal.d.ts # GraphDiv, GraphContext
│ ├── layout.d.ts # AxisName, ModeBar behavioral types, Template
│ └── layout.internal.d.ts # FullLayout, LayoutSize, SubplotInfo
│
├── lib/ # primitives + the schema-extraction machinery
│ ├── common.d.ts # Color, Datum, TypedArray, MarkerSymbol, ...
│ └── attributes.d.ts # AttributeMap, AttrInfo (compile-time validation)
│
└── generated/ # machine-generated types
└── schema.d.ts # all traces + layout + shared types (from plot-schema.json)
.internal.d.ts conventionFiles with the .internal.d.ts suffix contain types that are not part of the
public API (not re-exported in lib/index.d.ts). These are internal runtime types
used only within plotly.js itself — FullLayout, FullData, GraphDiv, etc.
If a file has no .internal suffix, all its exports are public.
test/plot-schema.json (runtime schema: traces + layout + animation + config)
│
▼
[ tasks/generate_schema_types.mjs ]
│
│ 0. Discover common enum aliases via COMMON_TYPE_ANCHORS
│ (Calendar/Dash/AxisType/PatternShape/XRef/YRef/TransitionEasing,
│ plus TraceType derived from the trace-names list, plus a deprecated
│ `PlotType` alias)
│ 1. Fingerprint every container subtree across traces and layout
│ 2. Extract shared interfaces (Font, ColorBar, HoverLabel, etc.).
│ Inject Transition and AnimationFrameOpts as shared types
│ (animation has < MIN_OCCURRENCES sites otherwise)
│ 3. Emit per-trace interfaces referencing shared types
│ 4. Emit layout component interfaces (LayoutAxis, Legend, Scene, etc.)
│ and the Layout interface with subplot index signatures
│ 5. Emit AnimationOpts (from schema.animation), Frame (from
│ schema.frames with field overrides for the recursive data/layout
│ fields), and Edits (from schema.config.edits)
│ 6. Wrap names in INTERNAL_INTERFACES inside `export namespace _internal`
│ and rewrite outside-namespace references to `_internal.X`
│
▼
src/types/generated/schema.d.ts
│ // Common enum aliases
│ export type Calendar = 'chinese' | 'coptic' | ...;
│ export type TraceType = 'bar' | 'scatter' | ...;
│ /** @deprecated Renamed to TraceType. */
│ export type PlotType = TraceType;
│ // Shared interfaces (public)
│ export interface Font { ... }
│ export interface ColorBar { ... }
│ // Internal namespace
│ export namespace _internal {
│ export interface Marker { ... }
│ export interface Line { ... }
│ // ...
│ }
│ // Trace interfaces
│ export interface ScatterData { marker?: _internal.Marker; ... }
│ export interface BarData { ... }
│ // Discriminated union over all traces
│ export type Data = Partial<BarData> | Partial<ScatterData> | ...;
│ // Layout
│ export interface LayoutAxis { autorangeoptions?: _internal.AutoRangeOptions; ... }
│ export interface Layout { ... }
│ // Animation / config
│ export interface AnimationOpts { transition?: Transition; ... }
│ export interface Frame { data?: any[]; layout?: Partial<Layout>; ... }
│ export interface Edits { ... }
│
▼
src/types/index.d.ts re-exports all types (internal authoring index).
lib/index.d.ts uses `export type * from '.../generated/schema'` so every
public schema-derived type is automatically re-exported to consumers.
Regenerate with npm run schema (which rebuilds plot-schema.json
and then runs the schema type generator).
The schema doesn't describe:
PlotMouseEvent, PlotHoverEvent, LegendClickEvent,
PlotlyHTMLElement and its on() overloads. These are runtime contracts.Plotly.newPlot, relayout,
restyle, etc. Live in src/types/core/api.d.ts.FullLayout._modules, GraphDiv._fullData,
_calcInverseTransform, etc. Live in .internal.d.ts files in src/types/core/.ModeBarButton, ModeBarDefaultButtons, Icon,
ButtonClickEvent, Template. These describe runtime behavior patterns
not captured in the attribute schema.Color, Datum, TypedArray, MarkerSymbol, etc.
Live in src/types/lib/common.d.ts. The generator references these types.When converting a JS file to TS and discovering an internal property like
fullLayout._someFlag, add it to the corresponding Full* interface:
// src/types/core/layout.internal.d.ts
export interface FullLayout extends Layout {
_modules?: any[];
_someFlag?: boolean; // add new ones here
[key: string]: any;
}
The [key: string]: any index signature is intentional — it absorbs
unknown internal properties so JS code can be migrated piecewise without
type errors.
For exported functions in .ts source files, use a top-level JSDoc block
with @param name - description lines. Omit the type from @param
(the TS signature already has it) and use a hyphen separator:
/**
* make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10...
*
* @param head - the head of the pattern, eg 'x' matches 'x', 'x2', 'x10' etc.
* @param tail - a fixed piece after the id
*/
export function counter(head: string, tail: string = '') { ... }