For developers

Build on the .velq format.

A .velq is an open, ZIP-based container for documents that must keep rendering offline — on any machine, at any later date. This page is the format's specification, and everything you need to read, write or extend it: the reference Rust crate, a conformance checklist, the plugin API, and the rules around the name.

ZIP containerSpec version 1Apache-2.0application/velq+zip

At a glance

Four kinds of entry. That's the format.

No custom encoding, no framing tricks. If you can write a ZIP and a JSON object, you can write a .velq.

manifest.json

Package metadata — title, timestamps, tags, and a single extension point. Required.

index.html

The document itself, the canonical view. Every reference in it points inside the archive. Required.

index.md

The Markdown source, present only when the package was authored from Markdown. Editors edit this; viewers never need it.

assets/

Everything the document uses: CSS, scripts, images, fonts — deduplicated and referenced by relative path.

See for yourself: rename any .velq to .zip, or run unzip -l report.velq. Nothing is hidden — that is the point.

Specification

The .velq container, version 1

Stable · shipped by Velq since 0.1 · reference implementation: velq-core

“Must” and “should” below are meant the RFC 2119 way: must is what conformance requires, should is what a good implementation does.

1 · Container

  • A .velq is a ZIP archive — magic bytes PK\x03\x04, UTF-8 entry names. Writers must use only the Stored or Deflate compression methods, so every ZIP tool ever written can open one.
  • The file extension is .velq. The suggested media type is application/velq+zip (unregistered); in practice the extension is the identifier.
  • Renaming a .velq to .zip and opening it anywhere is a supported operation, forever. A revision of this format that breaks it will not be called .velq.

2 · Entries

  • A ZIP is a valid .velq if and only if its root contains both manifest.json and index.html. There is no other precondition.
  • index.md is optional. When present, the package is a Markdown package: index.md is the editable source and index.html must be a rendering of it.
  • Every other entry is an asset. Velq's bundler stores them under assets/css|js|img|fonts/, named by a SHA-256 content-hash prefix — that layout is a convention, not a rule. Readers must not depend on asset paths beyond “relative, inside the archive”.
  • Tools that edit an existing package must carry over every entry they don't recognize, byte for byte. Unknown files belong to someone else's feature, not to the garbage collector.
  • When extracting to disk, sanitize entry paths (no absolute paths, no ..) — standard ZIP hygiene.

3 · manifest.json

  • One JSON object, camelCase keys. Every field is optional: missing fields take the defaults below, and readers must ignore top-level fields they don't recognize.
  • Editors may drop unknown top-level fields when rewriting — so tool-specific data does not go there. It goes under custom, keyed by a name you own ("com.example.mytool"), and editors must preserve custom untouched.
FieldTypeDefaultMeaning
titlestring"Untitled"Document title, shown by viewers and file managers.
creatednumber0Creation time, Unix epoch seconds.
updatednumber0Last-modified time, Unix epoch seconds.
sourceUrlstring · nullnullWhere the document was packaged from, if it came from a URL.
generatorstring"velq-core x.y.z"The tool that wrote the package. Put your own name here.
tagsstring[][]Free-form labels.
customany JSONnullThe extension point. Namespace your data by a key you own; editors preserve it verbatim.

Exactly the schema velq-core reads and writes — nothing here is aspirational.

A complete, valid manifest

{
  "title": "Q3 report",
  "created": 1783814400,
  "updated": 1783814400,
  "sourceUrl": null,
  "generator": "velq-core 0.1.1",
  "tags": ["report", "finance"],
  "custom": { "com.example.mytool": { "reviewed": true } }
}

4 · The offline contract

  • index.html together with the bundled assets must render completely with the network unplugged. A conforming writer rewrites every subresource reference — stylesheets, scripts, images, fonts — to a bundled relative path.
  • Scripts may be included and may run. A viewer should open packages in a sandbox that blocks network and file access; Velq's own viewer is an isolated zero-permission WebView with CSP connect-src 'none'.

5 · Versioning & stability

  • This page specifies version 1. Version-1 packages declare no version field.
  • Changes within version 1 are additive and backward-compatible: a version-1 reader opens every version-1 file, today and in ten years.
  • If a future revision ever needs to break that promise, it will declare itself in manifest.json (a formatVersion field) and be announced on this page first. There are no silent changes.

