Markdown To Create Verifiable Credential Type Metadata
A tool to generate VCTM (Verifiable Credential Type Metadata) files from markdown, as specified in Section 6 of draft-ietf-oauth-sd-jwt-vc-12.
mtcvctm allows you to author Verifiable Credential Type definitions using familiar markdown syntax and automatically converts them to valid VCTM JSON files. The tool is designed to be used in CI/CD pipelines, particularly as a GitHub Action, to maintain a registry of credential type definitions.
go install github.com/sirosfoundation/mtcvctm/cmd/mtcvctm@latestdocker pull ghcr.io/sirosfoundation/mtcvctm:latestmtcvctm generate credential.md
mtcvctm gen credential.md -o output.vctm
mtcvctm generate credential.md --base-url https://registry.example.comProcess all markdown files in a directory:
mtcvctm batch --input ./credentials --output ./vctm --base-url https://registry.example.comPublish existing VCTM JSON files without markdown conversion:
# Basic usage
mtcvctm publish-vctm --input ./vctm-files --output ./vctm
# With image processing
mtcvctm publish-vctm --input ./vctm-files --output ./vctm --inline-images
# In GitHub Action mode
mtcvctm publish-vctm --github-action --vctm-branch vctmThis command validates and publishes raw VCTM JSON files (matching patterns *.vctm.json, vctm_*.json, vctm-*.json), applies normalization rules, and generates a registry.
Apply normalization rules to fix legacy field names and add missing defaults:
# Normalize in place
mtcvctm normalize credential.vctm.json
# Preview changes without modifying
mtcvctm normalize --dry-run -v credential.vctm.json
# List available rules
mtcvctm normalize --list-rules
# Disable specific rules
mtcvctm normalize --disable-rules remove-empty-description credential.vctm.jsonmtcvctm batch --github-action --vctm-branch vctm --commit-message "Update VCTM files"---
vct: https://example.com/credentials/identity
background_color: "#1a365d"
text_color: "#ffffff"
---
# Identity Credential
A verifiable credential for identity verification.
## Claims
- `given_name` "Given Name" (string): The given name of the holder [mandatory]
- de-DE: "Vorname" - Der Vorname des Inhabers
- sv: "Förnamn" - Innehavarens förnamn
- `family_name` "Family Name" (string): The family name of the holder [mandatory]
- `birth_date` (date): Date of birth [sd=always]
- `nationality` (string): Nationality of the holder
## Images
The optional YAML front matter supports:
| Key | Description |
|---|---|
vct |
Verifiable Credential Type identifier |
background_color |
Background color for credential display |
text_color |
Text color for credential display |
extends |
Comma-separated list of VCT identifiers this type extends |
Claims are defined in list items with the following format:
- `claim_name` "Display Name" (type): Description [mandatory] [sd=always|never]
- locale: "Localized Label" - Localized description
- claim_name: The claim identifier (required)
- "Display Name": Human-readable display label for the claim (optional)
- type: The value type -
string,date,number, etc. (default:string) - Description: Human-readable description
- [mandatory]: Mark the claim as mandatory
- [sd=always|never]: Selective disclosure setting
Add translations as nested list items under a claim:
- `given_name` "Given Name" (string): The given name [mandatory]
- de-DE: "Vorname" - Der Vorname des Inhabers
- sv: "Förnamn" - Innehavarens förnamn
- fr: "Prénom" - Le prénom du titulaireThe format for localization items is:
- locale: "Label" - Description
Where locale is a BCP 47 language tag (e.g., en-US, de-DE, sv).
Images referenced in the markdown become:
- The first image becomes the credential logo
- SVG files become SVG templates for rendering
By default, images are embedded as base64 data URLs in the VCTM, making the output self-contained without external dependencies. Use --no-inline-images to generate URLs instead (requires --base-url).
Configuration can be provided via:
- YAML configuration file
- Command line arguments (take priority)
input: credential.md
output: credential.vctm
base_url: https://registry.example.com
language: en-US
vctm_branch: vctm
inline_images: true # Default: images embedded as data URLsUse mtcvctm as a GitHub Action to automatically generate VCTM files:
name: Update VCTM
on:
push:
branches: [main]
paths:
- 'credentials/**/*.md'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sirosfoundation/mtcvctm@v1
with:
input-dir: ./credentials
output-dir: ./vctm
base-url: https://registry.example.com
vctm-branch: vctm| Input | Description | Default |
|---|---|---|
input-dir |
Directory containing markdown files | . |
output-dir |
Output directory for VCTM files | . |
base-url |
Base URL for generating image URLs | `` |
vctm-branch |
Branch name for VCTM files | vctm |
commit-message |
Commit message for updates | Update VCTM files [skip ci] |
no-inline-images |
Use URLs instead of embedding images | false |
normalize |
Apply normalization rules | false |
disable-rules |
Comma-separated list of rules to disable | `` |
verbose-rules |
Show which normalization rules were applied | false |
| Output | Description |
|---|---|
registry-path |
Path to the generated registry file |
credential-count |
Number of credentials processed |
The tool generates a .well-known/vctm-registry.json file:
{
"version": "1.0",
"generated": "2024-01-15T10:00:00Z",
"repository": {
"url": "https://github.com/org/repo",
"owner": "org",
"name": "repo",
"branch": "main",
"commit": "abc123"
},
"credentials": [
{
"vct": "https://example.com/credentials/identity",
"name": "Identity Credential",
"source_file": "identity.md",
"vctm_file": "identity.vctm",
"last_modified": "2024-01-15T10:00:00Z",
"commit_history": [...]
}
]
}mtcvctm includes an extensible rules engine for normalizing VCTM data. Rules can fix legacy field names, add missing required fields, and clean up empty values.
| Rule | Description |
|---|---|
ensure-display-array |
Ensure display is an array, wrapping single objects |
rename-lang-to-locale |
Rename legacy lang to locale in display entries |
rename-lang-to-locale-in-claims |
Rename lang to locale in claim displays |
set-display-locale-default |
Set display locale to en-US if missing |
set-display-name-from-root |
Copy root name to display entries if missing |
set-claim-display-locale-default |
Set claim display locale if missing |
remove-empty-svg-template-properties |
Remove empty properties: {} from svg_templates |
remove-empty-description |
Remove empty description fields |
Enable normalization in your workflow:
- uses: sirosfoundation/mtcvctm@v1
with:
input-dir: ./credentials
output-dir: ./vctm
normalize: true
verbose-rules: trueOr via CLI:
# With batch command
mtcvctm batch --input ./credentials --output ./vctm --normalize --verbose-rules
# With publish-vctm command
mtcvctm publish-vctm --input ./vctm-files --output ./vctm --verbose-rules
# Normalize standalone
mtcvctm normalize --dry-run -v credential.vctm.jsonThe rules engine is extensible. Add custom rules by implementing the Rule interface:
import "github.com/sirosfoundation/mtcvctm/pkg/rules"
myRule := rules.NewRule(
"my-custom-rule",
"Description of what the rule does",
func(data map[string]interface{}) (bool, error) {
// Transform data, return true if changed
return false, nil
},
)
engine := rules.NewEngine()
engine.Register(myRule)make buildmake testmake coveragemake docker-buildBSD 2-Clause License - Copyright (c) 2026 Siros Foundation. See LICENSE for details.