Dataset Schema Framework

Use conditional-logic schema generation to keep Dataset markup aligned with real page state and avoid stale or invalid static templates.

When To Use It

Google Search CentralDataset documentation

Add Dataset markup to the canonical landing page for each dataset. Results surface in Google Dataset Search, not as a standard Google Search rich result, so success means the dataset is discoverable and correctly described there.

Key Implementation Documentation Highlights

  • Where it shows: datasets appear in Google Dataset Search, not as a rich result in standard Google Search. Mark up the canonical landing page of each dataset.
  • What counts as a dataset: a table or CSV file, an organized collection of tables, a proprietary data file, a collection of files that together form a meaningful dataset, a structured object in another format, images capturing data, or files relating to machine learning.
  • Required properties: name and description. The description is a summary between 50 and 5000 characters; the name is descriptive (for example "Snow depth in the Northern Hemisphere") and unique for distinct datasets whenever possible.
  • Recommended properties: alternateName, creator, citation, funder, hasPart, isPartOf, identifier, isAccessibleForFree, keywords, license, measurementTechnique, sameAs, spatialCoverage, temporalCoverage, variableMeasured, version, url, includedInDataCatalog, distribution.
  • creator and funder: a Person (name, givenName, familyName, sameAs) or an Organization (name, url, sameAs). Identify people with an ORCID ID and institutions with a ROR ID in sameAs.
  • identifier: a DOI or Compact Identifier, repeated when the dataset has more than one. citation lists academic articles the provider recommends citing in addition to the dataset; don't use it for the dataset's own citation, use identifier for that.
  • distribution: DataDownload objects with a required contentUrl (the download link) and a recommended encodingFormat (the file format).
  • temporalCoverage: ISO 8601 date or interval, such as 2008, 1950-01-01/2013-12-18, or 2013-12-19/.. for an open-ended interval.
  • spatialCoverage: a named place as text, a GeoCoordinates point, or a GeoShape box, circle, line or polygon. Points are space separated latitude longitude pairs, latitude first; a box is lat1 long1 lat2 long2.
  • license: a URL that unambiguously identifies the specific license version. Collections use hasPart on the parent and isPartOf on the smaller datasets.
  • Republished datasets: point sameAs at the canonical page; use isBasedOn when the copy has significant metadata changes or is derived or aggregated from other sources.
  • Google expects content parity: every marked-up value must match what users can see on the landing page. Validate with the Rich Results Test and allow several days for Google to crawl and index new pages.

Template Approach

const pageData = {
  title: "Charles River Hourly Water Temperature, 2016 to 2025",
  summary: "Hourly water temperature readings from 12 monitoring stations along the Charles River, Massachusetts, collected from 2016 to 2025.",
  pageUrl: "https://www.example.com/data/charles-river-water-temperature",
  licenseUrl: "https://creativecommons.org/licenses/by/4.0/",
  publisher: "Example River Institute"
};

const staticTemplate = {
  "@context": "https://schema.org",
  "@type": "Dataset",
  "name": pageData.title,
  "description": pageData.summary,
  "url": pageData.pageUrl,
  "license": pageData.licenseUrl,
  "creator": {
    "@type": "Organization",
    "name": pageData.publisher
  }
};

Conditional-logic Framework

const ISO_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?(Z|[+-]\d{2}:\d{2})?)?$/;