Conformance

“Works with .velq”, defined

Two short checklists. Meet the one that applies to your tool and the claim is yours to make.

A conforming reader

  • Opens any ZIP whose root contains manifest.json and index.html — nothing else required.
  • Displays index.html with every reference resolved from inside the archive, network off.
  • Ignores manifest fields it doesn't recognize, and survives a manifest of just {}.
  • Sanitizes entry paths if it extracts to disk.

A conforming writer / editor

  • Writes a plain ZIP (Stored or Deflate) containing manifest.json and index.html.
  • Rewrites every subresource reference to a bundled, relative path — the file renders offline.
  • Keeps index.md and index.html in sync when both exist.
  • Preserves custom and every entry it doesn't understand when editing an existing package.
  • Adds its own metadata under custom, never as new top-level manifest fields.

A known-good sample ships in the repository — demo.velq, the same fixture Velq's own tests open to prove that scripts run and the sandbox holds. The velq-core test suite is the executable form of this checklist: pack → validate → read → edit → re-validate.

Meet the list that applies to you, and you're welcome — encouraged — to say your product works with .velq.

Reference implementation

velq-core: the format, in ~350 lines of Rust

The crate Velq itself builds on. Apache-2.0, #![forbid(unsafe_code)], no Tauri dependency — it links into any Rust program. It is deliberately small: a .velq is a ZIP and a JSON object, and the library refuses to be cleverer than that.

Use it

# Cargo.toml — not on crates.io yet, so pull it straight from the repo
[dependencies]
velq-core = { git = "https://github.com/iKora128/velq" }

Write, validate, read

use std::path::Path;
use velq_core::{pack, read_manifest, validate, Asset, Manifest};

let manifest = Manifest { title: "Q3 report".into(), ..Manifest::default() };
let assets = [Asset {
    path: "assets/css/main.css".into(),
    bytes: b"body{margin:2rem}".to_vec(),
}];

pack(Path::new("report.velq"), &manifest, b"<h1>Q3</h1>", &assets)?;
validate(Path::new("report.velq"))?;          // manifest.json + index.html: ok
let title = read_manifest(Path::new("report.velq"))?.title;

The whole API

pack / pack_md

Write a package — HTML-only, or Markdown source plus its rendered HTML.

validate

The two-entry check: is this ZIP a .velq?

read_manifest / read_index_md / read_file_bytes / read_assets

Read metadata, the Markdown source, or any entry.

update_index / update_md

Replace the document, keep manifest and assets — written atomically, so a crash never corrupts the package.

unpack

Extract everything to a folder (the .zip escape hatch, as a function).

No Rust in your stack? The format needs none: ZIP plus JSON is a standard library away in JavaScript, Python, Go or Swift. If you port it, tell us — we'll link it here.

Extending the editor

Plugins are CodeMirror 6 extensions

Velq's rendering plugins use the same machinery as the built-in live preview. The core knows nothing about any specific plugin: KaTeX and Mermaid ship as reference plugins written only against the public API, and toggling them off leaves plain Markdown behind. If you can write a CM6 extension, you can write a Velq plugin.

import { usePlugins } from "@/plugins/runtime";
import type { VelqPlugin } from "@/plugins/api";

const myPlugin: VelqPlugin = {
  id: "highlight-todo",
  name: "TODO highlighter",
  description: "Tint TODO and FIXME markers.",
  extension: todoHighlightExtension, // any CodeMirror 6 extension
};

usePlugins.register(myPlugin);

The full guide, with both reference plugins dissected: docs/plugin-api.md.

Name & policy

Fork the code. Not the meaning of “.velq”.

The format only stays useful if a .velq opens everywhere. Here is exactly what's free and what's reserved.

The code is Apache-2.0

Use it, embed it, modify it, sell what you build — commercially, without asking, patent grant included. This applies to velq-core, the bundler, and the app.

The name identifies this project

Apache-2.0 §6 licenses no trademarks: “Velq” — the word and the mark — stays with the project. Say “works with .velq” or “built on velq-core” freely and truthfully; don't call a product or fork “Velq”, or anything confusingly close.

The format is stewarded here

Spec changes land on this page before they land in code, additively within version 1. Extensions belong in custom. A dialect that breaks the checklist isn't a .velq — call it something else.

Ship something that speaks .velq.