AtlasSchema

Add structured data to any website

The AtlasSchema SDK builds correct schema.org structured data (JSON-LD) from plain fields and checks it with Google’s rich result rules. It runs in your own code, needs no account and sends nothing anywhere. Connect AtlasSchema when you want every page checked and watched. Free and open source (MIT).

On this page

How it works

  1. Build. Give a builder plain fields (a product’s name, price and currency) and get a correct schema.org node back. Required fields and value formats are checked as you build.
  2. Print. Put the page’s nodes in one graph and print it as a single JSON-LD script. The Organization, WebSite and WebPage get stable ids and point at each other, so search engines and AI assistants see one entity.
  3. Check. Run the same checks the AtlasSchema service runs (Google’s required and recommended properties, schema.org basics, price and stock against the page), offline, as often as you like.
  4. Monitor (optional). Send your pages to AtlasSchema with an API key. It checks the whole website, keeps history and emails you when something breaks.

There is one SDK in two languages with the same builders, the same checks and the same output: PHP for WordPress, Laravel, Symfony, Joomla, Drupal, CodeIgniter and plain PHP, and JavaScript for Node, Next.js, Nuxt, Astro, React and the browser. Any other language can use the API.

WordPress: nothing to install

The free Atlas Schema plugin has the PHP SDK built in. It prints one connected graph on every page (Organization, WebSite, WebPage, breadcrumbs, articles), with forms for FAQ, products, courses, events, local businesses and custom JSON-LD, and it works with Yoast SEO, Rank Math and All in One SEO.

  1. In WordPress, open Plugins → Add New, search for Atlas Schema, install and activate it.
  2. Open Schema and follow the setup steps (your organization, logo and social profiles).
  3. Edit a post or page: the Structured data panel shows what is printed and lets you add FAQ, product details or a guided type.
  4. To check every page: in the AtlasSchema dashboard open your website’s Connect tab, create a key, paste it in Schema → Validate and press Send all pages now.

For theme and plugin developers

Inside WordPress, use the plugin’s SDK through Atlas_Schema_Sdk and add your nodes with the atlas_schema_graph filter. They join the same graph, so there is still one Organization and one WebPage.

functions.php or your plugin
add_filter( 'atlas_schema_graph', function ( $nodes, $post ) {
    if ( 'event' !== $post->post_type ) {
        return $nodes;
    }
    $built   = Atlas_Schema_Sdk::build( 'event', array(
        'name'       => get_the_title( $post ),
        'startDate'  => get_post_meta( $post->ID, 'starts', true ), // 2026-11-05T19:00:00+01:00
        'attendance' => 'offline',
        'locationName' => 'City Hall',
        'address'    => array( 'street' => '1 Main St', 'city' => 'Dublin', 'country' => 'IE' ),
    ) );
    $nodes[] = $built['node'];
    return $nodes;
}, 10, 2 );

Other filters: atlas_schema_organization (the site’s publisher), atlas_schema_home_graph (the blog home page) and atlas_schema_guided_node (a node from a guided form). If you ship your own plugin without Atlas Schema, install the PHP SDK with Composer and prefix it with Strauss, so another plugin’s copy can never clash with yours.

Install

composer require atlasschema/sdk

PHP 7.4 or newer, no dependencies (Laravel finds it on its own). JavaScript: Node 18 or newer, modern browsers, React 17 or newer for <JsonLd>; no dependencies.

Build and print

Build each node from plain fields, put the page’s nodes in one graph, and print it in the page’s <head> (or anywhere in the body).

use AtlasSchema\Schema;

$page = Schema::graph([
    Schema::organization(['name' => 'Acme Ltd', 'url' => 'https://acme.com/', 'logo' => 'https://acme.com/logo.png']),
    Schema::product([
        'name'   => 'Blue mug',
        'image'  => 'https://acme.com/mug.jpg',
        'offer'  => ['price' => 12.5, 'currency' => 'EUR', 'inStock' => true],
        'rating' => ['value' => 4.6, 'count' => 38],
    ]),
]);