function isAbsoluteUrl(value) {
  return typeof value === "string" && /^https?:\/\/[^\s/?#]+\.[^\s/?#]+(?:[/?#]\S*)?$/i.test(value);
}

// Returns the ISO 8601 string unchanged (timezone offset kept), or null if unparseable.
function toIsoDateTime(value) {
  if (typeof value !== "string") return null;
  const text = value.trim();
  const m = text.match(ISO_DATE_TIME);
  if (!m) return null;
  const [, y, mo, d, h = "00", mi = "00", s = "00"] = m;
  const date = new Date(Date.UTC(+y, +mo - 1, +d));
  if (date.getUTCMonth() !== +mo - 1 || date.getUTCDate() !== +d) return null;
  if (+h > 23 || +mi > 59 || +s > 59) return null;
  return text;
}

function toIsoDate(value) {
  const iso = toIsoDateTime(value);
  return iso ? iso.slice(0, 10) : null;
}

// Drops null, undefined, "", [] and {} recursively.
function compact(value) {
  if (Array.isArray(value)) {
    const items = value.map(compact).filter((v) => v !== undefined);
    return items.length ? items : undefined;
  }
  if (value && typeof value === "object") {
    const out = {};
    for (const [key, v] of Object.entries(value)) {
      const c = compact(v);
      if (c !== undefined) out[key] = c;
    }
    return Object.keys(out).length ? out : undefined;
  }
  return value === null || value === undefined || value === "" ? undefined : value;
}

function passesPageGates(source) {
  if (source.indexable === false) return false;
  if (source.canonicalUrl && source.url && source.canonicalUrl !== source.url) return false;
  return source.contentVisible !== false;
}


const ORCID = /^https:\/\/orcid\.org\/\d{4}-\d{4}-\d{4}-\d{3}[\dX]$/;
const ROR = /^https:\/\/ror\.org\/0[a-hj-km-np-tv-z0-9]{6}\d{2}$/;
const DOI = /^10\.\d{4,9}\/\S+$/;
const PARTIAL_DATE = /^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$/;

function textLength(value) {
  return typeof value === "string" ? value.trim().length : 0;
}

// "2016", "2016-05", "2016-05-01" or a full ISO 8601 date time.
function toTemporalPoint(value) {
  const m = typeof value === "string" ? value.match(PARTIAL_DATE) : null;
  if (m && m[3]) return toIsoDate(value);
  if (m) return !m[2] || (+m[2] >= 1 && +m[2] <= 12) ? value : null;
  return value && value.includes("T") ? toIsoDateTime(value) : null;
}

// ISO 8601 date or interval: "2008", "1950-01-01/2013-12-18", or "2013-12-19/.." for an open end.
function toTemporalCoverage(value) {
  if (typeof value !== "string") return null;
  const [start, end, extra] = value.trim().split("/");
  if (extra !== undefined || !toTemporalPoint(start)) return null;
  if (end === undefined) return start;
  if (end === "..") return `${start}/..`;
  if (!toTemporalPoint(end)) return null;
  const first = start.length === 4 ? `${start}-01-01` : start.length === 7 ? `${start}-01` : start.slice(0, 10);
  const last = end.length === 4 ? `${end}-12-31` : end.length === 7 ? `${end}-31` : end.slice(0, 10);
  return first <= last ? `${start}/${end}` : null; // the interval must not run backwards
}

function isLatLong(lat, long) {
  return typeof lat === "number" && typeof long === "number" && Math.abs(lat) <= 90 && Math.abs(long) <= 180;
}

// A named place, a GeoCoordinates point, or a GeoShape box ("lat1 long1 lat2 long2", latitude first).
function buildSpatialCoverage(place) {
  if (typeof place === "string") return place;
  if (!place) return null;
  if (place.point) {
    const { lat, long } = place.point;
    if (!isLatLong(lat, long)) return null;
    return { "@type": "Place", name: place.name, geo: { "@type": "GeoCoordinates", latitude: lat, longitude: long } };
  }
  if (Array.isArray(place.box) && place.box.length === 4) {
    const [lat1, long1, lat2, long2] = place.box;
    if (!isLatLong(lat1, long1) || !isLatLong(lat2, long2)) return null;
    return { "@type": "Place", name: place.name, geo: { "@type": "GeoShape", box: place.box.join(" ") } };
  }
  return place.name || null;
}

// Person: name, givenName, familyName, url, ORCID sameAs. Organization: name, url, ROR sameAs.
function buildAgent(agent) {
  if (!agent || !agent.name) return null;
  if (agent.type === "person") {
    return {
      "@type": "Person",
      name: agent.name,
      givenName: agent.givenName,
      familyName: agent.familyName,
      url: isAbsoluteUrl(agent.url) ? agent.url : null,
      sameAs: ORCID.test(agent.orcid || "") ? agent.orcid : null
    };
  }
  return {
    "@type": "Organization",
    name: agent.name,
    url: isAbsoluteUrl(agent.url) ? agent.url : null,
    sameAs: ROR.test(agent.ror || "") ? agent.ror : null
  };
}

// Bare DOIs become resolvable https://doi.org/ URLs; other identifiers stay as given.
function toIdentifier(value) {
  if (typeof value !== "string" || !value.trim()) return null;
  const text = value.trim();
  return DOI.test(text) ? `https://doi.org/${text}` : text;
}

function buildVariable(variable) {
  if (typeof variable === "string") return variable;
  if (!variable || !variable.name) return null;
  return { "@type": "PropertyValue", name: variable.name, unitText: variable.unit, description: variable.description };
}

function buildDistribution(file) {
  // contentUrl is required on every DataDownload.
  if (!file || !isAbsoluteUrl(file.contentUrl)) return null;
  return { "@type": "DataDownload", contentUrl: file.contentUrl, encodingFormat: file.encodingFormat };
}

// A related dataset as a URL, or as a Dataset with its own name and valid description.
function buildRelatedDataset(part) {
  if (isAbsoluteUrl(part)) return part;
  const length = textLength(part && part.description);
  if (!part || !part.name || length < 50 || length > 5000) return null;
  return { "@type": "Dataset", name: part.name, description: part.description.trim(), url: isAbsoluteUrl(part.url) ? part.url : null };
}

function buildDatasetSchema(source) {
  // (a) Page gates: the indexable, self-canonical landing page for this dataset, with the metadata visible.
  if (!passesPageGates(source) || !isAbsoluteUrl(source.url)) return null;

  // (b) Required: name, and a description between 50 and 5000 characters.
  const length = textLength(source.description);
  if (!source.name || length < 50 || length > 5000) return null;

  const identifier = (source.identifiers || []).map(toIdentifier).filter(Boolean);
  const isOwnCitation = (c) => identifier.includes(toIdentifier(c));

  // (c) Recommended properties, only when the source has them; (d) value rules inline.
  const schema = {
    "@context": "https://schema.org",
    "@type": "Dataset",
    "@id": `${source.url}#dataset`,
    name: source.name,
    description: source.description.trim(),
    alternateName: source.alternateNames,
    url: source.url,
    sameAs: (source.sameAs || []).filter(isAbsoluteUrl), // the canonical page when republished
    identifier, // repeat for multiple DOIs or Compact Identifiers
    version: source.version,
    keywords: source.keywords,
    license: isAbsoluteUrl(source.license) ? source.license : null, // a URL for one specific license version
    isAccessibleForFree: typeof source.free === "boolean" ? source.free : null,
    creator: (source.creators || []).map(buildAgent),
    funder: (source.funders || []).map(buildAgent),
    citation: (source.citations || []).filter((c) => !isOwnCitation(c)), // related articles, not the dataset itself
    temporalCoverage: toTemporalCoverage(source.temporalCoverage),
    spatialCoverage: buildSpatialCoverage(source.spatialCoverage),
    variableMeasured: (source.variables || []).map(buildVariable),
    measurementTechnique: source.measurementTechniques,
    isPartOf: (source.partOf || []).map(buildRelatedDataset),
    hasPart: (source.parts || []).map(buildRelatedDataset),
    includedInDataCatalog: source.catalog && source.catalog.name
      ? { "@type": "DataCatalog", name: source.catalog.name, url: isAbsoluteUrl(source.catalog.url) ? source.catalog.url : null }
      : null,
    distribution: (source.files || []).map(buildDistribution)
  };

  // (e) Strip empty values.
  return compact(schema);
}

// Usage:
// const source = {
//   "url": "https://www.example.com/data/charles-river-water-temperature",
//   "canonicalUrl": "https://www.example.com/data/charles-river-water-temperature",
//   "indexable": true,
//   "contentVisible": true,
//   "name": "Charles River Hourly Water Temperature, 2016 to 2025",
//   "description": "Hourly water temperature readings from 12 monitoring stations along the Charles River, Massachusetts, collected from January 2016 to December 2025 and quality controlled in 2026.",
//   "alternateNames": [
//     "CRWT 2016-2025"
//   ],
//   "identifiers": [
//     "10.5555/example.crwt.2026"
//   ],
//   "version": "2026.1",
//   "keywords": [
//     "water temperature",
//     "rivers",
//     "hydrology",
//     "Massachusetts"
//   ],
//   "license": "https://creativecommons.org/licenses/by/4.0/",
//   "free": true,
//   "creators": [
//     {
//       "type": "person",
//       "name": "Josiah Carberry",
//       "givenName": "Josiah",
//       "familyName": "Carberry",
//       "orcid": "https://orcid.org/0000-0002-1825-0097",
//       "url": "https://www.example.com/people/josiah-carberry"
//     }
//   ],
//   "funders": [
//     {
//       "type": "organization",
//       "name": "Example Watershed Foundation",
//       "url": "https://foundation.example.com/",
//       "ror": "https://ror.org/0abcdef12"
//     }
//   ],
//   "citations": [
//     "https://doi.org/10.5555/example.article.2025"
//   ],
//   "temporalCoverage": "2016-01-01/2025-12-31",
//   "spatialCoverage": {
//     "name": "Charles River watershed, Massachusetts",
//     "box": [
//       42.1,
//       -71.6,
//       42.4,
//       -71.0
//     ]
//   },
//   "variables": [
//     {
//       "name": "Water temperature",
//       "unit": "degrees Celsius"
//     }
//   ],
//   "measurementTechniques": [
//     "Submerged thermistor loggers, 60 minute sampling interval"
//   ],
//   "partOf": [
//     "https://www.example.com/data/new-england-river-observations"
//   ],
//   "parts": [
//     "https://www.example.com/data/charles-river-water-temperature/2025"
//   ],
//   "catalog": {
//     "name": "Example River Institute Data Catalog",
//     "url": "https://www.example.com/data/"
//   },
//   "files": [
//     {
//       "contentUrl": "https://www.example.com/data/files/crwt-2016-2025.csv",
//       "encodingFormat": "text/csv"
//     },
//     {
//       "contentUrl": "https://www.example.com/data/files/crwt-2016-2025.parquet",
//       "encodingFormat": "application/vnd.apache.parquet"
//     }
//   ],
//   "sameAs": [
//     "https://catalog.example.org/records/crwt-2016-2025"
//   ]
// };
// const schema = buildDatasetSchema(source); // null when a gate or required property fails

Why Conditional Logic Is Better Than Static Templates

  • Returns nothing when the description falls outside 50 to 5000 characters, instead of shipping a record Google rejects.
  • Normalizes bare DOIs into resolvable identifier URLs and drops citations that point back at the dataset itself.
  • Checks temporalCoverage intervals and GeoShape coordinate order and ranges before they reach the page.
  • Drops DataDownload entries without a contentUrl and emits optional fields only when the catalog record has them, so one function scales across every landing page.

Property Reference

What Google's Dataset documentation requires and recommends, property by property. These are the same rules the SchemaCDN validator checks. Each name links to its schema.org definition.

Dataset properties in Google's documentation
PropertyGoogleExpectsNotes
descriptionRequiredTextA short summary describing the dataset, between 50 and 5000 characters long.
nameRequiredTextA descriptive name of the dataset. Use unique names for distinct datasets.
alternateNameRecommendedText, can repeatAlternative names that have been used to refer to this dataset, such as aliases or abbreviations.
creatorRecommendedPerson or Organization, can repeatThe creator or author of this dataset (Person or Organization), ideally identified with ORCID or ROR IDs in sameAs.
creator.nameRecommendedText
creator.sameAsRecommendedURL, can repeatAn ORCID ID (person) or ROR ID (organization).
creator.urlRecommendedURLFor organizations.
creator.givenNameRecommendedTextFor persons.
creator.familyNameRecommendedTextFor persons.
citationRecommendedText or CreativeWork, can repeatAcademic articles the data provider recommends be cited in addition to the dataset itself.
funderRecommendedPerson or Organization, can repeatA person or organization that provides financial support for this dataset.
hasPartRecommendedURL or Dataset, can repeatUse when this dataset is a collection of smaller datasets.
isPartOfRecommendedURL or Dataset, can repeatUse when this dataset is part of a larger dataset collection.
identifierRecommendedURL, Text or PropertyValue, can repeatAn identifier such as a DOI or Compact Identifier. Repeat for multiple identifiers.
isAccessibleForFreeRecommendedBooleanWhether the dataset is accessible without payment.
keywordsRecommendedText, can repeatSummarizing the dataset.
licenseRecommendedURL or CreativeWorkA URL that unambiguously identifies the specific license version under which the dataset is distributed.
measurementTechniqueRecommendedText or URL, can repeatThe technique, technology, or methodology used in the dataset.
sameAsRecommendedURL, can repeatThe URL of a reference web page that unambiguously indicates the dataset's identity.
spatialCoverageRecommendedText or Place, can repeatA named place, a GeoCoordinates point, or a GeoShape describing the dataset's spatial aspect.
temporalCoverageRecommendedTextThe time interval the data covers, in ISO 8601 format (for example 1950-01-01/2013-12-18 or 2013-12-19/..).
variableMeasuredRecommendedText or PropertyValue, can repeatThe variable that this dataset measures, for example temperature or pressure.
versionRecommendedText or NumberThe version number for the dataset.
urlRecommendedURLLocation of a page describing the dataset.
includedInDataCatalogRecommendedDataCatalogThe catalog to which the dataset belongs.
distributionRecommendedDataDownload, can repeatDataDownload objects describing where to download the dataset and in which format.
distribution.contentUrlRequiredURLThe link for the download.
distribution.encodingFormatRecommendedText or URLThe file format of the distribution.

Explore Other Schema Framework Pages

Discussion Forum Use for forum and community threads where a primary post and subsequent replies are publicly visible. DiscussionForumPosting Open Limited Education Q&A Use for educational question-answer pages where a single canonical answer and supporting context are visible. Quiz Open Employer Aggregate Rating Use on pages that publish employer rating aggregates from a clear and policy-compliant review source. EmployerAggregateRating Open Retired Estimated Salary Use for compensation insight pages that present transparent salary ranges for specific occupations and locations. Occupation Open Event Use for upcoming events with a real-world or virtual occurrence, schedule, and location context. Event Open Retired Fact Check Use for fact-checking pages that evaluate a specific claim with a transparent rating methodology. ClaimReview Open Retired Home Activities Use for virtual classes and activities where participation details and timing are clearly listed. Event Open Retired How-To Use for instructional content that teaches a process through ordered steps and actionable detail. HowTo Open Image Metadata Use on image pages and image hosts that expose ownership and licensing information to search engines. ImageObject Open Job Posting Use for active job listings that include role details, hiring entity, and location/salary context. JobPosting Open Retired Learning Video Use for educational videos where the learning objective and pedagogical structure are explicit. LearningResource Open Local Business Use for physical businesses serving local customers with publicly verifiable contact and location details. LocalBusiness Open Loyalty Program Use to publish a loyalty program and its tiers once at the organization level, so member prices and member shipping can reference them. MemberProgram Open Math Solvers Use for pages that solve mathematical expressions and present step-by-step computational guidance. MathSolver Open Merchant Listing Use on pages where a product can be bought, with price, availability, shipping and return details. Product Open Limited Movie Use for movie detail pages with canonical film information and transparent metadata attribution. Movie Open Organization Use for official organization pages to clarify brand identity and authoritative entity signals. Organization Open Retired Practice Problems Use for educational practice content that presents solvable problems and answer validation structure. Quiz Open Product Snippet Use for product pages where pricing, availability, and product identity are visible to users. Product Open Product Variants Use when one product comes in variants such as size or color, grouped with ProductGroup. ProductGroup Open Profile Page Use for creator or contributor profile pages where identity attributes and ownership are explicit. ProfilePage Open Q&A Use for pages where users submit questions and community or editorial answers are displayed. QAPage Open Recipe Use for recipe pages with complete ingredients, instructions, and preparation details. Recipe Open Return Policy Use to publish a merchant return policy once at the organization level, with offer-level overrides. MerchantReturnPolicy Open Review Snippet Use when a page publishes genuine reviews tied to a clearly identified product, service, or entity. Review Open Shipping Policy Use to publish shipping rates, delivery times and destinations once at the organization level. ShippingService Open Site Name Use on the home page so Google shows the preferred site name in search results. WebSite Open Retired Sitelinks Search Box Use on sites with a functioning internal search endpoint that can accept URL query parameters. WebSite Open Software App (Beta) Use for software product pages that describe platform support and application category clearly. SoftwareApplication Open Limited Speakable Use for news-style pages where specific text segments are appropriate for text-to-speech playback. SpeakableSpecification Open Subscription and Paywalled Content Use for pages with restricted sections so Google can distinguish free previews from protected content. CreativeWork Open Limited Vacation Rental Use for rental property detail pages where booking-relevant amenities and location signals are explicit. VacationRental Open Retired Vehicle Listing Use for inventory pages selling vehicles with standardized listing fields and offer information. Car Open Video Use for pages where a primary video is embedded and key media metadata is visible on-page. VideoObject Open Article Use for editorial pages where the visible content is a complete article with a clear byline and publication timestamps. Article Open Limited Book Actions Use when your page helps users read, preview, or purchase books through clearly described actions. Book Open Breadcrumb Use on pages that belong to a clear hierarchy and display navigational trail links users can follow. BreadcrumbList Open Carousel Use when a page lists multiple related entities in a scannable sequence that can be surfaced as cards. ItemList Open Course Use for educational offerings where curriculum details, provider identity, and delivery context are explicit. Course Open

Implement Dataset Schema Frameworks Correctly

Get implementation support for conditional schema architecture, validation, and deployment governance.