easeo
Deterministic SEO metadata generation for content platforms.
Generate canonical URLs, titles, descriptions, robots directives, Open Graph, Twitter Cards, and JSON-LD from content entities. One Rust core with Python, Node.js, and Rust APIs.
What is easeo
easeo is a deterministic SEO metadata generator. It takes a content entity, the route it lives at, and a site-wide configuration, and returns a complete SEO payload: canonical URL, title, description, robots directives, Open Graph, Twitter Cards, and JSON-LD.
All logic lives in the Rust core (easeo-core). The Python and Node.js packages wrap the same engine, so the same entity, route, and config produce identical output in every language. The cross-language conformance suite asserts that byte for byte.
The tutorial walks through installation and a first payload. Concepts explains the entity model, fallback chains, URL normalization, and validation rules.
Content Entity
|
v
easeo
|
+-- canonical URL
+-- title & description
+-- robots directives
+-- Open Graph
+-- Twitter Cards
+-- JSON-LD
|
v
Deterministic Payload
Quick start
Every binding exposes the same call: pass an entity, a route, and a config, and get a payload back. The full tutorial covers configuration, overrides, rendering, and contracts.
Python
pip install easeo
from easeo import SEOConfig, SEOEntity, build_seo_payload
config = SEOConfig(
canonical_host="example.com",
public_base_url="https://example.com",
)
entity = SEOEntity(
entity_type="post",
title="Hello World",
excerpt="An example post.",
)
payload = build_seo_payload(entity, "/blog/hello", config)
print(payload.render_html())
JavaScript / TypeScript
npm install @easeo/core
import { buildSeoPayload } from "@easeo/core";
const payload = buildSeoPayload(
{ entityType: "post", title: "Hello World", description: "An example post." },
"/blog/hello",
{ canonicalHost: "example.com", publicBaseUrl: "https://example.com" }
);
console.log(payload.renderHtml());
Rust
cargo add easeo-core
use easeo_core::{SEOConfig, SEOEntity, EntityType, build_seo_payload};
let config = SEOConfig {
canonical_host: "example.com".into(),
public_base_url: "https://example.com".into(),
..Default::default()
};
let entity = SEOEntity {
entity_type: EntityType::Post,
title: Some("Hello World".into()),
excerpt: Some("An example post.".into()),
..Default::default()
};
let payload = build_seo_payload(&entity, "/blog/hello", &config).unwrap();
assert_eq!(payload.canonical, "https://example.com/blog/hello");
Why easeo
Other SEO libraries do too much (scoring, keyword analysis, content rewriting), while easeo does one thing and does it well: generate deterministic SEO metadata from content entities.
Given the same inputs, easeo always produces the same output. No timestamps, no randomness, no environment reads, no hidden I/O. This makes SEO metadata something you can:
- snapshot test: assert against expected payloads
- hash: generate stable ETags
- cache: safely memoize results
- diff: detect changes across builds
- validate in CI: commit your SEO intent as code with contracts
See Why easeo for the design goals and a comparison with other SEO tooling.
What easeo is not
- Not a scoring or keyword analysis tool.
- Not a crawler or site auditor.
- Not a content rewriter or generator.
- Not a runtime service. It is a pure function over its inputs.
- Not a full page renderer. It renders a head snippet, not a document.
Features
- 11 entity types: home, post, page, video, taxonomy, search, product, organization, local_business, faq, other. See the entity model
- URL normalization: HTTPS enforcement, lowercase paths, trailing slash control, duplicate slash collapse, tracking parameter removal. See URL normalization
- JSON-LD schemas: WebSite, WebPage, Article, Product, Organization, LocalBusiness, FAQPage, BreadcrumbList, plus custom schemas via registry. See JSON-LD schemas and custom JSON-LD
- Open Graph: full og:type, title, description, image, locale, audio, video
- Twitter Cards: summary_large_image by default, with image alt, site, creator
- Robots directives: index/noindex, follow/nofollow, max-snippet, max-image-preview, max-video-preview
- SEO contracts: machine-readable SEO intent for CI validation. See the contract reference
- Validation: built-in checks for title length, description, canonical format, OG image, robots directives. See validation
- Hashing: SHA-256 payload hashing and ETag generation
- HTML rendering: complete head snippet with meta tags, OG, Twitter, JSON-LD
- Extension points: config-scoped hooks and schema registries. See hooks
Learn more in the guides and start from a recipe such as a blog post, product page, or search results page.
Packages
| Package | Registry | Purpose |
|---|---|---|
easeo |
PyPI | Python bindings for the Rust core |
easeo[fastapi], easeo[django], easeo[flask], easeo[zensical] |
PyPI extras | Framework adapters and the docs extension |
@easeo/core |
npm | Node.js bindings for the Rust core |
easeo-core |
crates.io | Pure Rust engine with no I/O dependencies |
Integrations
Per-framework setup guides live in the Integrations docs.
JavaScript / TypeScript
| Framework | Package | API |
|---|---|---|
| Next.js | @easeo/next |
easeoMetadata() returns a Next.js Metadata object |
| Astro | @easeo/astro |
Build-time integration with contract emission |
| Vite | @easeo/vite |
transformIndexHtml hook |
| Nuxt | @easeo/nuxt |
useEaseoSeo() composable |
| SvelteKit | @easeo/sveltekit |
buildEaseoPayload() and <EaseoHead /> |
| React | @easeo/react |
<EaseoHead /> component |
Python
| Framework | Import |
|---|---|
| FastAPI | from easeo.adapters.fastapi import EaseoSEO |
| Django | from easeo.adapters.django import seo_head |
| Flask | from easeo.adapters.flask import Easeo |
| Zensical | from easeo.contrib.zensical import EaseoExtension |
API
| Symbol | Description |
|---|---|
build_seo_payload(entity, route_path, config, overrides=None) |
Builds the deterministic SEOPayload |
SEOConfig |
Site-wide configuration: host, base URL, defaults, hooks, registries |
SEOEntity |
Content entity: type, title, excerpt, images, dates, breadcrumbs, FAQ, product fields |
SEOOverrides |
Highest-precedence per-call overrides |
SEOPayload |
Output with render_html(), to_dict(), to_json(), hash(), and etag() |
URLPolicy |
Canonical URL normalization policy |
build_seo_contract |
Builds a machine-readable SEO contract |
validate_payload |
Runs the built-in validation checks |
HookRegistry, SchemaRegistry |
Config-scoped post-processing and JSON-LD generators |
SEOEntityBuilder, from_blog_post, from_product, from_faq |
Ergonomic entity constructors |
Names are shown in Python. JavaScript uses camelCase and Rust uses snake_case. Full signatures live in the Python, JavaScript, and Rust API references.
Documentation
- Tutorial: installation, first payload, rendering, contracts, configuration
- Concepts: determinism, entity and payload models, fallback chains, URL normalization, schemas, validation
- Guides: custom JSON-LD, hooks, contracts in CI, framework recipes
- Reference: Python, JavaScript, and Rust APIs, contracts, errors
- Integrations: per-framework setup
- Recipes: blog post, product page, category page, search results
- Examples: core and framework examples for every binding
LLM-friendly versions: llms.txt and llms-full.txt.
Building
# Rust core
cargo build --workspace --release
# Python bindings
pip install maturin
maturin develop -m crates/easeo-python/Cargo.toml
# Node bindings
npm install -g @napi-rs/cli@^3
cd packages/core
napi build --platform --release --manifest-path ../../crates/easeo-node/Cargo.toml
cp ../../crates/easeo-node/*.node .
Testing
# Rust
cargo test --workspace
# Python
pytest tests/python/
# JavaScript
node --test tests/javascript/*.cjs
# Cross-language conformance
python tests/conformance/test_conformance.py
Next steps
- Follow the tutorial from installation to first payload
- Commit SEO intent with contracts in CI
- Extend structured data with custom schemas
- Read how fields resolve in fallback chains
- Browse examples for every binding and framework
Contributors
- Emiliano Gandini Outeda: creator and maintainer
License
MIT
Metadata
Release files for easeo 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| easeo-0.1.1-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| easeo-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| easeo-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| easeo-0.1.1-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| easeo-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 3.7 MB
Release files / easeo-0.1.1-cp310-abi3-win_amd64.whl
| Download URL | easeo-0.1.1-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 621.8 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c0c8f5bd8487fe13673ccf3f6e327089c3e25f35ffa932fe5167a92275b5f935
|
|
BLAKE2b-256 checksum How to use checksums |
d7353fe75b69cbdb4e15cb5f9b76d233015b968a5c5139680bfc21ea582db916
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / easeo-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | easeo-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 798.4 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e90d8ca8f6381b659ccfe03dea4aa5e664b3f6c50ed4d592676cb1b424c24799
|
|
BLAKE2b-256 checksum How to use checksums |
2a161387a45fc564ae9f87c3a08f208e7d43d68acb5c8ff9f4999e937a926aac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / easeo-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | easeo-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 784.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
11d87e43110e1fcb8257a9d0847936373cc05af4145c2b6e173ee84611be839c
|
|
BLAKE2b-256 checksum How to use checksums |
8e33cd9517d3f6a0098565fb10bbff6045687d4027fb6c5a6ed0ccc2f25b363e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / easeo-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | easeo-0.1.1-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 723.7 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8fbe6e28c055b04c281e45620cfbc554eb2b61041f27c01960cd942b60729d25
|
|
BLAKE2b-256 checksum How to use checksums |
b98a678f11c7fd0a47756c9457186b9440b7d4abb4f4b4a812edabf06d4437b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / easeo-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | easeo-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 734.4 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
e2f333afeb67ebc4ebafe4956a19ced5045d2a04e61caa51f94013deb3c3c5a6
|
|
BLAKE2b-256 checksum How to use checksums |
ee4283bed92c03bcfc4d185f96190f2b1152630df0ff9538f7f98be268b2e221
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log