echo $page->toScript(); // <script type="application/ld+json">…</script>

A value can never break out of the script tag: < is always escaped. For a type the SDK has no builder for, use Schema::custom([...]) / schema.custom({...}) with any schema.org node.

All types and fields

One builder per type (Schema::product() in PHP, schema.product() in JavaScript). Pass type to choose a more specific kind where one is listed. This table is made from the SDK’s own type list, so it always matches the SDK.

Builderschema.org typeGoogleRequiredOther fields
organization
Organization
OrganizationRich resultname urllogo description email telephone address sameAs
localBusiness
Local business
LocalBusiness
type: Restaurant, CafeOrCoffeeShop, Bakery, BarOrPub, Store, and 22 more
Rich resultname addresstelephone email url image logo priceRange geo hours sameAs parentOrganization
person
Person
Person—nameurl image jobTitle worksFor description email sameAs
service
Service
Service—namedescription serviceType provider areaServed url offer
website
Website
WebSiteRich resultname urlsearchUrl inLanguage publisher
webPage
Web page
WebPage
type: AboutPage, ContactPage, ProfilePage, CollectionPage, ItemPage, and 2 more
—name urldescription inLanguage datePublished dateModified image primaryImage speakable isPartOf breadcrumb mainEntity publisher
breadcrumbs
Breadcrumbs
BreadcrumbListRich resultitems
itemList
List or carousel
ItemListPartlyitemsname
article
Article
Article
type: BlogPosting, NewsArticle, TechArticle
Rich resultheadline image datePublished authorurl dateModified publisher description inLanguage articleSection keywords wordCount mainEntityOfPage isPartOf speakable
blog
Blog
Blog—name urldescription publisher inLanguage posts
faq
FAQ
FAQPageRetired 2026questions
qa
Question and answer
QAPageRich resultquestion answerauthor
howTo
How-to
HowToRetired 2023name stepsdescription image totalTime
video
Video
VideoObjectRich resultname description thumbnailUrl uploadDatecontentUrl embedUrl duration
recipe
Recipe
RecipeRich resultname imagedescription author prepTime cookTime totalTime recipeYield recipeCategory recipeCuisine ingredients steps rating
product
Product
ProductRich resultnameurl image description sku gtin brand offer rating review subjectOf isVariantOf
productGroup
Product with variants
ProductGroupRich resultname variantsurl image description productGroupID brand variesBy
model3d
3D model
3DModel—name filesdescription url image width height depth license free creator keywords dateModified
review
Review
ReviewRich resultitemName author ratingValueitemType bestRating reviewBody datePublished
aggregateRating
Rating summary
AggregateRatingRich resultitemName ratingValue ratingCountitemType bestRating
softwareApplication
Software application
SoftwareApplication
type: WebApplication, MobileApplication, VideoGame
Rich resultname applicationCategory offeroperatingSystem url image description downloadUrl featureList publisher rating
book
Book
BookRetired 2025name authorisbn url image datePublished publisher description
event
Event
EventRich resultname startDateendDate url attendance locationName address onlineUrl status image description organizer performer offer
jobPosting
Job posting
JobPostingRich resulttitle description datePosted hiringOrganizationvalidThrough employmentType address remote salary
course
Course
CoursePartly (since 2025)name description providerurl image courseCode inLanguage courseMode courseWorkload startDate endDate offer
learningResource
Learning resource
LearningResourceRetired 2025namedescription url image learningResourceType educationalLevel teaches timeRequired inLanguage free publisher

26 types. "Google" says whether Google Search still shows a rich result for the type; retired types are still valid structured data that other search engines and AI assistants read. Open a type for every field:

organization() · Organization · Organization

Google: Logo and details in knowledge panels and attribution.

FieldWhat it isTakes
name requiredNametext
url requiredAddress (URL)web address
logoLogo addressimage address
descriptionDescriptiontext
emailEmailtext
telephoneTelephonetext
addressAddressaddress
sameAsSocial profilesweb addresses (list)
One address per line: X, LinkedIn, Facebook, Wikipedia…
localBusiness() · Local business · LocalBusiness

