DEV Community

Cover image for I Turned My Blog's CSV Table Helper into a Remark Plugin
logarithmicspirals
logarithmicspirals

Posted on Originally published at logarithmicspirals.com

I Turned My Blog's CSV Table Helper into a Remark Plugin

Writing a table directly in Markdown works for a few rows, but it becomes awkward when the data changes often or comes
from a spreadsheet. I wanted the writing surface to stay readable while keeping the structured data in a format that is
easy to edit, validate, and reuse.

That led to remark-csv-table, a Remark plugin that converts local CSV files referenced by a Markdown directive into
GitHub Flavored Markdown (GFM) tables during the build. It started as a custom plugin in this Astro site and is now a
published package that other Remark pipelines can use.

The Markdown Table Problem

Markdown tables keep content and presentation together, but they are not a great editing format for larger datasets.
Adding a column means adjusting every row. Copying data from a spreadsheet can introduce alignment mistakes. Reviewing
changes in a long table is also harder than reviewing a focused data file.

The workflow I wanted was simple: keep a post's data in a nearby CSV file and let the Markdown refer to it.

::csv{src="./model-results.csv"}
Enter fullscreen mode Exit fullscreen mode

At build time, the directive becomes a regular GFM table. The source article remains focused on the explanation, while
the data remains in a CSV file that can be opened in a spreadsheet or edited as plain text.

From A Site Helper To A Package

My articles have become increasingly data-heavy, with longer tables and more content in the cells. Eventually, I decided writing the tables directly into the Markdown source for the articles was consuming more time than it was worth. I decided to store the data in CSV and inject that into the Markdown as part of the rendering process.

The original implementation lived in this site's Markdown pipeline as remark-csv-tables.ts. It did more than parse a
file: it resolved each src value relative to the current Markdown file, rejected paths outside the post directory,
required a non-empty header and at least one data row, and replaced the directive with an mdast table node.

Those checks are important. A directive should not be able to read an arbitrary file from a build machine, and a broken
CSV should fail clearly rather than silently produce a malformed article.

Once the plugin was useful in more than one place, keeping it tied to this site's directory structure stopped making
sense. The reusable version exposes a contentDirectory option instead of assuming this repository's content path.

Once I got the plugin working, I realized it would be potentially helpful to other developers or even useful to myself on future projects. Also, I have not had a chance to publish my own public package to npm yet, so this seemed like a good candidate for a learning experience to do that. The main thing I learned from creating this package is that publishing is the easy part, and the hard part is getting the project ready for the first release.

Install And Configure remark-csv-table

remark-csv-table is ESM-only and requires Node 24 or newer. It works in a Remark pipeline that also includes
remark-directive for the ::csv syntax and remark-gfm for GFM table output.

bun add remark-csv-table remark-directive remark-gfm unified remark-parse remark-stringify
Enter fullscreen mode Exit fullscreen mode

Here is a minimal Unified pipeline:

import { unified } from "unified";
import remarkDirective from "remark-directive";
import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import remarkCsvTable from "remark-csv-table";

const result = await unified()
  .use(remarkParse)
  .use(remarkDirective)
  .use(remarkGfm)
  .use(remarkCsvTable, { contentDirectory: "content" })
  .use(remarkStringify)
  .process({
    path: "content/posts/example.md",
    value: '::csv{src="./table.csv"}',
  });
Enter fullscreen mode Exit fullscreen mode

The CSV path must remain inside both the Markdown file's directory and contentDirectory. By default,
contentDirectory is the current working directory. CSV files need a header and at least one data row, headers cannot
be empty, and cells support single-line inline Markdown.

Using It In Astro

Astro can load the package in the same Remark plugin list as other Markdown transforms. This site imports its configured
unified processor from @astrojs/markdown-remark and includes the plugin after remark-directive.

import { unified } from "@astrojs/markdown-remark";
import remarkDirective from "remark-directive";
import remarkCsvTable from "remark-csv-table";

export const markdownConfig = {
  processor: unified({
    remarkPlugins: [remarkDirective, remarkCsvTable],
  }),
};
Enter fullscreen mode Exit fullscreen mode

With that configuration, an article and its CSV can stay together:

src/data/blog/example-post/
  index.md
  model-results.csv
Enter fullscreen mode Exit fullscreen mode
The following results compare the available configurations.

::csv{src="./model-results.csv"}
Enter fullscreen mode Exit fullscreen mode

This is deliberately a build-time transformation. Readers receive normal table HTML, not a client-side CSV parser or a
network request for the source data.

What Changed On This Site

The extraction let this site remove its custom remark-csv-tables.ts file and replace it with the published package in
the Markdown configuration. Existing article directives keep the same ::csv{src="./file.csv"} syntax, so the content
authoring workflow stays the same.

The difference is maintenance scope. The CSV-to-table behavior now has its own package, documentation, tests, and
release cycle instead of being coupled to one Astro project. That makes it useful to other sites while keeping this
site's Markdown configuration smaller.

Try The Plugin

If you keep structured data beside Markdown content, install
remark-csv-table on npm and see the source at
github.com/h93xV2/remark-csv-table. The package is intended for the
specific case where local, build-time CSV tables are easier to maintain than hand-written Markdown tables.

Install remark-csv-table from npm

Top comments (0)