BetaMaShop is in public beta. We improve it continuously, and your feedback shapes what comes next.

The blog editor (Tiptap)

Customizing & extendingEnglish· 6 min read· Updated July 01, 2026

Write and publish blog articles in your generated backoffice. The editor stores content as JSON in a Tiptap-shaped column, so word count, reading time, and the SEO score all read the same document.

How-to: write, format, and publish a blog article in your generated backoffice.

Your generated backoffice ships a blog editor at /blog/new (create) and /blog/[id]/edit (edit). This page shows you how to write an article, format the body, fill the sidebar, run the SEO checks, and save. Both routes render the same component, BlogArticleEditor, and both require the editor staff role or higher.

Open the editor

Go to /blog and click into an article, or open /blog/new to start a fresh one. The /blog/new route does not insert a row when it loads. The draft is created on your first Save, then the URL replaces itself with /blog/[id]/edit so later saves update the same row.

The page is a two-column layout above 1024px: the writing column on the left (title, excerpt, content, SEO), a settings sidebar on the right. Below 1024px the sidebar stacks under the content.

Write the title, excerpt, and body

Fill three fields in the left column, top to bottom.

  • Title drives two auto-fills. Until you edit them by hand, typing the title also fills the URL slug (via slugify) and the SEO meta title.
  • Excerpt is a short summary shown in listings and search results. Keep it to one or two sentences.
  • Content is a plain-text area with a formatting toolbar above it.

The content toolbar has six buttons. Each one inserts Markdown-style syntax around your selection or at the start of the current line. It does not render formatting inline while you type.

ButtonWhat it inserts
Boldwraps the selection in **
Italicwraps the selection in _
Headingprefixes the current line with ##
Listprefixes the current line with -
Linkwraps the selection as [text](https://)
Imageinserts ![alt](https://) at the line start

How the body is stored

The body is not saved as a Markdown string. On save, each line of the textarea becomes one paragraph in a Tiptap-shaped JSON document written to blog_posts.content_json (a jsonb column, NOT NULL DEFAULT '{}'). A three-line body serializes to this shape:

{
 "type": "doc",
 "content": [
 { "type": "paragraph", "content": [{ "type": "text", "text": "First line" }] },
 { "type": "paragraph" },
 { "type": "paragraph", "content": [{ "type": "text", "text": "Third line" }] }
 ]
}

Blank lines become empty paragraphs. The document is never empty: an empty body still produces one paragraph. When you reopen the article, the editor reads the same document back into plain text, so the round trip is stable. The Markdown characters you insert with the toolbar (**, ## , and so on) are stored as literal text inside the paragraph, not converted into bold or heading nodes.

Storing a Tiptap document, rather than a raw string, is what keeps the downstream helpers working: the same JSON feeds the word counter, the reading-time estimate, and the SEO analysis. It also means a full block editor can read and write the exact same column later without a data migration.

Fill the sidebar

The right column groups the article metadata into three cards.

Publication

  • Status is Draft or Published. Choosing Published with an empty publish date stamps the current time on save.
  • Author is read-only. It shows your staff name (or email) and is set from your session.
  • Publish date is a datetime field. Leave it empty to publish immediately.
  • Featured is a toggle that flags the article for the blog home.

Organization

  • Category is a single-select of your blog categories, plus a "No category" option.
  • Tags is an add-on-Enter field. Duplicate tags (case-insensitive) are ignored, and the list is capped at 20 tags.
  • Allow comments is a toggle, on by default for new articles.

Featured image

Paste an image URL into the field. There is no file upload in the shipped editor: the empty state reads "Paste an image URL below. Upload to your storage bucket is wired at fork time." The image URL is saved to blog_posts.cover_image_url, which is a separate field from the document body, not a block inside it.

Run the SEO checks

Below the content sits a live SEO panel with a score ring. The score is recomputed as you type from the title, slug, meta fields, focus keyword, the document body, and any JSON-LD you attach. The heading reads "All applicable checks pass." when nothing is failing, or "N checks to improve." otherwise.

The analyzer walks the same content_json document to check real content signals, including:

  • Focus keyword in the title, slug, first paragraph, and a top-level heading.
  • Meta title length (30 to 60 characters) and meta description length (120 to 160 characters).
  • Alt text coverage across every image node in the body.
  • At least one internal link (a link whose href is relative, not http:// or https://).
  • Content length, which flags bodies under 300 words and warns hard under 100.

The checks are guidance, not a gate. You can save an article whatever the score.

Save

Click Save in the sticky header. The button label switches to "Saving..." while the write runs. On success you get a toast: "Article created" for a new article, "Article saved" for an edit. A new article then redirects to its /blog/[id]/edit URL so the next save updates the same row.

On save, the server recomputes and persists two derived fields from the document, so you never set them by hand:

  • words_count counts every text node in the body, tokenized on whitespace.
  • reading_minutes is ceil(words / 220), with a floor of 1 minute for any non-empty body and 0 for an empty one.

The save also replaces the article's tags and upserts its SEO row. If the write fails, the toast shows the underlying error message, or "Could not save the article" when no message is available. Nothing is saved until you click Save; there is no autosave in the shipped editor.

What "Tiptap" means here, and what is not wired yet

"Tiptap" refers to the JSON document format, not to a rich block editor in the shipped UI. The backoffice includes Tiptap building blocks in src/components/editor (a block toolbar, a slash menu, and custom nodes for callouts, a labelled divider, a product embed, a link with safe rel defaults, and a cover-image field). The Tiptap packages are installed. But none of those components are mounted by /blog/new or /blog/[id]/edit today: the shipped editor is the Markdown-shortcut textarea described above. Treat the block toolbar, the slash menu, and the custom nodes as scaffolding, not as live controls.

Because the storage shape is a stable Tiptap document, you can ask the AI to swap the textarea for the full block editor without changing the column or losing existing articles. See prompting to make that change, and components for the wider backoffice UI. The generated code is yours to edit directly, as described in your code; your prompts and generated code are not sent to OpenAI or Anthropic.

blogeditortiptap
Was this page helpful?
Your feedback is anonymous.