Kinds (type): LocalBusiness, Restaurant, CafeOrCoffeeShop, Bakery, BarOrPub, Store, ClothingStore, Dentist, MedicalClinic, Physician, HairSalon, BeautySalon, AutoRepair, Hotel, RealEstateAgent, LegalService, AccountingService, Plumber, Electrician, HomeAndConstructionBusiness, ProfessionalService, FinancialService, MedicalBusiness, HealthAndBeautyBusiness, AutomotiveBusiness, LodgingBusiness, SportsActivityLocation, EducationalOrganization.

Google: Business details in the knowledge panel: hours, ratings, directions.

FieldWhat it isTakes
name requiredNametext
address requiredAddressaddress
telephoneTelephonetext
emailEmailtext
urlAddress (URL)web address
imageImage addressimage address
logoLogo addressimage address
priceRangePrice rangetext
Such as €€ or 10-30 EUR
geoMap positionlat, lng
lat, lng
hoursOpening hoursopening hours
days, opens, closes
sameAsSocial profilesweb addresses (list)
One address per line: X, LinkedIn, Facebook, Wikipedia…
parentOrganizationPart of (organization)organization
The company or chain this location belongs to
person() · Person · Person

Google: No rich result of its own; names authors and profile pages.

FieldWhat it isTakes
name requiredNametext
urlAddress (URL)web address
imageImage addressimage address
jobTitleJob titletext
worksForWorks fororganization
descriptionDescriptiontext
emailEmailtext
sameAsSocial profilesweb addresses (list)
One address per line: X, LinkedIn, Facebook, Wikipedia…
service() · Service · Service

Google: No rich result of its own.

FieldWhat it isTakes
name requiredNametext
descriptionDescriptiontext
serviceTypeKind of servicetext
providerProviderorganization
areaServedArea servedtext
urlAddress (URL)web address
offerOfferoffer
price, currency, inStock or availability, url, validFrom, category, returns { days, country, free }, shipping { cost, country, handlingDays, transitDays }
website() · Website · WebSite

Google: The site name shown in search results.

FieldWhat it isTakes
name requiredNametext
url requiredAddress (URL)web address
searchUrlSite search addresssearch address
Such as https://example.com/?s={search_term_string}
inLanguageLanguagelanguage or list
Such as en-US; a list for a site in several languages
publisherPublisherorganization
webPage() · Web page · WebPage

Kinds (type): WebPage, AboutPage, ContactPage, ProfilePage, CollectionPage, ItemPage, FAQPage, CheckoutPage.

Google: No rich result of its own; ProfilePage has one.

