1import { themes as prismThemes } from 'prism-react-renderer'
2import type { Config } from '@docusaurus/types'
3import type * as Preset from '@docusaurus/preset-classic'
4
5import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs'
6import type { Options as BlogOptions } from '@docusaurus/plugin-content-blog'
7import type { Options as PageOptions } from '@docusaurus/plugin-content-pages'
8
9import tailwindPlugin from './plugins/tailwind-config.cjs'
10import agentDiscoveryPlugin from './plugins/agent-discovery.cjs'
11import * as fs from 'fs'
12import * as path from 'path'
13
14// Load versions from versions.json if it exists (generated at build time)
15const versionsPath = path.join(__dirname, 'versions.json')
16const versions: string[] = fs.existsSync(versionsPath)
17 ? JSON.parse(fs.readFileSync(versionsPath, 'utf-8'))
18 : []
19
20// Build version configuration dynamically
21// Trunk docs (current) are "Next" at /docs/next
22// Highest version in versions.json (e.g., 1.11.x) is "Latest" at /docs (default)
23// Previous versions are at /docs/v1.10, etc.
24// Maintenance policy: latest + 1 previous minor versions are maintained
25const hasVersions = versions.length > 0
26
27// Extract major and minor version numbers from versions to determine ordering and maintenance status
28const getVersion = (v: string): { major: number; minor: number } => {
29 const match = v.match(/^(\d+)\.(\d+)/)
30 return match
31 ? { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) }
32 : { major: 0, minor: 0 }
33}
34
35// Sort versions by semver (major desc, then minor desc)
36const sortedVersions = [...versions].sort((a, b) => {
37 const vA = getVersion(a)
38 const vB = getVersion(b)
39 if (vB.major !== vA.major) return vB.major - vA.major
40 return vB.minor - vA.minor
41})
42
43// Highest version is "latest", trunk (current) is "next"
44const latestVersion = sortedVersions[0] || null
45const latestVer = latestVersion ? getVersion(latestVersion) : { major: 0, minor: 0 }
46
47// Final release of the previous major stays maintained through the transition
48// (e.g. v1.11.x remains maintained after v2.0 ships as latest). sortedVersions is
49// major-desc, so the first entry below the latest major is that major's newest release.
50const prevMajorLatest = sortedVersions.find((v) => getVersion(v).major < latestVer.major) || null
51
52const docsVersionConfig = hasVersions
53 ? {
54 lastVersion: latestVersion!, // The stable release is the default
55 onlyIncludeVersions: [...versions, 'current'],
56 versions: {
57 current: {
58 label: 'Next',
59 path: 'next',
60 banner: 'unreleased' as const
61 },
62 ...Object.fromEntries(
63 versions.map((version) => {
64 const ver = getVersion(version)
65 const isLatest = version === latestVersion
66 // Maintained (no banner): the latest major's current + previous minor,
67 // plus the final release of the previous major (e.g. v1.11.x under v2.0).
68 const isMaintained =
69 ver.major > latestVer.major ||
70 (ver.major === latestVer.major && ver.minor >= latestVer.minor - 1) ||
71 version === prevMajorLatest
72
73 return [
74 version,
75 {
76 label: isLatest
77 ? `Latest (v${version.replace('.x', '')})`
78 : `v${version.replace('.x', '')}`,
79 path: isLatest ? '' : `v${version.replace('.x', '')}`,
80 banner: isMaintained ? ('none' as const) : ('unmaintained' as const)
81 }
82 ]
83 })
84 )
85 }
86 }
87 : {
88 // No versions generated - use current docs only (for local dev)
89 lastVersion: 'current',
90 versions: {
91 current: {
92 label: 'Latest',
93 path: '',
94 banner: 'none' as const
95 }
96 },
97 onlyIncludeVersions: ['current']
98 }
99
100const config: Config = {
101 title: 'Spice.ai OSS',
102 tagline:
103 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
104 favicon: 'img/favicon.ico',
105
106 // Set the production url of your site here
107 url: 'https://spiceai.org',
108 // Set the /<baseUrl>/ pathname under which your site is served
109 // For GitHub pages deployment, it is often '/<projectName>/'
110 baseUrl: '/',
111
112 trailingSlash: false,
113
114 // GitHub pages deployment config.
115 // If you aren't using GitHub pages, you don't need these.
116 organizationName: 'spiceai', // Usually your GitHub org/user name.
117 projectName: 'docs', // Usually your repo name.
118
119 onBrokenAnchors: 'throw',
120 onBrokenLinks: 'throw',
121
122 markdown: {
123 mermaid: true,
124 hooks: {
125 onBrokenMarkdownLinks: 'throw'
126 }
127 },
128
129 // Even if you don't use internationalization, you can use this field to set
130 // useful metadata like html lang. For example, if your site is Chinese, you
131 // may want to replace "en" with "zh-Hans".
132 i18n: {
133 defaultLocale: 'en',
134 locales: ['en']
135 },
136
137 future: {
138 v4: {
139 removeLegacyPostBuildHeadAttribute: true // required
140 },
141 faster: true
142 },
143
144 // Read by src/clientModules/webmcp.ts to search the latest docs version.
145 customFields: {
146 latestDocsVersion: latestVersion ?? 'current'
147 },
148
149 clientModules: ['./src/clientModules/webmcp.ts'],
150
151 presets: [
152 [
153 'classic',
154 {
155 docs: {
156 routeBasePath: '/docs',
157 path: 'docs',
158 sidebarPath: 'sidebars.ts',
159 onInlineTags: 'throw',
160 docItemComponent: '@theme/ApiItem',
161 editUrl: ({ versionDocsDirPath, docPath }) => {
162 return `https://github.com/spiceai/docs/edit/trunk/website/${versionDocsDirPath}/${docPath}`
163 },
164 ...docsVersionConfig
165 },
166 blog: {
167 path: 'blog',
168 showLastUpdateAuthor: true,
169 showLastUpdateTime: true,
170 onInlineTags: 'throw',
171 onUntruncatedBlogPosts: 'ignore',
172 editUrl: ({ locale, blogDirPath, blogPath }) => {
173 return `https://github.com/spiceai/docs/edit/trunk/website/${blogDirPath}/${blogPath}`
174 },
175 remarkPlugins: [],
176 postsPerPage: 5,
177 feedOptions: {
178 type: 'all',
179 description:
180 'Keep up to date with upcoming Spice.ai OSS releases and articles by following our feed!',
181 copyright: `Copyright © 2021-2026 Spice AI, Inc.`,
182 xslt: true
183 },
184 blogTitle: 'Spice.ai OSS blog',
185 blogDescription: 'Read blog posts about Spice.ai OSS from the team and community',
186 blogSidebarCount: 'ALL',
187 blogSidebarTitle: 'All Posts'
188 } satisfies BlogOptions,
189 pages: {
190 remarkPlugins: [],
191 showLastUpdateAuthor: true,
192 showLastUpdateTime: true
193 } satisfies PageOptions,
194 theme: {
195 customCss: ['./src/css/custom.css', './src/css/openapi.css', './src/css/preflight.css']
196 },
197 gtag: {
198 trackingID: 'G-SST0X6NS37',
199 anonymizeIP: true
200 },
201 sitemap: {
202 lastmod: 'date',
203 changefreq: 'weekly',
204 priority: 0.5,
205 ignorePatterns: ['/tags/**'],
206 filename: 'sitemap.xml'
207 }
208 } satisfies Preset.Options
209 ]
210 ],
211 themes: ['docusaurus-theme-openapi-docs', '@docusaurus/theme-mermaid'],
212 themeConfig: {
213 // SEO metadata configuration
214 metadata: [
215 {
216 name: 'keywords',
217 content:
218 'spice, spice.ai, sql query engine, ai compute engine, data federation, data acceleration, rag, retrieval augmented generation, arrow flight, datafusion, duckdb, rust, llm, openai compatible, mcp server'
219 },
220 { name: 'author', content: 'Spice AI, Inc.' },
221 { name: 'robots', content: 'index, follow' },
222 { property: 'og:type', content: 'website' },
223 { property: 'og:site_name', content: 'Spice.ai OSS' },
224 { property: 'og:image:width', content: '1200' },
225 { property: 'og:image:height', content: '630' },
226 { property: 'og:image:alt', content: 'Spice.ai - SQL Query and AI Compute Engine' },
227 { name: 'twitter:card', content: 'summary_large_image' },
228 { name: 'twitter:site', content: '@spice_ai' },
229 { name: 'twitter:creator', content: '@spice_ai' }
230 ],
231 announcementBar: {
232 content: '<a href="/releases/v2.3.2">Spice.ai OSS v2.3.2</a> is now available! 🎉',
233 backgroundColor: 'var(--announcement-bar-bg)',
234 textColor: 'var(--announcement-bar-text)',
235 isCloseable: true
236 },
237 navbar: {
238 title: 'Spice.ai OSS',
239 logo: {
240 alt: 'Spice.ai OSS logo',
241 src: 'img/logo.svg'
242 },
243 hideOnScroll: true,
244 items: [
245 {
246 type: 'doc',
247 position: 'left',
248 docId: 'index',
249 label: 'Docs'
250 },
251 {
252 type: 'docSidebar',
253 position: 'left',
254 sidebarId: 'api',
255 label: 'API'
256 },
257 { to: 'releases', label: 'Releases', position: 'left' },
258 {
259 label: 'Blog',
260 href: 'https://spice.ai/blog',
261 position: 'left'
262 },
263 { to: 'cookbook', label: 'Cookbook', position: 'left' },
264 { to: 'docs/reference/sql', label: 'SQL Reference', position: 'left' },
265 // Version dropdown is only shown when versions exist
266 ...(hasVersions
267 ? [
268 {
269 type: 'docsVersionDropdown' as const,
270 position: 'right' as const,
271 dropdownActiveClassDisabled: true
272 }
273 ]
274 : []),
275 {
276 label: 'Try Spice Cloud',
277 href: 'https://spice.ai/login',
278 position: 'right'
279 },
280 {
281 label: 'X',
282 href: 'https://x.com/spice_ai',
283 position: 'right'
284 },
285 {
286 label: 'Slack',
287 href: 'https://spice.ai/slack',
288 position: 'right'
289 },
290 {
291 label: 'YouTube',
292 href: 'https://www.youtube.com/@spiceai',
293 position: 'right'
294 },
295 {
296 href: 'https://github.com/spiceai/spiceai',
297 position: 'right',
298 className: 'header-github-link',
299 'aria-label': 'GitHub repository'
300 }
301 ]
302 },
303 footer: {
304 style: 'dark',
305 links: [
306 {
307 title: 'Docs',
308 items: [
309 {
310 label: 'Getting Started',
311 to: '/docs/getting-started'
312 },
313 {
314 label: 'API',
315 to: '/docs/api'
316 },
317 {
318 label: 'CLI',
319 to: '/docs/cli'
320 },
321 {
322 label: 'SDKs',
323 to: '/docs/sdks'
324 }
325 ]
326 },
327 {
328 title: 'Community',
329 items: [
330 {
331 label: 'Reddit',
332 href: 'https://reddit.com/r/spiceai'
333 },
334 {
335 label: 'Slack',
336 href: 'https://spice.ai/slack'
337 },
338 {
339 label: 'X',
340 href: 'https://x.com/spice_ai'
341 },
342 {
343 label: 'YouTube',
344 href: 'https://www.youtube.com/@spiceai'
345 }
346 ]
347 },
348 {
349 title: 'More',
350 items: [
351 {
352 label: 'Blog',
353 href: 'https://spice.ai/blog'
354 },
355 {
356 label: 'GitHub',
357 href: 'https://github.com/spiceai/spiceai'
358 }
359 ]
360 }
361 ],
362 copyright: `Copyright © 2025 Spice AI, Inc.`
363 },
364 languageTabs: [
365 {
366 highlight: 'bash',
367 language: 'curl',
368 logoClass: 'curl'
369 }
370 ],
371 prism: {
372 theme: prismThemes.oneLight,
373 darkTheme: prismThemes.gruvboxMaterialDark,
374 additionalLanguages: ['bash', 'json', 'csharp']
375 },
376 algolia: {
377 appId: '0SP8I8JTL8',
378 apiKey: '72f66fe334ccd3c7db696a123d68735c',
379 indexName: 'spiceai',
380 contextualSearch: true
381 }
382 } satisfies Preset.ThemeConfig,
383
384 headTags: [
385 // Structured data for SEO (JSON-LD)
386 {
387 tagName: 'script',
388 attributes: {
389 type: 'application/ld+json'
390 },
391 innerHTML: JSON.stringify({
392 '@context': 'https://schema.org',
393 '@type': 'SoftwareApplication',
394 name: 'Spice.ai OSS',
395 applicationCategory: 'DeveloperApplication',
396 operatingSystem: 'Linux, macOS, Windows',
397 description:
398 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
399 url: 'https://spiceai.org',
400 downloadUrl: 'https://github.com/spiceai/spiceai/releases',
401 softwareVersion: '1.10.0',
402 author: {
403 '@type': 'Organization',
404 name: 'Spice AI, Inc.',
405 url: 'https://spice.ai'
406 },
407 license: 'https://github.com/spiceai/spiceai/blob/trunk/LICENSE',
408 programmingLanguage: 'Rust',
409 offers: {
410 '@type': 'Offer',
411 price: '0',
412 priceCurrency: 'USD'
413 }
414 })
415 },
416 {
417 tagName: 'script',
418 attributes: {
419 type: 'application/ld+json'
420 },
421 innerHTML: JSON.stringify({
422 '@context': 'https://schema.org',
423 '@type': 'Organization',
424 name: 'Spice AI, Inc.',
425 url: 'https://spice.ai',
426 logo: 'https://spiceai.org/img/logo.svg',
427 sameAs: [
428 'https://github.com/spiceai',
429 'https://x.com/spice_ai',
430 'https://www.youtube.com/@spiceai',
431 'https://www.linkedin.com/company/spiceai'
432 ]
433 })
434 },
435 {
436 tagName: 'link',
437 attributes: {
438 rel: 'preconnect',
439 href: 'https://fonts.googleapis.com'
440 }
441 },
442 {
443 tagName: 'link',
444 attributes: {
445 rel: 'preconnect',
446 href: 'https://fonts.gstatic.com',
447 crossorigin: 'true'
448 }
449 },
450 {
451 tagName: 'link',
452 attributes: {
453 href: 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap',
454 rel: 'stylesheet'
455 }
456 },
457 {
458 tagName: 'link',
459 attributes: {
460 rel: 'icon',
461 type: 'image/png',
462 sizes: '32x32',
463 href: '/favicon-32x32.png'
464 }
465 },
466 {
467 tagName: 'link',
468 attributes: {
469 rel: 'icon',
470 type: 'image/png',
471 sizes: '16x16',
472 href: '/favicon-16x16.png'
473 }
474 },
475 {
476 tagName: 'script',
477 attributes: {
478 type: 'text/javascript',
479 id: 'hs-script-loader',
480 async: 'true',
481 defer: 'true',
482 src: '//js.hs-scripts.com/46107967.js'
483 }
484 },
485 // SEO: hreflang for language targeting
486 {
487 tagName: 'link',
488 attributes: {
489 rel: 'alternate',
490 hreflang: 'en',
491 href: 'https://spiceai.org'
492 }
493 },
494 {
495 tagName: 'link',
496 attributes: {
497 rel: 'alternate',
498 hreflang: 'x-default',
499 href: 'https://spiceai.org'
500 }
501 }
502 ],
503
504 plugins: [
505 tailwindPlugin,
506 [
507 '@docusaurus/plugin-content-blog',
508 {
509 id: 'releases',
510 path: 'releases',
511 routeBasePath: 'releases',
512 showLastUpdateAuthor: true,
513 showLastUpdateTime: true,
514 onInlineTags: 'throw',
515 onUntruncatedBlogPosts: 'ignore',
516 editUrl: ({ locale, blogDirPath, blogPath }) => {
517 return `https://github.com/spiceai/docs/edit/trunk/website/${blogDirPath}/${blogPath}`
518 },
519 remarkPlugins: [],
520 postsPerPage: 10,
521 feedOptions: {
522 type: 'all',
523 description: 'Keep up to date with Spice.ai OSS releases by following our feed!',
524 copyright: `Copyright © 2025-2026 Spice AI, Inc.`,
525 xslt: true
526 },
527 blogTitle: 'Spice.ai OSS Releases',
528 blogDescription: 'Spice.ai OSS release notes and announcements',
529 blogSidebarCount: 'ALL',
530 blogSidebarTitle: 'All Releases'
531 } satisfies BlogOptions
532 ],
533 [
534 'docusaurus-plugin-openapi-docs',
535 {
536 id: 'api',
537 docsPluginId: 'classic',
538 config: {
539 spice: {
540 proxy: 'http://localhost:8090',
541
542 specPath: 'public/openapi.json',
543 outputDir: 'docs/api/HTTP',
544 sidebarOptions: {
545 groupPathsBy: 'tag'
546 }
547 } satisfies OpenApiPlugin.Options
548 }
549 }
550 ],
551 [
552 '@docusaurus/plugin-client-redirects',
553 {
554 redirects: [
555 {
556 from: '/blog',
557 to: 'https://spice.ai/blog'
558 },
559 {
560 from: '/blog/announcing-1.0-stable',
561 to: 'https://spice.ai/blog/announcing-spice-ai-open-source-1-0-stable'
562 },
563 {
564 from: '/blog/amazon-s3-vectors-with-spice',
565 to: 'https://spice.ai/blog/getting-started-with-amazon-s3-vectors-and-spice'
566 },
567 {
568 from: '/blog/releases/v1.10-0',
569 to: '/releases/v1.10.0'
570 },
571 {
572 from: '/query-federation',
573 to: '/docs/features/query-federation'
574 },
575 {
576 from: '/docs/next/features/large-language-models/evaluate',
577 to: '/docs/next/features/large-language-models/decisions'
578 },
579 // 2021 blog posts
580 {
581 from: '/blog/a-new-class-of-applications-that-learn-and-adapt',
582 to: 'https://spice.ai/blog/a-new-class-of-applications-that-learn-and-adapt'
583 },
584 {
585 from: '/blog/2021/a-new-class-of-applications-that-learn-and-adapt',
586 to: 'https://spice.ai/blog/a-new-class-of-applications-that-learn-and-adapt'
587 },
588 {
589 from: '/blog/ai-needs-ai-ready-data',
590 to: 'https://spice.ai/blog/ai-needs-ai-ready-data'
591 },
592 {
593 from: '/blog/2021/ai-needs-ai-ready-data',
594 to: 'https://spice.ai/blog/ai-needs-ai-ready-data'
595 },
596 {
597 from: '/blog/making-apps-that-learn-and-adapt',
598 to: 'https://spice.ai/blog/making-apps-that-learn-and-adapt'
599 },
600 {
601 from: '/blog/2021/making-apps-that-learn-and-adapt',
602 to: 'https://spice.ai/blog/making-apps-that-learn-and-adapt'
603 },
604 {
605 from: '/blog/q-learning-reward-is-all-you-need',
606 to: 'https://spice.ai/blog/q-learning-reward-is-all-you-need'
607 },
608 {
609 from: '/blog/spiceais-approach-to-time-series-ai',
610 to: 'https://spice.ai/blog/spiceais-approach-to-time-series-ai'
611 },
612 {
613 from: '/blog/2021/spiceais-approach-to-time-series-ai',
614 to: 'https://spice.ai/blog/spiceais-approach-to-time-series-ai'
615 },
616 {
617 from: '/blog/spicepods-from-zero-to-hero',
618 to: 'https://spice.ai/blog/spicepods-from-zero-to-hero'
619 },
620 {
621 from: '/blog/2021/spicepods-from-zero-to-hero',
622 to: 'https://spice.ai/blog/spicepods-from-zero-to-hero'
623 },
624 {
625 from: '/blog/teaching-apps-how-to-learn-with-spicepods',
626 to: 'https://spice.ai/blog/teaching-apps-how-to-learn-with-spicepods'
627 },
628 // 2022 blog posts
629 {
630 from: '/blog/adding-soft-actor-critic',
631 to: 'https://spice.ai/blog/adding-soft-actor-critic'
632 },
633 {
634 from: '/blog/building-on-apache-arrow-and-flight',
635 to: 'https://spice.ai/blog/building-on-apache-arrow-and-flight'
636 },
637 {
638 from: '/blog/what-data-informs-ai-driven-decision-making',
639 to: 'https://spice.ai/blog/what-data-informs-ai-driven-decision-making'
640 },
641 {
642 from: '/blog/2022/what-data-informs-ai-driven-decision-making',
643 to: 'https://spice.ai/blog/what-data-informs-ai-driven-decision-making'
644 },
645 // 2024 blog posts
646 {
647 from: '/blog/adding-spice',
648 to: 'https://spice.ai/blog/adding-spice-the-next-generation-of-spice-ai-oss'
649 },
650 {
651 from: '/blog/2024/adding-spice',
652 to: 'https://spice.ai/blog/adding-spice--the-next-generation-of-spice-ai-oss'
653 },
654 {
655 from: '/federated-queries',
656 to: '/docs/features/query-federation'
657 },
658 {
659 from: '/data-ingestion',
660 to: '/docs/features/data-ingestion'
661 },
662 {
663 from: '/data-acceleration',
664 to: '/docs/features/data-acceleration'
665 },
666 {
667 from: '/monitoring',
668 to: '/docs/features/observability'
669 }
670 ]
671 }
672 ],
673 [
674 '@signalwire/docusaurus-plugin-llms-txt',
675 {
676 siteTitle: 'Spice.ai OSS',
677 siteDescription:
678 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
679 depth: 2,
680 logLevel: 1,
681 content: {
682 includeBlog: false,
683 includePages: true,
684 includeDocs: true,
685 includeVersionedDocs: false,
686 enableLlmsFullTxt: true,
687 // Served to agents that request text/markdown (functions/_middleware.js).
688 enableMarkdownFiles: true,
689 excludeRoutes: ['/tags/**', '/search', '/api/HTTP/**'],
690 // The plugin's default selectors, then .main-wrapper for the homepage, which has no <main>
691 // element. Without it, the homepage Markdown includes the navbar.
692 contentSelectors: [
693 '.theme-doc-markdown',
694 'main .container .col',
695 'main .theme-doc-wrapper',
696 'article',
697 'main .container',
698 'main',
699 '.main-wrapper'
700 ]
701 },
702 optionalLinks: [
703 {
704 title: 'GitHub Repository',
705 url: 'https://github.com/spiceai/spiceai',
706 description: 'Spice.ai OSS source code and issue tracker'
707 },
708 {
709 title: 'Cookbook',
710 url: 'https://github.com/spiceai/cookbook',
711 description: 'Ready-to-use recipes and examples for Spice.ai'
712 },
713 {
714 title: 'Spice Cloud Platform',
715 url: 'https://spice.ai',
716 description: 'Managed cloud platform for Spice.ai'
717 }
718 ]
719 }
720 ],
721 [
722 agentDiscoveryPlugin,
723 {
724 openApiSpec: 'public/openapi.json',
725 skillsRepository: 'spiceai/skills',
726 // The commit of the spiceai/skills v2.3.2 release. A commit, unlike a tag, cannot be moved to
727 // different content. Update it when a new skills release ships.
728 skillsCommit: 'b9bab4ec4d6d4d046d550c9866a784c0e952c0e0'
729 }
730 ]
731 ]
732}
733
734export default config
735
1import { themes as prismThemes } from 'prism-react-renderer'
2import type { Config } from '@docusaurus/types'
3import type * as Preset from '@docusaurus/preset-classic'
4
5import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs'
6import type { Options as BlogOptions } from '@docusaurus/plugin-content-blog'
7import type { Options as PageOptions } from '@docusaurus/plugin-content-pages'
8
9import tailwindPlugin from './plugins/tailwind-config.cjs'
10import agentDiscoveryPlugin from './plugins/agent-discovery.cjs'
11import * as fs from 'fs'
12import * as path from 'path'
13
14// Load versions from versions.json if it exists (generated at build time)
15const versionsPath = path.join(__dirname, 'versions.json')
16const versions: string[] = fs.existsSync(versionsPath)
17 ? JSON.parse(fs.readFileSync(versionsPath, 'utf-8'))
18 : []
19
20// Build version configuration dynamically
21// Trunk docs (current) are "Next" at /docs/next
22// Highest version in versions.json (e.g., 1.11.x) is "Latest" at /docs (default)
23// Previous versions are at /docs/v1.10, etc.
24// Maintenance policy: latest + 1 previous minor versions are maintained
25const hasVersions = versions.length > 0
26
27// Extract major and minor version numbers from versions to determine ordering and maintenance status
28const getVersion = (v: string): { major: number; minor: number } => {
29 const match = v.match(/^(\d+)\.(\d+)/)
30 return match
31 ? { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) }
32 : { major: 0, minor: 0 }
33}
34
35// Sort versions by semver (major desc, then minor desc)
36const sortedVersions = [...versions].sort((a, b) => {
37 const vA = getVersion(a)
38 const vB = getVersion(b)
39 if (vB.major !== vA.major) return vB.major - vA.major
40 return vB.minor - vA.minor
41})
42
43// Highest version is "latest", trunk (current) is "next"
44const latestVersion = sortedVersions[0] || null
45const latestVer = latestVersion ? getVersion(latestVersion) : { major: 0, minor: 0 }
46
47// Final release of the previous major stays maintained through the transition
48// (e.g. v1.11.x remains maintained after v2.0 ships as latest). sortedVersions is
49// major-desc, so the first entry below the latest major is that major's newest release.
50const prevMajorLatest = sortedVersions.find((v) => getVersion(v).major < latestVer.major) || null
51
52const docsVersionConfig = hasVersions
53 ? {
54 lastVersion: latestVersion!, // The stable release is the default
55 onlyIncludeVersions: [...versions, 'current'],
56 versions: {
57 current: {
58 label: 'Next',
59 path: 'next',
60 banner: 'unreleased' as const
61 },
62 ...Object.fromEntries(
63 versions.map((version) => {
64 const ver = getVersion(version)
65 const isLatest = version === latestVersion
66 // Maintained (no banner): the latest major's current + previous minor,
67 // plus the final release of the previous major (e.g. v1.11.x under v2.0).
68 const isMaintained =
69 ver.major > latestVer.major ||
70 (ver.major === latestVer.major && ver.minor >= latestVer.minor - 1) ||
71 version === prevMajorLatest
72
73 return [
74 version,
75 {
76 label: isLatest
77 ? `Latest (v${version.replace('.x', '')})`
78 : `v${version.replace('.x', '')}`,
79 path: isLatest ? '' : `v${version.replace('.x', '')}`,
80 banner: isMaintained ? ('none' as const) : ('unmaintained' as const)
81 }
82 ]
83 })
84 )
85 }
86 }
87 : {
88 // No versions generated - use current docs only (for local dev)
89 lastVersion: 'current',
90 versions: {
91 current: {
92 label: 'Latest',
93 path: '',
94 banner: 'none' as const
95 }
96 },
97 onlyIncludeVersions: ['current']
98 }
99
100const config: Config = {
101 title: 'Spice.ai OSS',
102 tagline:
103 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
104 favicon: 'img/favicon.ico',
105
106 // Set the production url of your site here
107 url: 'https://spiceai.org',
108 // Set the /<baseUrl>/ pathname under which your site is served
109 // For GitHub pages deployment, it is often '/<projectName>/'
110 baseUrl: '/',
111
112 trailingSlash: false,
113
114 // GitHub pages deployment config.
115 // If you aren't using GitHub pages, you don't need these.
116 organizationName: 'spiceai', // Usually your GitHub org/user name.
117 projectName: 'docs', // Usually your repo name.
118
119 onBrokenAnchors: 'throw',
120 onBrokenLinks: 'throw',
121
122 markdown: {
123 mermaid: true,
124 hooks: {
125 onBrokenMarkdownLinks: 'throw'
126 }
127 },
128
129 // Even if you don't use internationalization, you can use this field to set
130 // useful metadata like html lang. For example, if your site is Chinese, you
131 // may want to replace "en" with "zh-Hans".
132 i18n: {
133 defaultLocale: 'en',
134 locales: ['en']
135 },
136
137 future: {
138 v4: {
139 removeLegacyPostBuildHeadAttribute: true // required
140 },
141 faster: true
142 },
143
144 // Read by src/clientModules/webmcp.ts to search the latest docs version.
145 customFields: {
146 latestDocsVersion: latestVersion ?? 'current'
147 },
148
149 clientModules: ['./src/clientModules/webmcp.ts'],
150
151 presets: [
152 [
153 'classic',
154 {
155 docs: {
156 routeBasePath: '/docs',
157 path: 'docs',
158 sidebarPath: 'sidebars.ts',
159 onInlineTags: 'throw',
160 docItemComponent: '@theme/ApiItem',
161 editUrl: ({ versionDocsDirPath, docPath }) => {
162 return `https://github.com/spiceai/docs/edit/trunk/website/${versionDocsDirPath}/${docPath}`
163 },
164 ...docsVersionConfig
165 },
166 blog: {
167 path: 'blog',
168 showLastUpdateAuthor: true,
169 showLastUpdateTime: true,
170 onInlineTags: 'throw',
171 onUntruncatedBlogPosts: 'ignore',
172 editUrl: ({ locale, blogDirPath, blogPath }) => {
173 return `https://github.com/spiceai/docs/edit/trunk/website/${blogDirPath}/${blogPath}`
174 },
175 remarkPlugins: [],
176 postsPerPage: 5,
177 feedOptions: {
178 type: 'all',
179 description:
180 'Keep up to date with upcoming Spice.ai OSS releases and articles by following our feed!',
181 copyright: `Copyright © 2021-2026 Spice AI, Inc.`,
182 xslt: true
183 },
184 blogTitle: 'Spice.ai OSS blog',
185 blogDescription: 'Read blog posts about Spice.ai OSS from the team and community',
186 blogSidebarCount: 'ALL',
187 blogSidebarTitle: 'All Posts'
188 } satisfies BlogOptions,
189 pages: {
190 remarkPlugins: [],
191 showLastUpdateAuthor: true,
192 showLastUpdateTime: true
193 } satisfies PageOptions,
194 theme: {
195 customCss: ['./src/css/custom.css', './src/css/openapi.css', './src/css/preflight.css']
196 },
197 gtag: {
198 trackingID: 'G-SST0X6NS37',
199 anonymizeIP: true
200 },
201 sitemap: {
202 lastmod: 'date',
203 changefreq: 'weekly',
204 priority: 0.5,
205 ignorePatterns: ['/tags/**'],
206 filename: 'sitemap.xml'
207 }
208 } satisfies Preset.Options
209 ]
210 ],
211 themes: ['docusaurus-theme-openapi-docs', '@docusaurus/theme-mermaid'],
212 themeConfig: {
213 // SEO metadata configuration
214 metadata: [
215 {
216 name: 'keywords',
217 content:
218 'spice, spice.ai, sql query engine, ai compute engine, data federation, data acceleration, rag, retrieval augmented generation, arrow flight, datafusion, duckdb, rust, llm, openai compatible, mcp server'
219 },
220 { name: 'author', content: 'Spice AI, Inc.' },
221 { name: 'robots', content: 'index, follow' },
222 { property: 'og:type', content: 'website' },
223 { property: 'og:site_name', content: 'Spice.ai OSS' },
224 { property: 'og:image:width', content: '1200' },
225 { property: 'og:image:height', content: '630' },
226 { property: 'og:image:alt', content: 'Spice.ai - SQL Query and AI Compute Engine' },
227 { name: 'twitter:card', content: 'summary_large_image' },
228 { name: 'twitter:site', content: '@spice_ai' },
229 { name: 'twitter:creator', content: '@spice_ai' }
230 ],
231 announcementBar: {
232 content: '<a href="/releases/v2.3.2">Spice.ai OSS v2.3.2</a> is now available! 🎉',
233 backgroundColor: 'var(--announcement-bar-bg)',
234 textColor: 'var(--announcement-bar-text)',
235 isCloseable: true
236 },
237 navbar: {
238 title: 'Spice.ai OSS',
239 logo: {
240 alt: 'Spice.ai OSS logo',
241 src: 'img/logo.svg'
242 },
243 hideOnScroll: true,
244 items: [
245 {
246 type: 'doc',
247 position: 'left',
248 docId: 'index',
249 label: 'Docs'
250 },
251 {
252 type: 'docSidebar',
253 position: 'left',
254 sidebarId: 'api',
255 label: 'API'
256 },
257 { to: 'releases', label: 'Releases', position: 'left' },
258 {
259 label: 'Blog',
260 href: 'https://spice.ai/blog',
261 position: 'left'
262 },
263 { to: 'cookbook', label: 'Cookbook', position: 'left' },
264 { to: 'docs/reference/sql', label: 'SQL Reference', position: 'left' },
265 // Version dropdown is only shown when versions exist
266 ...(hasVersions
267 ? [
268 {
269 type: 'docsVersionDropdown' as const,
270 position: 'right' as const,
271 dropdownActiveClassDisabled: true
272 }
273 ]
274 : []),
275 {
276 label: 'Try Spice Cloud',
277 href: 'https://spice.ai/login',
278 position: 'right'
279 },
280 {
281 label: 'X',
282 href: 'https://x.com/spice_ai',
283 position: 'right'
284 },
285 {
286 label: 'Slack',
287 href: 'https://spice.ai/slack',
288 position: 'right'
289 },
290 {
291 label: 'YouTube',
292 href: 'https://www.youtube.com/@spiceai',
293 position: 'right'
294 },
295 {
296 href: 'https://github.com/spiceai/spiceai',
297 position: 'right',
298 className: 'header-github-link',
299 'aria-label': 'GitHub repository'
300 }
301 ]
302 },
303 footer: {
304 style: 'dark',
305 links: [
306 {
307 title: 'Docs',
308 items: [
309 {
310 label: 'Getting Started',
311 to: '/docs/getting-started'
312 },
313 {
314 label: 'API',
315 to: '/docs/api'
316 },
317 {
318 label: 'CLI',
319 to: '/docs/cli'
320 },
321 {
322 label: 'SDKs',
323 to: '/docs/sdks'
324 }
325 ]
326 },
327 {
328 title: 'Community',
329 items: [
330 {
331 label: 'Reddit',
332 href: 'https://reddit.com/r/spiceai'
333 },
334 {
335 label: 'Slack',
336 href: 'https://spice.ai/slack'
337 },
338 {
339 label: 'X',
340 href: 'https://x.com/spice_ai'
341 },
342 {
343 label: 'YouTube',
344 href: 'https://www.youtube.com/@spiceai'
345 }
346 ]
347 },
348 {
349 title: 'More',
350 items: [
351 {
352 label: 'Blog',
353 href: 'https://spice.ai/blog'
354 },
355 {
356 label: 'GitHub',
357 href: 'https://github.com/spiceai/spiceai'
358 }
359 ]
360 }
361 ],
362 copyright: `Copyright © 2025 Spice AI, Inc.`
363 },
364 languageTabs: [
365 {
366 highlight: 'bash',
367 language: 'curl',
368 logoClass: 'curl'
369 }
370 ],
371 prism: {
372 theme: prismThemes.oneLight,
373 darkTheme: prismThemes.gruvboxMaterialDark,
374 additionalLanguages: ['bash', 'json', 'csharp']
375 },
376 algolia: {
377 appId: '0SP8I8JTL8',
378 apiKey: '72f66fe334ccd3c7db696a123d68735c',
379 indexName: 'spiceai',
380 contextualSearch: true
381 }
382 } satisfies Preset.ThemeConfig,
383
384 headTags: [
385 // Structured data for SEO (JSON-LD)
386 {
387 tagName: 'script',
388 attributes: {
389 type: 'application/ld+json'
390 },
391 innerHTML: JSON.stringify({
392 '@context': 'https://schema.org',
393 '@type': 'SoftwareApplication',
394 name: 'Spice.ai OSS',
395 applicationCategory: 'DeveloperApplication',
396 operatingSystem: 'Linux, macOS, Windows',
397 description:
398 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
399 url: 'https://spiceai.org',
400 downloadUrl: 'https://github.com/spiceai/spiceai/releases',
401 softwareVersion: '1.10.0',
402 author: {
403 '@type': 'Organization',
404 name: 'Spice AI, Inc.',
405 url: 'https://spice.ai'
406 },
407 license: 'https://github.com/spiceai/spiceai/blob/trunk/LICENSE',
408 programmingLanguage: 'Rust',
409 offers: {
410 '@type': 'Offer',
411 price: '0',
412 priceCurrency: 'USD'
413 }
414 })
415 },
416 {
417 tagName: 'script',
418 attributes: {
419 type: 'application/ld+json'
420 },
421 innerHTML: JSON.stringify({
422 '@context': 'https://schema.org',
423 '@type': 'Organization',
424 name: 'Spice AI, Inc.',
425 url: 'https://spice.ai',
426 logo: 'https://spiceai.org/img/logo.svg',
427 sameAs: [
428 'https://github.com/spiceai',
429 'https://x.com/spice_ai',
430 'https://www.youtube.com/@spiceai',
431 'https://www.linkedin.com/company/spiceai'
432 ]
433 })
434 },
435 {
436 tagName: 'link',
437 attributes: {
438 rel: 'preconnect',
439 href: 'https://fonts.googleapis.com'
440 }
441 },
442 {
443 tagName: 'link',
444 attributes: {
445 rel: 'preconnect',
446 href: 'https://fonts.gstatic.com',
447 crossorigin: 'true'
448 }
449 },
450 {
451 tagName: 'link',
452 attributes: {
453 href: 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap',
454 rel: 'stylesheet'
455 }
456 },
457 {
458 tagName: 'link',
459 attributes: {
460 rel: 'icon',
461 type: 'image/png',
462 sizes: '32x32',
463 href: '/favicon-32x32.png'
464 }
465 },
466 {
467 tagName: 'link',
468 attributes: {
469 rel: 'icon',
470 type: 'image/png',
471 sizes: '16x16',
472 href: '/favicon-16x16.png'
473 }
474 },
475 {
476 tagName: 'script',
477 attributes: {
478 type: 'text/javascript',
479 id: 'hs-script-loader',
480 async: 'true',
481 defer: 'true',
482 src: '//js.hs-scripts.com/46107967.js'
483 }
484 },
485 // SEO: hreflang for language targeting
486 {
487 tagName: 'link',
488 attributes: {
489 rel: 'alternate',
490 hreflang: 'en',
491 href: 'https://spiceai.org'
492 }
493 },
494 {
495 tagName: 'link',
496 attributes: {
497 rel: 'alternate',
498 hreflang: 'x-default',
499 href: 'https://spiceai.org'
500 }
501 }
502 ],
503
504 plugins: [
505 tailwindPlugin,
506 [
507 '@docusaurus/plugin-content-blog',
508 {
509 id: 'releases',
510 path: 'releases',
511 routeBasePath: 'releases',
512 showLastUpdateAuthor: true,
513 showLastUpdateTime: true,
514 onInlineTags: 'throw',
515 onUntruncatedBlogPosts: 'ignore',
516 editUrl: ({ locale, blogDirPath, blogPath }) => {
517 return `https://github.com/spiceai/docs/edit/trunk/website/${blogDirPath}/${blogPath}`
518 },
519 remarkPlugins: [],
520 postsPerPage: 10,
521 feedOptions: {
522 type: 'all',
523 description: 'Keep up to date with Spice.ai OSS releases by following our feed!',
524 copyright: `Copyright © 2025-2026 Spice AI, Inc.`,
525 xslt: true
526 },
527 blogTitle: 'Spice.ai OSS Releases',
528 blogDescription: 'Spice.ai OSS release notes and announcements',
529 blogSidebarCount: 'ALL',
530 blogSidebarTitle: 'All Releases'
531 } satisfies BlogOptions
532 ],
533 [
534 'docusaurus-plugin-openapi-docs',
535 {
536 id: 'api',
537 docsPluginId: 'classic',
538 config: {
539 spice: {
540 proxy: 'http://localhost:8090',
541
542 specPath: 'public/openapi.json',
543 outputDir: 'docs/api/HTTP',
544 sidebarOptions: {
545 groupPathsBy: 'tag'
546 }
547 } satisfies OpenApiPlugin.Options
548 }
549 }
550 ],
551 [
552 '@docusaurus/plugin-client-redirects',
553 {
554 redirects: [
555 {
556 from: '/blog',
557 to: 'https://spice.ai/blog'
558 },
559 {
560 from: '/blog/announcing-1.0-stable',
561 to: 'https://spice.ai/blog/announcing-spice-ai-open-source-1-0-stable'
562 },
563 {
564 from: '/blog/amazon-s3-vectors-with-spice',
565 to: 'https://spice.ai/blog/getting-started-with-amazon-s3-vectors-and-spice'
566 },
567 {
568 from: '/blog/releases/v1.10-0',
569 to: '/releases/v1.10.0'
570 },
571 {
572 from: '/query-federation',
573 to: '/docs/features/query-federation'
574 },
575 {
576 from: '/docs/next/features/large-language-models/evaluate',
577 to: '/docs/next/features/large-language-models/decisions'
578 },
579 // 2021 blog posts
580 {
581 from: '/blog/a-new-class-of-applications-that-learn-and-adapt',
582 to: 'https://spice.ai/blog/a-new-class-of-applications-that-learn-and-adapt'
583 },
584 {
585 from: '/blog/2021/a-new-class-of-applications-that-learn-and-adapt',
586 to: 'https://spice.ai/blog/a-new-class-of-applications-that-learn-and-adapt'
587 },
588 {
589 from: '/blog/ai-needs-ai-ready-data',
590 to: 'https://spice.ai/blog/ai-needs-ai-ready-data'
591 },
592 {
593 from: '/blog/2021/ai-needs-ai-ready-data',
594 to: 'https://spice.ai/blog/ai-needs-ai-ready-data'
595 },
596 {
597 from: '/blog/making-apps-that-learn-and-adapt',
598 to: 'https://spice.ai/blog/making-apps-that-learn-and-adapt'
599 },
600 {
601 from: '/blog/2021/making-apps-that-learn-and-adapt',
602 to: 'https://spice.ai/blog/making-apps-that-learn-and-adapt'
603 },
604 {
605 from: '/blog/q-learning-reward-is-all-you-need',
606 to: 'https://spice.ai/blog/q-learning-reward-is-all-you-need'
607 },
608 {
609 from: '/blog/spiceais-approach-to-time-series-ai',
610 to: 'https://spice.ai/blog/spiceais-approach-to-time-series-ai'
611 },
612 {
613 from: '/blog/2021/spiceais-approach-to-time-series-ai',
614 to: 'https://spice.ai/blog/spiceais-approach-to-time-series-ai'
615 },
616 {
617 from: '/blog/spicepods-from-zero-to-hero',
618 to: 'https://spice.ai/blog/spicepods-from-zero-to-hero'
619 },
620 {
621 from: '/blog/2021/spicepods-from-zero-to-hero',
622 to: 'https://spice.ai/blog/spicepods-from-zero-to-hero'
623 },
624 {
625 from: '/blog/teaching-apps-how-to-learn-with-spicepods',
626 to: 'https://spice.ai/blog/teaching-apps-how-to-learn-with-spicepods'
627 },
628 // 2022 blog posts
629 {
630 from: '/blog/adding-soft-actor-critic',
631 to: 'https://spice.ai/blog/adding-soft-actor-critic'
632 },
633 {
634 from: '/blog/building-on-apache-arrow-and-flight',
635 to: 'https://spice.ai/blog/building-on-apache-arrow-and-flight'
636 },
637 {
638 from: '/blog/what-data-informs-ai-driven-decision-making',
639 to: 'https://spice.ai/blog/what-data-informs-ai-driven-decision-making'
640 },
641 {
642 from: '/blog/2022/what-data-informs-ai-driven-decision-making',
643 to: 'https://spice.ai/blog/what-data-informs-ai-driven-decision-making'
644 },
645 // 2024 blog posts
646 {
647 from: '/blog/adding-spice',
648 to: 'https://spice.ai/blog/adding-spice-the-next-generation-of-spice-ai-oss'
649 },
650 {
651 from: '/blog/2024/adding-spice',
652 to: 'https://spice.ai/blog/adding-spice--the-next-generation-of-spice-ai-oss'
653 },
654 {
655 from: '/federated-queries',
656 to: '/docs/features/query-federation'
657 },
658 {
659 from: '/data-ingestion',
660 to: '/docs/features/data-ingestion'
661 },
662 {
663 from: '/data-acceleration',
664 to: '/docs/features/data-acceleration'
665 },
666 {
667 from: '/monitoring',
668 to: '/docs/features/observability'
669 }
670 ]
671 }
672 ],
673 [
674 '@signalwire/docusaurus-plugin-llms-txt',
675 {
676 siteTitle: 'Spice.ai OSS',
677 siteDescription:
678 'A portable SQL query and AI compute engine, written in Rust, for data-grounded apps and agents.',
679 depth: 2,
680 logLevel: 1,
681 content: {
682 includeBlog: false,
683 includePages: true,
684 includeDocs: true,
685 includeVersionedDocs: false,
686 enableLlmsFullTxt: true,
687 // Served to agents that request text/markdown (functions/_middleware.js).
688 enableMarkdownFiles: true,
689 excludeRoutes: ['/tags/**', '/search', '/api/HTTP/**'],
690 // The plugin's default selectors, then .main-wrapper for the homepage, which has no <main>
691 // element. Without it, the homepage Markdown includes the navbar.
692 contentSelectors: [
693 '.theme-doc-markdown',
694 'main .container .col',
695 'main .theme-doc-wrapper',
696 'article',
697 'main .container',
698 'main',
699 '.main-wrapper'
700 ]
701 },
702 optionalLinks: [
703 {
704 title: 'GitHub Repository',
705 url: 'https://github.com/spiceai/spiceai',
706 description: 'Spice.ai OSS source code and issue tracker'
707 },
708 {
709 title: 'Cookbook',
710 url: 'https://github.com/spiceai/cookbook',
711 description: 'Ready-to-use recipes and examples for Spice.ai'
712 },
713 {
714 title: 'Spice Cloud Platform',
715 url: 'https://spice.ai',
716 description: 'Managed cloud platform for Spice.ai'
717 }
718 ]
719 }
720 ],
721 [
722 agentDiscoveryPlugin,
723 {
724 openApiSpec: 'public/openapi.json',
725 skillsRepository: 'spiceai/skills',
726 // The commit of the spiceai/skills v2.3.2 release. A commit, unlike a tag, cannot be moved to
727 // different content. Update it when a new skills release ships.
728 skillsCommit: 'b9bab4ec4d6d4d046d550c9866a784c0e952c0e0'
729 }
730 ]
731 ]
732}
733
734export default config
735
