Lumen ChartsLumen Charts
Save & restore

Saving and loading charts

What each kind of saved state holds, quick snapshots, and the save/load adapter behind the built-in Save and Load controls.

Download Markdown

What you save depends on what you want users to get back. Layouts, indicator templates, drawing templates and chart settings templates each cover a different part of the chart.

What's savedWhat it containsUsually handled by
Chart layoutHow the chart is set up, plus saved analysisA complete layout adapter
Study templateA set of indicator settingsA study template adapter
Drawing templateA reusable drawing styleA drawing template adapter
Chart settings templateAppearance and settingsA chart template adapter
Separate drawing stateDrawings and groups, stored on their ownA separate drawing adapter

Quick snapshots

widget.save(state => {
  localStorage.setItem('my-chart:layout:v1', JSON.stringify(state));
});
const saved = localStorage.getItem('my-chart:layout:v1');
if (saved) widget.load(JSON.parse(saved));

Be ready for JSON parse errors, a full storage quota and saved data the runtime rejects. Putting a version in your own storage key doesn't change the runtime's own schema version.

Built-in Save and Load controls

Provide a complete adapter and the chart shows its Save and Load controls. If the layout adapter is missing or incomplete, those controls stay off. The chart never guesses a storage URL. The sample stores layouts in the browser and supports list, load, save and delete.

Save/load adapter

A layout adapter needs all four operations: list, save, load and delete. Handle sign-in in your backend requests. Don't put credentials in the chart options.

MethodReturns
getAllCharts()A list of layout details
saveChart(chartData)The saved layout's ID
getChartContent(id)The content string saved earlier
removeChart(id)Completes once the layout is deleted

Example: saving in the browser

// Import these public types from the declarations in your authorized distribution.
export const storageKey = 'lumencharts-docs:layouts:v1'
type RecordEntry = ChartData & { id: string; timestamp: number }
type StoragePort = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>
export function createDemoStorage(storage: StoragePort) {
  function read(): RecordEntry[] {
    const raw = storage.getItem(storageKey)
    if (!raw) return []
    let parsed: unknown
    try { parsed = JSON.parse(raw) } catch { throw new Error('Saved sample layouts are invalid. Reset saved layouts to start again.') }
    if (!Array.isArray(parsed) || parsed.length > 30 || !parsed.every(item => item && typeof item === 'object' && typeof item.id === 'string' && /^\d+$/.test(item.id) && typeof item.name === 'string' && typeof item.content === 'string' && typeof item.symbol === 'string' && typeof item.resolution === 'string' && Number.isFinite(item.timestamp))) throw new Error('Saved sample layouts are invalid. Reset saved layouts to start again.')
    return parsed as RecordEntry[]
  }
  function write(records: RecordEntry[]) {
    const value = JSON.stringify(records)
    if (value.length > 2_000_000) throw new Error('Sample storage is full. Delete a saved layout first.')
    storage.setItem(storageKey, value)
  }
  const adapter: Pick<IExternalSaveLoadAdapter, 'getAllCharts' | 'saveChart' | 'getChartContent' | 'removeChart'> = {
    async getAllCharts() { return read().map(({ id, name, symbol, resolution, timestamp }) => ({ id: Number(id), name, symbol, resolution: resolution as ResolutionString, timestamp })) },
    async saveChart(chart) {
      const records = read()
      const id = String(chart.id ?? Math.max(Date.now(), ...records.map(item => Number(item.id) + 1)))
      const next = records.filter(item => item.id !== id)
      if (next.length >= 30) throw new Error('Save up to 30 sample layouts. Delete one first.')
      next.push({ ...chart, id, timestamp: Math.floor(Date.now() / 1000) })
      write(next)
      return id
    },
    async getChartContent(id) { const item = read().find(item => item.id === String(id)); if (!item) throw new Error('Saved layout was not found'); return item.content },
    async removeChart(id) { write(read().filter(item => item.id !== String(id))) },
  }
  return { adapter, reset: () => storage.removeItem(storageKey) }
}

The legacy interface types list extra methods for other features. The runtime checks for the complete layout methods on their own. Optional template and separate drawing storage each need their own complete set of methods. Don't add empty methods that just report success to satisfy a broad type.

On your server

Check that the signed-in user is allowed to perform every list, read, write and delete. The client_id and user_id strings narrow down whose data you're working with, but they don't prove who the user is. Limit list sizes in the database, and enforce a size limit on content before you store it.

Return saved content exactly as you received it; don't rewrite what's inside. Give a clear error for missing records, denied access, invalid content and temporary failures. If a response arrives after another operation has started, or after the chart was removed, ignore it.

Loading chart… · Fixed sample period ending 15 October 2026.

On this page