PTTJS (Plain Text Table JavaScript) is a table format I created to solve my own pain points—and, judging by the feedback I’ve already received, I’m not the only one who has felt the limitations of existing text‑table formats.
A JavaScript library with a parser + serializer and an Obsidian plugin are already available.
Goal
The main goal of PTTJS is to provide a text format that can faithfully store complex tables—far beyond what CSV or Markdown can handle—while still remaining human‑readable.
Motivation
I tolerated the shortcomings of other formats for a long time, but eventually the trade‑offs became impossible to ignore. Key drivers were:
- Feeding richer tables to LLMs. Documents often contain intricate, merged‑cell tables that can’t be flattened to CSV/Markdown, forcing us to reach for heavier multimodal models.
- Extracting tables via CV into plain text, not into a deeply nested JSON blob that’s unreadable until you re‑render it.
- Letting an LLM manipulate an entire table natively—rows, columns, formulas—without shoe‑horning everything through Google Sheets or Excel.
- Opening tables without special software. Most tables end up trapped in XLS(X) or ODT; PTTJS keeps them plain text.
- Editing complex tables in plain‑text tools like Obsidian.
- Personal dislike of Google Sheets/Excel (surely I’m not the only one!).
the problem is indicated in points 1 and 2, the format is recognized normally and inserted into the text
Format Overview
The very first line is always an annotation:
|PTTJS 1.0|encoding=UTF‑8|
Pages
A table can span multiple pages:
|(@P1|Page Name){
…table data…
}|
Page markers are optional; omit them and the whole table lives on a single page.
Cells
Every cell starts with |
and ends with >
:
|H([1|1]1|1|@C1)>
-
H
— optional marks a header cell -
([X|Y])
— zero‑based cell index (optional; auto‑filled if absent) -
(X|Y)
— cell span (defaults to1|1
; always declared in the top‑left merged cell) -
(@ID)
— cell id for references
Row ends with:
<|
If a row is otherwise empty but you still have rows below it, include at least one empty cell: |><|
.
Forbidden characters inside cell content (\n | > < { }
) must be URL‑encoded:
\n → %5Cn
| → %7C
> → %3E
< → %3C
{ → %7B
} → %7D
The library provides escapeValue
/ unescapeValue
helpers.
Scripts
>>>SCRIPT
…typings, formulas, styling…
<<<SCRIPT
- Always placed after all table data.
- Can work across pages.
- Use
<=
for CSS‑like styles. - Use
=>
for cell typings. - Use
=
for formulas. - Target a range with
:
—e.g.0:0|0
(whole first row) or0|0:0
(whole first column).
The cells always contain up-to-date information. When we add a script, it recalculates the table data and adds the up-to-date information to the cells.
Examples
1 – Basic Table
|PTTJS 1.0|
|H>Plate Number|H>Year|H>Make & Model<|
|>080XXX02|>2005|>LEXUS RX 350<|
|>787XXX16|>2015|>GEELY GC7<|
|>871XXX05|>1997|>TOYOTA IPSUM<|
|>A602XXX|>1996|>MITSUBISHI PAJERO<|
|>890XXX02|>1997|>TOYOTA LAND CRUISER PRADO<|
|>216XXX13|>2007|>DAEWOO NEXIA<|
2 – With Explicit Indexes
|PTTJS 1.0|encoding=UTF‑8|
|H([0|0])>Plate Number|H([1|0])>Year|H([2|0])>Make & Model<|
|([0|1])>080XXX02|([1|1])>2007|([2|1])>LEXUS RX 350<|
…
3 – Complex Header (Merged Cells)
|PTTJS 1.0|encoding=UTF‑8|
|H(1|2)>Plate Number|H(2|1)>Vehicle Data|H><|
|H>|H>Year|H>Make & Model<|
|>080XXX02|>2007|>LEXUS RX 350<|
…
visualization from Obsidian
4 – With Scripts
|PTTJS 1.0|encoding=UTF‑8|
|H([0|0])>Plate Number|H([1|0])>Year|H([2|0])>Make & Model<|
|([0|1])>080XXX02|([1|1])>2007|([2|1])>LEXUS RX 350<|
…
|([0|7])><|
|([0|8])>Average Year|([1|8])>2003<|
>>>SCRIPT
(1|1,1|6)=>NUMBER(2,' ')
(1|8)=DIV(SUM(1|1,1|6),COUNT(1|1,1|6))
(0|8:8)<=BORDER(each,2,solid,#000)
<<<SCRIPT
Row 9 calculates the average year; numeric formatting is applied; a black 2 px border is added to the "Average Year" label cell.
Roadmap
- Execute script functions directly on the parsed Store object.
- Import/export converters (XLS(X), ODT, CSV, MD ↔ PTTJS).
- Libraries in other languages.
- Lightweight web UI for editing PTTJS.
Contributing
The GitHub repo is public—PRs and ideas are very welcome!
Final Words
Thanks for reading! I hope PTTJS helps you tackle the same table woes I faced. Feel free to reach out, fork the repo, or open an issue—let’s make complex tables easy together.
Top comments (0)