FieldWhat it isTakes
name requiredTitletext
url requiredAddress (URL)web address
descriptionDescriptiontext
inLanguageLanguagetext
datePublishedPublisheddate
dateModifiedLast changeddate
imageImage addressimage address
primaryImageMain imageimage address
speakableParts to read aloudCSS selectors
CSS selectors, one per line, such as h1
isPartOfPart of (website)ref
The @id of a node in the same graph (such as https://example.com/#organization), or the node itself
breadcrumbBreadcrumbsref
The @id of a node in the same graph (such as https://example.com/#organization), or the node itself
mainEntityMain subjectref
What the page is about: the @id of a product, software, FAQ… or the node itself
publisherPublisherorganization
breadcrumbs() · Breadcrumbs · BreadcrumbList

Google: The page path shown in search results.

FieldWhat it isTakes
items requiredPathpath
name, url — from the home page to this page
itemList() · List or carousel · ItemList

Google: A carousel only together with Recipe, Course, Restaurant or Movie items.

FieldWhat it isTakes
nameNametext
items requiredPage addressesaddresses (list)
One address per line, in order (or url, name)
article() · Article · Article

Kinds (type): Article, BlogPosting, NewsArticle, TechArticle.

Google: Title and larger images in article results.

FieldWhat it isTakes
headline requiredHeadlinetext
Up to 110 characters
urlAddress (URL)web address
image requiredImage addressimage address
datePublished requiredPublisheddate and time
dateModifiedLast changeddate and time
author requiredAuthorperson
publisherPublisherorganization
descriptionDescriptiontext
inLanguageLanguagetext
articleSectionSectiontext
Such as the category
keywordsKeywordstext
Comma separated
wordCountWordsnumber
mainEntityOfPagePage it is the main subject ofref
The @id of a node in the same graph (such as https://example.com/#organization), or the node itself
isPartOfPart of (blog)ref
The @id of a node in the same graph (such as https://example.com/#organization), or the node itself
speakableParts to read aloudCSS selectors
CSS selectors, one per line
blog() · Blog · Blog

Google: No rich result of its own; its posts are articles.

FieldWhat it isTakes
name requiredNametext
url requiredAddress (URL)web address
descriptionDescriptiontext
publisherPublisherorganization
inLanguageLanguagetext
postsPostsrefs
The @ids of the posts, or the posts themselves
faq() · FAQ · FAQPage

Google: Google stopped showing FAQ rich results on 7 May 2026.

FieldWhat it isTakes
questions requiredQuestions and answersquestions
q, a
qa() · Question and answer · QAPage

Google: Q&A result: one question and its answers.

FieldWhat it isTakes
question requiredQuestiontext
answer requiredAccepted answertext
authorAsked byperson
howTo() · How-to · HowTo

Google: Google stopped showing How-to rich results on 13 September 2023.

FieldWhat it isTakes
name requiredNametext
descriptionDescriptiontext
imageImage addressimage address
totalTimeTotal timeduration
Such as PT30M
steps requiredStepssteps
One step per line
video() · Video · VideoObject

Google: Video results, key moments and live badges.

FieldWhat it isTakes
name requiredNametext
description requiredDescriptiontext
thumbnailUrl requiredThumbnail addressimage address
uploadDate requiredUploadeddate and time
contentUrlVideo file addressweb address
embedUrlPlayer addressweb address
durationLengthduration
Such as PT2M30S
recipe() · Recipe · Recipe

Google: Recipe results and carousels.

FieldWhat it isTakes
name requiredNametext
image requiredImage addressimage address
descriptionDescriptiontext
authorAuthorperson
prepTimePreparationduration
cookTimeCookingduration
totalTimeTotal timeduration
recipeYieldServestext
recipeCategoryCategorytext
recipeCuisineCuisinetext
ingredientsIngredientslist
One per line
stepsInstructionssteps
One step per line
ratingRatingrating
value, count, best (only ratings shown on the page)
product() · Product · Product

Google: Price, availability and ratings; merchant listings.

FieldWhat it isTakes
name requiredNametext
urlAddress (URL)web address
imageImage addressimage address
descriptionDescriptiontext
skuSKUtext
gtinGTIN (barcode)text
brandBrandbrand
offerOfferoffer
price, currency, inStock or availability, url, validFrom, category, returns { days, country, free }, shipping { cost, country, handlingDays, transitDays }
ratingRatingrating
value, count, best (only ratings shown on the page)
reviewA reviewreview
author, rating, body
subjectOf3D model or video of itref
A 3D model (3DModel) or video about the product: its @id or the node itself
isVariantOfVariant of (product group)ref
The @id of a node in the same graph (such as https://example.com/#organization), or the node itself
productGroup() · Product with variants · ProductGroup

Google: Product variants in merchant listings.

FieldWhat it isTakes
name requiredNametext
urlAddress (URL)web address
imageImage addressimage address
descriptionDescriptiontext
productGroupIDGroup IDtext
Such as the parent SKU
brandBrandbrand
variesByVaries byenums
variants requiredVariantsrefs
The variant products: their @ids or the products themselves
model3d() · 3D model · 3DModel

Google: No rich result of its own in Search.

FieldWhat it isTakes
name requiredNametext
descriptionDescriptiontext
urlAddress (URL)web address
imageImage addressimage address
files requiredModel filesmediaFiles
url, format (model/gltf-binary for GLB, model/vnd.usdz+zip for USDZ), size
widthWidth (cm)measure
heightHeight (cm)measure
depthDepth (cm)measure
licenseLicense addressweb address
Such as https://creativecommons.org/publicdomain/zero/1.0/
freeFree to useyes/no
creatorMade byorganization
keywordsKeywordstext
Comma separated
dateModifiedLast changeddate
review() · Review · Review

Google: Review snippets.

FieldWhat it isTakes
itemName requiredWhat is reviewedtext
itemTypeKind of thingone of Product, LocalBusiness, SoftwareApplication, Book, Course, Event, Recipe, Organization
author requiredReviewerperson
ratingValue requiredRatingnumber
bestRatingBest possible ratingnumber
Default 5
reviewBodyReview texttext
datePublishedWrittendate
aggregateRating() · Rating summary · AggregateRating

Google: Rating stars in review snippets.

FieldWhat it isTakes
itemName requiredWhat is ratedtext
itemTypeKind of thingone of Product, LocalBusiness, SoftwareApplication, Book, Course, Recipe, Organization
ratingValue requiredAverage ratingnumber
ratingCount requiredNumber of ratingsnumber
bestRatingBest possible ratingnumber
softwareApplication() · Software application · SoftwareApplication

Kinds (type): SoftwareApplication, WebApplication, MobileApplication, VideoGame.

Google: Software app results with rating and price.

FieldWhat it isTakes
name requiredNametext
applicationCategory requiredCategoryone of BusinessApplication, DeveloperApplication, DesignApplication, EducationalApplication, FinanceApplication, GameApplication, HealthApplication, LifestyleApplication, MultimediaApplication, SecurityApplication, SocialNetworkingApplication, TravelApplication, UtilitiesApplication
operatingSystemOperating systemtext
Such as Windows, macOS, Android, Web
urlAddress (URL)web address
imageImage addressimage address
descriptionDescriptiontext
downloadUrlDownload addressweb address
featureListFeatureslist
One per line
publisherPublisherorganization
offer requiredOfferoffer
price, currency, inStock or availability, url, validFrom, category, returns { days, country, free }, shipping { cost, country, handlingDays, transitDays }
ratingRatingrating
value, count, best (only ratings shown on the page)
book() · Book · Book

Google: Google retired Book actions in June 2025; a book's reviews can still show as a review snippet.

FieldWhat it isTakes
name requiredTitletext
author requiredAuthorperson
isbnISBNtext
urlAddress (URL)web address
imageImage addressimage address
datePublishedPublisheddate
publisherPublisherorganization
descriptionDescriptiontext
event() · Event · Event

Google: Event listings with date and place.

FieldWhat it isTakes
name requiredNametext
startDate requiredStartsdate and time
With a time zone, such as 2026-11-05T19:00:00+01:00
endDateEndsdate and time
urlAddress (URL)web address
attendanceWhereone of offline, online, mixed
locationNameVenuetext
addressVenue addressaddress
onlineUrlOnline addressweb address
With a venue and Where = mixed, both are listed
statusStatusone of EventScheduled, EventPostponed, EventRescheduled, EventMovedOnline, EventCancelled
Default EventScheduled
imageImage addressimage address
descriptionDescriptiontext
organizerOrganizerorganization
performerPerformer or speakername
offerOfferoffer
price, currency, inStock or availability, url, validFrom, category, returns { days, country, free }, shipping { cost, country, handlingDays, transitDays }
jobPosting() · Job posting · JobPosting

Google: Google job search.

FieldWhat it isTakes
title requiredJob titletext
description requiredDescriptiontext
datePosted requiredPosteddate
validThroughOpen untildate and time
employmentTypeEmployment typeone of FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM, OTHER
hiringOrganization requiredEmployerorganization
addressWorkplace addressaddress
remoteRemote jobyes/no
salarySalarysalary
min, max or value, currency, unit (HOUR, MONTH, YEAR)
course() · Course · Course

Google: Google retired the course info result in June 2025; the course list carousel still shows name, description and provider.

FieldWhat it isTakes
name requiredNametext
description requiredDescriptiontext
provider requiredProviderorganization
urlAddress (URL)web address
imageImage addressimage address
courseCodeCourse codetext
inLanguageLanguagetext
Such as en
courseModeHow it is taughtone of online, onsite, blended
courseWorkloadWorkloadduration
Such as PT10H
startDateStartsdate
endDateEndsdate
offerOfferoffer
price, currency, inStock or availability, url, validFrom, category, returns { days, country, free }, shipping { cost, country, handlingDays, transitDays }
learningResource() · Learning resource · LearningResource

Google: Google retired the learning video result in June 2025.

FieldWhat it isTakes
name requiredNametext
descriptionDescriptiontext
urlAddress (URL)web address
imageImage addressimage address
learningResourceTypeKindtext
Such as Lesson, Tutorial, Quiz, Worksheet
educationalLevelLeveltext
Such as Beginner
teachesWhat it teachestext
timeRequiredTime it takesduration
Such as PT15M
inLanguageLanguagetext
Such as en
freeFree to useyes/no
publisherPublisherorganization

How values are written

Plain values are enough; the SDK writes the schema.org shapes.

One graph per page

Schema::graph() / graph() links what you give it: the Organization gets https://your-site/#organization, the WebSite #website, the WebPage its-address#webpage; the WebSite names the Organization as its publisher, the WebPage is part of the WebSite, and articles name the Organization as publisher. Nodes that already have an @id keep it, so you can use your own ids (and must, when your site lives in a folder such as https://example.com/shop/).

Print the same Organization and WebSite (with the same ids) on every page, and one WebPage per page. Two FAQ blocks or two breadcrumbs on a page confuse Google; the checks below warn you.

Check and fix

$result = $page->check();        // ['valid' => true, 'status' => 'warning', 'errors' => [], 'warnings' => [...], 'types' => [...]]
$fix    = $page->fixed();        // ['nodes' => [...], 'changes' => [...]]: the graph with fixable errors repaired
$report = Schema::checkHtml($html); // a whole page: every JSON-LD block, its language, price and stock

Errors stop a rich result (a missing required property, a FAQ question without an answer, a price that differs from the page); warnings are recommended properties; info explains Google’s rules. A good place for check() is a test: build the structured data of a few typical pages and fail the build when valid is false.

Mistakes and strict mode

A missing required field, a field the type does not have or a value in the wrong format is a problem. The node is still built, so a live page never breaks over structured data.

Connect AtlasSchema (optional)

Building and checking never need an account. To have AtlasSchema check the whole website, keep history and email you when something breaks:

  1. Create a free account at the dashboard and add your website.
  2. Open the website’s Connect tab and create a key (it starts with as_live_). Keep it in an environment variable, never in the page.
  3. Send pages after you publish or change them. Each page sent counts as one page of your plan.
use AtlasSchema\Client;

$client = new Client(getenv('ATLASSCHEMA_API_KEY'));
$client->sendPages([
    ['url' => 'https://acme.com/mug/', 'title' => 'Blue mug', 'graph' => $page],
]);                                  // up to 50 pages per call
$client->site();                     // score, page counts, this month's usage
$client->check('https://acme.com/');  // fetch and check one live page now

A refused request throws an error with the API’s error code (quota_exceeded, invalid_api_key…; PHP: AtlasSchema\ApiException with getErrorCode()). In PHP you can pass your framework’s HTTP client as ['transport' => fn ($method, $url, $headers, $body) => ['status' => …, 'body' => …]]. Every endpoint is described in the API reference.

Every website check also gives an AI readiness score: whether AI crawlers can reach the site, whether pages show their text without JavaScript, whether the sitemap lists the right pages, the structured data score, and whether 3D model files are served correctly. Each item says what to fix and on which pages.

Crawler files: robots.txt, llms.txt and sitemaps

The SDK also writes the files crawlers read first. They are plain text from plain options, so you can serve them from any route or write them at build time. PHP and JavaScript write exactly the same text.

use AtlasSchema\Seo;

// robots.txt: one switch for AI answer engines, one for AI training crawlers
echo Seo::robotsTxt([
    'disallow' => ['/cart/', '/account/'],
    'answerEngines' => true,   // ChatGPT search, Claude, Perplexity can read and cite the site
    'training' => false,       // GPTBot, ClaudeBot, Google-Extended… may not
    'sitemaps' => ['https://acme.com/sitemap.xml'],
]);

// llms.txt: a short map of the site for AI tools
echo Seo::llmsTxt([
    'title' => 'Acme',
    'intro' => 'Handmade mugs, shipped worldwide.',
    'sections' => [[
        'title' => 'Shop',
        'links' => [['title' => 'Blue mug', 'url' => 'https://acme.com/mug/']],
    ]],
]);

// An XML sitemap
echo Seo::sitemap([['loc' => 'https://acme.com/', 'lastmod' => '2026-10-22']]);

seo.indexNow({ key, keyLocation, urls }) (PHP Seo::indexNow) writes the IndexNow request that tells Bing, Yandex and other IndexNow engines which pages changed; send it with any HTTP client and serve the key at keyLocation. facts.diff(before, after) tells which facts an edit changed.

Blocking AI answer engines means AI assistants cannot cite your pages. Blocking training crawlers does not affect that. llms.txt is optional: there is no proof yet that it changes AI answers, so put the effort into the sitemap first. List every public page in it, including pages your app makes itself, such as product or model pages.

What AI sees, and whether AI crawlers can read the site

Most AI crawlers do not run JavaScript, and a firewall or CDN can refuse them even when robots.txt allows them. The SDK shows both. Reading a page needs no network; the access check fetches the site you give it, through your own HTTP client in PHP.

use AtlasSchema\AiView;
use AtlasSchema\AiAccess;

// The page as a crawler without JavaScript reads it, and what of your content it misses
$view = AiView::view($html);                 // text, words, headings, links, images, jsRisk…
$gap  = AiView::compare($view, $postContent); // coverage, missing sentences, missingPrices

// Can the AI crawlers read the site? Pass any HTTP client as the transport
$result = AiAccess::check('https://acme.com/', [
    'transport' => fn ($url, $userAgent, $accept) => myGet($url, $userAgent, $accept),
    // return ['status' => 200, 'url' => $finalUrl, 'headers' => [...], 'body' => $html] or ['error' => $message]
]);
$result['summary']['answer'];     // ['total' => 6, 'open' => 6, 'blockedByServer' => 0]
AiAccess::fixSteps($result);       // what to do, step by step, for the AI search crawlers that are blocked

Each crawler gets a verdict: open, blocked_robots (robots.txt), blocked_server (refused or a bot challenge page), different (a different or much shorter page) or unknown. The fixes name the CDN when it can be told from the answers (Cloudflare, Vercel, Sucuri, Akamai, Fastly, Amazon CloudFront) and include a message you can send to your host. The check asks from your server, so a firewall that only blocks outside traffic can still refuse the real crawlers; the AtlasSchema service checks from outside.

Do AI assistants get your facts right?

People ask ChatGPT and Perplexity for prices, opening hours and addresses. The SDK takes the facts from your own structured data, asks the AI engines neutral questions (they never contain the answer), and judges every answer as right, wrong, outdated (an earlier value of yours) or not_mentioned. The judging is plain rules, not AI, so the same answers always give the same verdicts. You use your own API keys; failed requests are reported, never judged.

use AtlasSchema\Facts;

$facts = Facts::fromGraph($nodes);              // prices, stock, opening hours, address, phone, event dates
$questions = Facts::questions($facts);           // "How much does Blue mug from Acme cost?"
$answers = Facts::ask($questions, [
    'engines' => ['openai', 'perplexity'],
    'keys' => ['openai' => getenv('OPENAI_API_KEY'), 'perplexity' => getenv('PERPLEXITY_API_KEY')],
    'runs' => 2,                                 // answers vary: ask more than once
    'transport' => fn (array $r) => myRequest($r), // $r: method, url, headers, body; return ['status', 'body']
]);
$result = Facts::compare($facts, $answers);
$result['summary'];                             // right, wrong, outdated, notMentioned, answers, errors

Engines: openai (the Responses API with web search) and perplexity (Sonar). Their API answers are close to, but not the same as, what people see in the apps. Pass previous (your earlier values, by fact key) to compare to tell an outdated answer from a wrong one. The AtlasSchema service runs this every week for you, on its own keys, within your plan.

Is your business the same everywhere?

AI assistants put together what every place says about a business. entity.check(nodes) (PHP Entity::check($nodes, ['transport' => …])) compares the organization in your structured data with each profile it lists in sameAs: a Wikidata item through Wikidata's public data, other pages through their public meta tags and JSON-LD. Social networks and Wikipedia are only checked to answer, because their terms do not allow reading them. Pass pages ([{ url, nodes }]) to also check that every page states the same name, phone and postal code. Names are compared without case, accents, spaces or a legal form ("Blue Mug Café, Inc" and "BlueMug Cafe Ltd" match).

Moving an existing website

Most sites already print some structured data. Follow these steps so search engines see one clean graph and nothing you rely on gets lost:

  1. Take stock. Save each main page’s title, description, canonical, robots tag and JSON-LD, plus robots.txt, the sitemaps and llms.txt. This is your “before” copy.
  2. Choose one source of structured data. Find everything that prints JSON-LD: the theme, an SEO plugin, review or shop apps. Either the SDK prints the whole graph, or it adds to the existing graph through the same @ids. Two separate graphs for the same organization or page confuse search engines.
  3. Give every kind of page a type. Home, docs, product, catalogue and article pages each get a page type (WebPage, CollectionPage, ItemPage…), breadcrumbs, and the thing the page is about as its main entity (the product, the software, the 3D model).
  4. Keep your @ids. Search engines already know the ids your pages print. Keep them, and pass your site’s address to graph() so the Organization and WebSite stay https://your-site/#organization and #website.
  5. Write the crawler files with the SDK (see above).
  6. Compare before and after. Every difference in the JSON-LD and the head should be one you meant to make.
  7. Connect and check. Add the website in AtlasSchema, create a key on its Connect tab, send your pages and run the first check.
  8. Turn on monitoring. Scheduled checks and alerts email you when something breaks. Read the AI readiness tab too: a firewall or CDN can block AI crawlers that robots.txt allows.

Framework recipes

Symfony (Twig)

Build the graph in the controller and pass $page->toScript() to the template, then print it with {{ schema|raw }} in the <head> block. The script is already escaped safely.

Joomla

In a template override or a system plugin: Factory::getApplication()->getDocument()->addCustomTag($page->toScript());

Drupal, CodeIgniter, plain PHP

Anywhere you render HTML: <?php echo $page->toScript(); ?> in the page’s head.

Node and Express

Add page.toScript() to the HTML you send, or pass it to your template engine as unescaped HTML (<%- schema %> in EJS, {{{schema}}} in Handlebars).

Next.js, Nuxt, Astro and static sites

Next.js: <JsonLd> in the page component. Nuxt: useHead({ script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(page.toJSON()) }] }). Astro: <script type="application/ld+json" set:html={JSON.stringify(page.toJSON())} />. Build the graph from the same data as the page, so the two always agree.

Questions

Does the SDK send anything to AtlasSchema?

No. Building and checking run in your code with no network access. Only the optional client calls the API, with the key and pages you give it.

Do the PHP and JavaScript SDKs give the same result?

Yes. They read the same type definitions and pass the same test cases, so the same fields give the same JSON-LD, the same checks and the same fixes.

Can I use it with Yoast SEO or Rank Math?

On WordPress, the Atlas Schema plugin joins their graph instead of printing a second one. In your own code, give your nodes the same @ids as the SEO plugin’s Organization and WebSite, or print only what it does not.

Which versions are supported?

PHP 7.4 to the newest PHP 8, and Node 18 or newer. Both follow semantic versioning: a minor version adds types and fields, never removes them.

Where do I report a problem?

Write to contact@atlasaidev.com or use the dashboard’s Help page.