Roam: Convert {{[[table]]}} blocks to Markdown pipe tables#505
Roam: Convert {{[[table]]}} blocks to Markdown pipe tables#505cristip73 wants to merge 1 commit intoobsidianmd:masterfrom
Conversation
Previously table markers were stripped (commented-out code) and table children rendered as plain bullet lists. This adds proper structural conversion: each row's linear first-child chain becomes table columns, with Roam markup scrubbed, pipes escaped, and uneven rows padded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| const binaryRegex = /https:\/\/firebasestorage(.*?)\?alt(.*?)/; | ||
|
|
||
| const blockRefRegex = /(?<=\(\()\b(.*?)\b(?=\)\))/g; | ||
| const roamTableRe = /^\{\{(\[\[)?table(\]\])?\}\}$/i; |
There was a problem hiding this comment.
The current regex has two independent optional groups:
/^\{\{(\[\[)?table(\]\])?\}\}$/i
^^^^ ^^^^
group 1 group 2
Each ? makes its group independently optional, producing 4 possible matches:
| Group 1 | Group 2 | Matches | Valid Roam? |
|---|---|---|---|
| absent | absent | {{table}} |
yes |
| present | present | {{[[table]]}} |
yes |
| present | absent | {{[[table}} |
no |
| absent | present | {{table]]}} |
no |
The proposed fix ties the brackets together as a single alternation:
/^\{\{(\[\[table\]\]|table)\}\}$/i
This only matches {{[[table]]}} or {{table}} -- brackets are always balanced.
| const roamTableRe = /^\{\{(\[\[)?table(\]\])?\}\}$/i; | |
| const roamTableRe = /^\{\{(\[\[table\]\]|table)\}\}$/i; |
| while (current) { | ||
| const scrubbed = await this.roamMarkupScrubber(graphFolder, attachmentsFolder, current.string || ''); | ||
| cells.push(scrubbed.replace(/\|/g, '\\|')); | ||
| current = current.children?.[0]; |
There was a problem hiding this comment.
If a Roam table cell has multiple children (e.g., user added sub-items), all but the first are silently discarded with no warning. This could cause data loss without any indication to the user. Consider logging a warning when current.children.length > 1.
| const prefix = json.heading ? '#'.repeat(json.heading) + ' ' : ''; | ||
| const scrubbed = await this.roamMarkupScrubber(graphFolder, attachmentsFolder, json.string); | ||
| markdown.push(`${isChild ? indent + '* ' : indent}${prefix}${scrubbed}`); | ||
| if ('string' in json && json.string && roamTableRe.test(json.string.trim()) && json.children) { |
There was a problem hiding this comment.
- children:
[](truthy) enters convertRoamTable, returns''-- table marker vanishes silently - children:
undefinedfalls to the else branch and renders raw{{[[table]]}}in output
Consider having the table-detection guard not require json.children, and letting convertRoamTable handle all cases uniformly.
| // Build pipe table | ||
| const lines: string[] = []; | ||
| // Header row | ||
| lines.push(indent + '| ' + tableData[0].join(' | ') + ' |'); |
There was a problem hiding this comment.
The formatting of the various table sections could be unified and simplified by inserting the separator row after the header:
const separator = tableData[0].map(() => '---');
tableData.splice(1, 0, separator);
const lines = tableData.map(row => indent + '| ' + row.join(' | ') + ' |');

Summary
Roam Research
{{[[table]]}}blocks are currently not converted duringimport — the table marker text is left as-is (the only handling was a
commented-out
replaceAllat line 289), and table children become plainbullet lists that lose all tabular structure in Obsidian.
This PR adds proper structural conversion of Roam tables into standard
Markdown pipe tables.
Partially addresses #180.
How it works
roamTableRedetects{{[[table]]}}and{{table}}markers (case-insensitive)
jsonToMarkdown(), when a table block is detected, processing isdelegated to a new
convertRoamTable()method instead of the normalbullet/children recursion
convertRoamTable()walks each row's linear first-child chain toextract columns (Roam encodes table cells as nested first-children),
scrubs Roam markup via the existing
roamMarkupScrubber(), escapespipe characters, pads uneven rows, and builds a standard pipe table
Edge cases handled
\|Test plan
npm run build)npm run lint){{[[table]]}}blocks andverify pipe tables render correctly in Obsidian
🤖 Generated with Claude Code