Tools for building content-driven sites with Conteditor-managed Firestore and Astro.
| Package | Description |
|---|---|
@contedra/core |
Core library — Firebase connection, model parsing, and Zod schema generation |
@contedra/astro-loader-firestore |
Astro Content Layer loader for Conteditor Firestore |
@contedra/md-importer |
CLI tool to import Markdown files + images into Firestore |
pnpm add @contedra/astro-loader-firestoreDefine a content collection in your Astro project:
// src/content/config.ts
import { defineCollection } from "astro:content";
import { contedraLoader } from "@contedra/astro-loader-firestore";
const blogPosts = defineCollection({
loader: contedraLoader({
modelFile: "./models/blog_posts.json",
firebaseConfig: {
projectId: "your-project-id",
credential: "./service-account.json",
},
}),
});
export const collections = { blogPosts };| Option | Type | Required | Description |
|---|---|---|---|
modelFile |
string |
Yes | Path to the Conteditor model JSON file |
firebaseConfig.projectId |
string |
Yes | Firebase project ID |
firebaseConfig.credential |
string |
No | Path to service account JSON (uses ADC if omitted) |
collection |
string |
No | Firestore collection name (defaults to modelName) |
bodyField |
string |
No | Field to map to Astro's body (auto-detects element: "markdown" fields) |
Conteditor dataType |
Zod Schema | Description |
|---|---|---|
string |
z.string() |
Plain string |
datetime |
z.coerce.date() |
Firestore Timestamp converted to Date |
relatedOne |
z.string() |
Referenced document ID |
relatedMany |
z.array(z.string()) |
Array of referenced document IDs |
With credential option (local development):
firebaseConfig: {
projectId: "your-project-id",
credential: "./service-account.json",
}Without credential (Application Default Credentials):
firebaseConfig: {
projectId: "your-project-id",
}When credential is omitted, the loader uses Application Default Credentials (ADC). Set the GOOGLE_APPLICATION_CREDENTIALS environment variable or use Workload Identity Federation in CI/CD environments.
pnpm add @contedra/md-importernpx @contedra/md-importer \
--md-dir ./content \
--model ./models/blog_posts.json \
--project-id your-project-id \
--credential ./service-account.json \
--storage-bucket your-project-id.firebasestorage.appCLI Options:
| Option | Required | Description |
|---|---|---|
--md-dir <path> |
Yes | Directory containing .md files |
--model <path> |
Yes | Path to model definition JSON |
--project-id <id> |
Yes | Firebase project ID |
--credential <path> |
No | Path to service account JSON (uses ADC if omitted) |
--collection <name> |
No | Firestore collection name (defaults to modelName) |
--storage-bucket <name> |
No* | Firebase Storage bucket name (e.g. your-project.firebasestorage.app) |
--no-images |
No | Skip image extraction, upload, and URL replacement |
--field-mapping <json> |
No | JSON mapping frontmatter keys to model properties |
*
--storage-bucketis required unless--no-imagesis set.
Example with field mapping:
npx @contedra/md-importer \
--md-dir ./content \
--model ./models/blog_posts.json \
--project-id your-project-id \
--field-mapping '{"article_title":"title","article_date":"publishedAt"}'import { mdImporter } from "@contedra/md-importer";
const result = await mdImporter({
mdDir: "./content",
modelFile: "./models/blog_posts.json",
firebaseConfig: {
projectId: "your-project-id",
credential: "./service-account.json",
},
fieldMapping: {
article_title: "title",
article_date: "publishedAt",
},
});
console.log(`Imported: ${result.imported.length}`);
console.log(`Errors: ${result.errors.length}`);By default, images referenced in markdown are read from the local filesystem relative to the .md file and uploaded to Firebase Storage. You can provide a custom resolver:
const result = await mdImporter({
mdDir: "./content",
modelFile: "./models/blog_posts.json",
firebaseConfig: { projectId: "your-project-id" },
resolveImage: async (imagePath, mdFilePath) => {
// Custom logic — e.g., fetch from a remote URL or CDN
const response = await fetch(`https://cdn.example.com/${imagePath}`);
return Buffer.from(await response.arrayBuffer());
},
});Both the Astro loader and Markdown importer use Conteditor model definition JSON files to define the schema of your content:
{
"id": "blog_posts",
"modelName": "blog_posts",
"properties": [
{ "propertyName": "title", "dataType": "string", "require": true },
{ "propertyName": "content", "dataType": "string", "fieldType": { "element": "markdown" } },
{ "propertyName": "publishedAt", "dataType": "datetime" },
{ "propertyName": "category", "dataType": "relatedOne", "relatedModel": "categories" },
{ "propertyName": "tags", "dataType": "relatedMany", "relatedModel": "tags" }
]
}Supported dataType values: string, datetime, relatedOne, relatedMany
A property with "fieldType": { "element": "markdown" } is automatically detected as the body field for Astro's content rendering.
A demo Astro project is included in the demo/ directory. It uses the Firestore emulator with sample blog posts and tags. See demo/README.md for setup instructions.
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test
# Lint
pnpm lintAll packages share the same version number. Use the built-in scripts to bump versions:
# Bump patch version (e.g., 0.1.0 → 0.1.1)
pnpm version:patch
# Bump minor version (e.g., 0.1.0 → 0.2.0)
pnpm version:minor
# Bump major version (e.g., 0.1.0 → 1.0.0)
pnpm version:majorUse the release script to bump all packages, commit, tag, push, and create a GitHub release in one step:
./scripts/release.sh patch # 0.1.0 → 0.1.1
./scripts/release.sh minor # 0.1.1 → 0.2.0
./scripts/release.sh major # 0.2.0 → 1.0.0This will:
- Bump the version in all packages by the specified type
- Commit and tag as
v<new-version> - Push to
mainwith tags - Create a GitHub release with auto-generated notes
Pushing the tag triggers the publish workflow, which builds and publishes all packages to npm.
Packages are published to npm under the @contedra scope via GitHub Actions. Pushing a version tag (e.g., v0.1.1) triggers the publish workflow, which builds and publishes all packages.
Requirements: Set the NPM_TOKEN secret in your GitHub repository settings.
MIT