How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.
Persistence is Doctrine ORM over MySQL. Mapping is done with attributes on entity classes in src/, and the schema is versioned with Doctrine Migrations.
IDs are UUIDv7 values stored as binary. They’re unique without a round trip, and they sort by creation time, which is what makes cursor pagination possible.
Domain code depends on repository interfaces; Doctrine implementations are bound during boot. The shared base class is immutable, so filters return a narrowed clone rather than mutating shared state:
Handlers call add() and entity methods; the entity manager is flushed once, after the response is emitted. Console commands and the cron entry point flush too.Flush explicitly only when you must, such as when streaming a response that has to persist state before it finishes.
Files are named by the version they belong to, such as Version50000 for 5.0.0, alongside timestamped ones for changes within a release.
migrations:diff compares the database against the entity mappings. Always read the generated SQL before running it: it will happily drop a column that an extension added outside the mapping.
Schema changes aren’t always enough: an update may need to reshape existing rows. Those live in migrations/update as classes implementing the migration interface, and are run in order by the migration manager, which records what it has run in the migrated option so each one runs once.The update flow triggers them after the schema migration.
php bin/console orm:validate-schema # mappings versus the databasephp bin/console orm:info # list mapped entitiesphp bin/console orm:schema-tool:update --dump-sqlphp bin/console dbal:run-sql "SELECT COUNT(*) FROM workspace"
orm:schema-tool:update --force bypasses migrations and leaves the migration table out of step with reality. Use migrations on anything but a scratch database.
Adding columns to core tables is a fork: the next update’s migrations don’t know about them, and migrations:diff will propose dropping them. If you need extra data, see Data and persistence.
Anything that changes the schema, including an update, should be preceded by a database dump. The update flow migrates in place, and there’s no automatic rollback.
{"docsConfig":{"theme":"aspen","$schema":"https://mintlify.com/docs.json","name":"Aikeedo Docs","description":"Self-hosted software with customizable features to launch your AI SaaS business quickly and generate revenue fast.","colors":{"primary":"#24282c","light":"#07C983","dark":"#24282c"},"logo":{"light":"https://mintcdn.com/aikeedo/AhiAA6H0ZT4BonAW/logo/light.svg?fit=max\u0026auto=format\u0026n=AhiAA6H0ZT4BonAW\u0026q=85\u0026s=fc9332778893ff8eda4b5ce14142b391","dark":"https://mintcdn.com/aikeedo/AhiAA6H0ZT4BonAW/logo/dark.svg?fit=max\u0026auto=format\u0026n=AhiAA6H0ZT4BonAW\u0026q=85\u0026s=6c14d9ba24e13514459c53f3b5d1c321"},"favicon":"https://mintcdn.com/aikeedo/vTLcl7xb6rBrq85h/favicon.ico?fit=max\u0026auto=format\u0026n=vTLcl7xb6rBrq85h\u0026q=85\u0026s=5d27267681fd6d9a3947a07360b817e5","navbar":{"links":[{"href":"https://aikeedo.com/support/?utm_source=docs\u0026utm_medium=nav","label":"Support"}],"primary":{"type":"button","label":"Buy now","href":"https://aikeedo.com?utm_source=docs\u0026utm_medium=header"}},"footer":{"socials":{"website":"https://aikeedo.com?utm_source=docs\u0026utm_medium=footer","x":"https://x.com/heyaikeedo","github":"https://github.com/heyaikeedo","linkedin":"https://www.linkedin.com/company/heyaikeedo","youtube":"https://www.youtube.com/@heyaikeedo","instagram":"https://www.instagram.com/heyaikeedo","facebook":"https://www.facebook.com/heyaikeedo"}},"seo":{"indexing":"all"},"icons":{"library":"tabler"},"integrations":{"gtm":{"tagId":"GTM-WJJWH53N"}},"contextual":{"options":["copy","view","chatgpt","claude","mcp","cursor","vscode"],"display":"header"},"styling":{"latex":false},"navigation":{"global":{"anchors":[{"anchor":"Affiliates","icon":"coin","href":"https://aikeedo.com/affiliates/?utm_source=docs\u0026utm_medium=nav"},{"anchor":"Changelog","icon":"chart-area-line","href":"https://aikeedo.com/changelog/?utm_source=docs\u0026utm_medium=nav"},{"anchor":"Feature Requests","icon":"star","href":"https://feedback.aikeedo.com/"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"aikeedo","hasAudioNarration":false,"actualSubdomain":"aikeedo","customDomains":["docs.aikeedo.com"],"gitSource":{"type":"github","owner":"heyaikeedo","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ac51820143b90197a75803b","collectionId":"e19d0d9f-1044-448e-8bbc-48789dc10ec4","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ac51820143b90197a75803b"},"feedback":{"thumbs":true,"edits":true,"issues":true,"agentFeedback":true},"entitlements":{},"buildId":"6ac51820143b90197a75803b","clientVersion":"0.0.3990","searchFilterCounts":[{"language":"default","tag":"Development","count":83},{"language":"default","tag":"Docs","count":80},{"language":"default","tag":"Integrations","count":40},{"language":"default","tag":"Plugins","count":23},{"language":"default","tag":"Core Architecture","count":21},{"language":"default","tag":"Themes","count":19},{"language":"default","tag":"Payment Gateways","count":17},{"language":"default","tag":"Building Blocks","count":15},{"language":"default","tag":"Extension Guides","count":15},{"language":"default","tag":"Advanced","count":11}]},"assistantConfig":{},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"development/core/database-and-migrations","slug":"/development/core/database-and-migrations","pageMetadata":{"title":"Database and migrations","sidebarTitle":"Database","description":"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.","href":"/development/core/database-and-migrations"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Table,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`Persistence is Doctrine ORM over MySQL. Mapping is done with attributes on entity classes in `,_jsx(_components.code,{children:`src/`}),`, and the schema is versioned with Doctrine Migrations.`]}),`\n`,_jsx(Heading,{level:`2`,id:`mapping`,children:`Mapping`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`14`,language:`php`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`php`,children:_jsxs(_components.code,{language:`php`,numberOfLines:`14`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`#[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\Entity`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`#[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\Table`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'category'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`#[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\HasLifecycleCallbacks`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`class`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#4EC9B0`},children:` CategoryEntity`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` #[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\Embedded`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`class`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:`Id`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`::`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`class`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`columnPrefix`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` private`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:` Id`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $id`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` #[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\Embedded`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`class`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:`Title`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`::`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`class`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`columnPrefix`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` private`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:` Title`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $title`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` #[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`ORM\\\\Column`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`type`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:`Types`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`::`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`DATETIME_IMMUTABLE`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'created_at'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)]`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` private`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:` DateTimeInterface`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $createdAt`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Value objects are embeddables, so validation lives in the value object and the column stays flat.`}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Only `,_jsx(_components.code,{children:`src/`}),` is scanned for mappings. Plugins can’t add entities. See `,_jsx(_components.a,{href:`/development/plugins/data-and-persistence`,children:`Data and persistence`}),`.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`identifiers`,children:`Identifiers`}),`\n`,_jsx(_components.p,{children:`IDs are UUIDv7 values stored as binary. They’re unique without a round trip, and they sort by creation time, which is what makes cursor pagination possible.`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`php`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`php`,children:_jsxs(_components.code,{language:`php`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`$id`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $entity`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`->`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`getId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(); `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// Shared\\\\Domain\\\\ValueObjects\\\\Id`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`$string`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`string`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`$id`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`->`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`getValue`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`();`})]}),`\n`]})})}),`\n`,_jsx(Heading,{level:`2`,id:`inheritance`,children:`Inheritance`}),`\n`,_jsx(_components.p,{children:`Several tables use single-table inheritance with a discriminator column, so related kinds share one table:`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Table`}),_jsx(_components.th,{children:`Holds`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`library_item`})}),_jsx(_components.td,{children:`Conversations, images, videos, speeches, transcriptions, isolated voices, classifications, memories and canvas artifacts`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`file`})}),_jsx(_components.td,{children:`Generic files and images`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`data_unit`})}),_jsx(_components.td,{children:`Knowledge base sources of different kinds`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`stat`})}),_jsx(_components.td,{children:`Usage, signup and subscription statistics`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`repositories`,children:`Repositories`}),`\n`,_jsx(_components.p,{children:`Domain code depends on repository interfaces; Doctrine implementations are bound during boot. The shared base class is immutable, so filters return a narrowed clone rather than mutating shared state:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`4`,language:`php`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`php`,children:_jsxs(_components.code,{language:`php`,numberOfLines:`4`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`$orders`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $repo`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` ->`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`filterByWorkspace`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`$workspace`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` ->`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`filterByStatus`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4EC9B0`},children:`OrderStatus`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`::`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`COMPLETED`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` ->`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`slice`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`20`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Two behaviors come from the base:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Soft deletes.`}),` Entities with a `,_jsx(_components.code,{children:`deletedAt`}),` field are excluded automatically.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Cursor pagination.`}),` Results can start after, or end before, a known ID, which is what the API’s `,_jsx(_components.code,{children:`starting_after`}),` and `,_jsx(_components.code,{children:`ending_before`}),` parameters use.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`when-writes-happen`,children:`When writes happen`}),`\n`,_jsxs(_components.p,{children:[`Handlers call `,_jsx(_components.code,{children:`add()`}),` and entity methods; the entity manager is flushed once, after the response is emitted. Console commands and the cron entry point flush too.`]}),`\n`,_jsx(_components.p,{children:`Flush explicitly only when you must, such as when streaming a response that has to persist state before it finishes.`}),`\n`,_jsx(Heading,{level:`2`,id:`schema-migrations`,children:`Schema migrations`}),`\n`,_jsxs(_components.p,{children:[`Migrations live in `,_jsx(_components.code,{children:`migrations/mysql`}),`, are namespaced `,_jsx(_components.code,{children:`Migrations\\\\MySql`}),`, and are tracked in a `,_jsx(_components.code,{children:`migration`}),` table.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`4`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`4`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` migrations:status`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` migrations:migrate`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` migrations:diff`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # generate from mapping changes`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` migrations:execute`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --up`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'Migrations\\\\MySql\\\\Version50000'`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Files are named by the version they belong to, such as `,_jsx(_components.code,{children:`Version50000`}),` for 5.0.0, alongside timestamped ones for changes within a release.`]}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`migrations:diff`}),` compares the database against the entity mappings. Always read the generated SQL before running it: it will happily drop a column that an extension added outside the mapping.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`data-migrations`,children:`Data migrations`}),`\n`,_jsxs(_components.p,{children:[`Schema changes aren’t always enough: an update may need to reshape existing rows. Those live in `,_jsx(_components.code,{children:`migrations/update`}),` as classes implementing the migration interface, and are run in order by the migration manager, which records what it has run in the `,_jsx(_components.code,{children:`migrated`}),` option so each one runs once.`]}),`\n`,_jsx(_components.p,{children:`The update flow triggers them after the schema migration.`}),`\n`,_jsx(Heading,{level:`2`,id:`working-with-the-schema`,children:`Working with the schema`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`4`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`4`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` orm:validate-schema`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # mappings versus the database`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` orm:info`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # list mapped entities`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` orm:schema-tool:update`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --dump-sql`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`php`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` bin/console`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` dbal:run-sql`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"SELECT COUNT(*) FROM workspace\"`})]}),`\n`]})})}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`orm:schema-tool:update --force`}),` bypasses migrations and leaves the migration table out of step with reality. Use migrations on anything but a scratch database.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`tables`,children:`Tables`}),`\n`,_jsxs(_components.p,{children:[`Core tables include `,_jsx(_components.code,{children:`user`}),`, `,_jsx(_components.code,{children:`workspace`}),`, `,_jsx(_components.code,{children:`workspace_invitation`}),`, `,_jsx(_components.code,{children:`plan`}),`, `,_jsx(_components.code,{children:`plan_snapshot`}),`, `,_jsx(_components.code,{children:`subscription`}),`, `,_jsx(_components.code,{children:`order`}),`, `,_jsx(_components.code,{children:`coupon`}),`, `,_jsx(_components.code,{children:`option`}),`, `,_jsx(_components.code,{children:`category`}),`, `,_jsx(_components.code,{children:`preset`}),`, `,_jsx(_components.code,{children:`assistant`}),`, `,_jsx(_components.code,{children:`voice`}),`, `,_jsx(_components.code,{children:`file`}),`, `,_jsx(_components.code,{children:`library_item`}),`, `,_jsx(_components.code,{children:`message`}),`, `,_jsx(_components.code,{children:`artifact_version`}),`, `,_jsx(_components.code,{children:`data_unit`}),`, `,_jsx(_components.code,{children:`chatbot`}),`, `,_jsx(_components.code,{children:`chatbot_message`}),`, `,_jsx(_components.code,{children:`chatbot_conversation`}),`, `,_jsx(_components.code,{children:`contact`}),`, `,_jsx(_components.code,{children:`company`}),`, `,_jsx(_components.code,{children:`affiliate`}),`, `,_jsx(_components.code,{children:`payout`}),`, `,_jsx(_components.code,{children:`import_job`}),`, `,_jsx(_components.code,{children:`stat`}),` and `,_jsx(_components.code,{children:`migration`}),`.`]}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Adding columns to core tables is a fork: the next update’s migrations don’t know about them, and `,_jsx(_components.code,{children:`migrations:diff`}),` will propose dropping them. If you need extra data, see `,_jsx(_components.a,{href:`/development/plugins/data-and-persistence`,children:`Data and persistence`}),`.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`backups`,children:`Backups`}),`\n`,_jsx(_components.p,{children:`Anything that changes the schema, including an update, should be preceded by a database dump. The update flow migrates in place, and there’s no automatic rollback.`}),`\n`,_jsx(Heading,{level:`2`,id:`related`,children:`Related`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/development/core/modules-and-layers`,children:`Modules and layers`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/development/core/console-commands`,children:`Console commands`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/versioning/how-to-update`,children:`How to update`})}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Database and migrations","sidebarTitle":"Database","description":"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.","href":"/development/core/database-and-migrations"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Mapping","slug":"mapping","depth":2,"children":[]},{"title":"Identifiers","slug":"identifiers","depth":2,"children":[]},{"title":"Inheritance","slug":"inheritance","depth":2,"children":[]},{"title":"Repositories","slug":"repositories","depth":2,"children":[]},{"title":"When writes happen","slug":"when-writes-happen","depth":2,"children":[]},{"title":"Schema migrations","slug":"schema-migrations","depth":2,"children":[]},{"title":"Data migrations","slug":"data-migrations","depth":2,"children":[]},{"title":"Working with the schema","slug":"working-with-the-schema","depth":2,"children":[]},{"title":"Tables","slug":"tables","depth":2,"children":[]},{"title":"Backups","slug":"backups","depth":2,"children":[]},{"title":"Related","slug":"related","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"aikeedo","siteName":"Aikeedo Docs","title":"Database and migrations - Aikeedo Docs","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-09-17T19:23:16.965Z","originalFileLocation":"development/core/database-and-migrations.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Aikeedo Docs\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Database and migrations - Aikeedo Docs\">\u003cmeta data-mint-head property=\"og:description\" content=\"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Aikeedo Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.aikeedo.com/development/core/database-and-migrations\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://aikeedo.mintlify.app/_mintlify/api/og/v2?division=Subsystems\u0026amp;title=Database+and+migrations\u0026amp;description=How+Aikeedo+maps+entities+with+Doctrine%2C+identifies+rows%2C+paginates%2C+and+applies+schema+and+data+migrations.\u0026amp;theme=5b73d043ae552be97b4ec072\u0026amp;signature=0f9db40d917eacb24f7db2dd5c383b18639cbdd3883bfa765616bb33a77c4ae6\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Database and migrations - Aikeedo Docs\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://aikeedo.mintlify.app/_mintlify/api/og/v2?division=Subsystems\u0026amp;title=Database+and+migrations\u0026amp;description=How+Aikeedo+maps+entities+with+Doctrine%2C+identifies+rows%2C+paginates%2C+and+applies+schema+and+data+migrations.\u0026amp;theme=5b73d043ae552be97b4ec072\u0026amp;signature=0f9db40d917eacb24f7db2dd5c383b18639cbdd3883bfa765616bb33a77c4ae6\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/aikeedo/qXdfuDxaHkWXOEhU/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Aikeedo Docs\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#24282c\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.aikeedo.com/development/core/database-and-migrations\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/development/core/database-and-migrations.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.aikeedo.com/#organization\",\"name\":\"Aikeedo Docs\",\"url\":\"https://docs.aikeedo.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/aikeedo/AhiAA6H0ZT4BonAW/logo/light.svg?fit=max\u0026auto=format\u0026n=AhiAA6H0ZT4BonAW\u0026q=85\u0026s=fc9332778893ff8eda4b5ce14142b391\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.aikeedo.com#website\",\"name\":\"Aikeedo Docs\",\"url\":\"https://docs.aikeedo.com\",\"publisher\":{\"@id\":\"https://docs.aikeedo.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.aikeedo.com/development/core/database-and-migrations#webpage\",\"url\":\"https://docs.aikeedo.com/development/core/database-and-migrations\",\"name\":\"Database and migrations\",\"description\":\"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\",\"dateModified\":\"2026-09-17T19:23:16.965Z\",\"isPartOf\":{\"@id\":\"https://docs.aikeedo.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.aikeedo.com/development/core/database-and-migrations#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.aikeedo.com/development/core/database-and-migrations#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Core Architecture\",\"item\":\"https://docs.aikeedo.com/development/core/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Database and migrations\",\"item\":\"https://docs.aikeedo.com/development/core/database-and-migrations\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.aikeedo.com/development/core/database-and-migrations#article\",\"headline\":\"Database and migrations\",\"name\":\"Database and migrations\",\"description\":\"How Aikeedo maps entities with Doctrine, identifies rows, paginates, and applies schema and data migrations.\",\"url\":\"https://docs.aikeedo.com/development/core/database-and-migrations\",\"mainEntityOfPage\":{\"@id\":\"https://docs.aikeedo.com/development/core/database-and-migrations#webpage\"},\"dateModified\":\"2026-09-17T19:23:16.965Z\",\"publisher\":{\"@id\":\"https://docs.aikeedo.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.aikeedo.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Aikeedo Docs\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Aikeedo Docs","nav":{"global":{"anchors":[{"anchor":"Affiliates","icon":"coin","href":"https://aikeedo.com/affiliates/?utm_source=docs\u0026utm_medium=nav"},{"anchor":"Changelog","icon":"chart-area-line","href":"https://aikeedo.com/changelog/?utm_source=docs\u0026utm_medium=nav"},{"anchor":"Feature Requests","icon":"star","href":"https://feedback.aikeedo.com/"}]},"tabs":[{"tab":"Docs","pages":[{"title":"Docs","href":"/overview/introduction"}]},{"tab":"Development","groups":[{"group":"Get Started","pages":[{"title":"Aikeedo developer documentation","sidebarTitle":"Introduction","href":"/development/introduction"},{"title":"Local development environment","sidebarTitle":"Local development","href":"/development/local-development"},{"title":"Extending Aikeedo","sidebarTitle":"Extending Aikeedo","href":"/development/extending-aikeedo"},{"title":"Coding standards and quality tools","sidebarTitle":"Coding standards","href":"/development/coding-standards"},{"title":"Get your add-on listed","sidebarTitle":"Get listed","href":"/development/community-add-ons"}]},{"group":"Plugins","pages":[{"title":"Aikeedo plugins","sidebarTitle":"Overview","href":"/development/plugins/overview"},{"title":"Build your first plugin","sidebarTitle":"Quickstart","href":"/development/plugins/quickstart"},{"title":"Plugin manifest reference","sidebarTitle":"Manifest","href":"/development/plugins/manifest"},{"title":"Plugin lifecycle and hooks","sidebarTitle":"Lifecycle and hooks","href":"/development/plugins/lifecycle-and-hooks"},{"group":"Building Blocks","pages":[{"title":"Dependency injection and core services","sidebarTitle":"Dependency injection","href":"/development/plugins/dependency-injection-and-services"},{"title":"Routes and request handlers","sidebarTitle":"Routes and handlers","href":"/development/plugins/routes-and-request-handlers"},{"title":"Admin settings pages","sidebarTitle":"Admin settings pages","href":"/development/plugins/admin-settings-pages"},{"title":"App pages and workspace APIs","sidebarTitle":"App pages and APIs","href":"/development/plugins/app-pages-and-apis"},{"title":"Frontend integration","sidebarTitle":"Frontend integration","href":"/development/plugins/frontend-integration"},{"title":"Navigation","sidebarTitle":"Navigation","href":"/development/plugins/navigation"},{"title":"Events and scheduled work","sidebarTitle":"Events and cron","href":"/development/plugins/events-and-cron"},{"title":"Data and persistence","sidebarTitle":"Data and persistence","href":"/development/plugins/data-and-persistence"},{"title":"Files and storage","sidebarTitle":"Files and storage","href":"/development/plugins/files-and-storage"},{"title":"Using AI models and credits","sidebarTitle":"AI models and credits","href":"/development/plugins/ai-models-and-credits"},{"title":"Streaming responses","sidebarTitle":"Streaming responses","href":"/development/plugins/streaming-responses"},{"title":"Public pages and endpoints","sidebarTitle":"Public endpoints","href":"/development/plugins/public-endpoints-and-embeds"},{"title":"Public assets","sidebarTitle":"Public assets","href":"/development/plugins/public-assets"},{"title":"Plugin localization","sidebarTitle":"Localization","href":"/development/plugins/localization"},{"title":"Custom extension points","sidebarTitle":"Custom extension points","href":"/development/plugins/custom-extension-points"}]},{"group":"Ship \u0026 Maintain","pages":[{"title":"Packaging and distribution","sidebarTitle":"Packaging","href":"/development/plugins/packaging-and-distribution"},{"title":"Debugging plugins","sidebarTitle":"Debugging","href":"/development/plugins/debugging"},{"title":"Upgrading plugins to Aikeedo 5.0","sidebarTitle":"Upgrading to v5","href":"/development/plugins/upgrading-to-v5"}]}]},{"group":"Extension Guides","pages":[{"group":"Payment Gateways","pages":[{"title":"Payment gateways","sidebarTitle":"Overview","href":"/development/plugins/guides/payments/overview"},{"title":"Build a payment gateway","sidebarTitle":"Build a gateway","href":"/development/plugins/guides/payments/building-a-gateway"},{"title":"Checkout flows","sidebarTitle":"Checkout flows","href":"/development/plugins/guides/payments/checkout-flows"},{"title":"Subscriptions and renewals","sidebarTitle":"Subscriptions","href":"/development/plugins/guides/payments/subscriptions-and-renewals"},{"title":"Payment webhooks","sidebarTitle":"Webhooks","href":"/development/plugins/guides/payments/webhooks"}]},{"title":"Build a tax engine","sidebarTitle":"Tax engines","href":"/development/plugins/guides/tax-engine"},{"title":"Build a storage adapter","sidebarTitle":"Storage adapters","href":"/development/plugins/guides/storage-adapter"},{"title":"Build a vector store","sidebarTitle":"Vector stores","href":"/development/plugins/guides/vector-store"},{"title":"Add an AI provider","sidebarTitle":"AI providers","href":"/development/plugins/guides/ai-provider"},{"title":"Add an AI tool","sidebarTitle":"AI tools","href":"/development/plugins/guides/ai-tool"},{"title":"Build a currency rate provider","sidebarTitle":"Currency rate providers","href":"/development/plugins/guides/currency-rate-provider"},{"title":"Add per-plan settings","sidebarTitle":"Plan config extensions","href":"/development/plugins/guides/plan-config-extension"},{"title":"Build an event-driven integration","sidebarTitle":"Event-driven integrations","href":"/development/plugins/guides/event-driven-integration"},{"title":"Add an import adapter","sidebarTitle":"Import adapters","href":"/development/plugins/guides/import-adapter"},{"title":"Build an embeddable widget","sidebarTitle":"Embeddable widgets","href":"/development/plugins/guides/embeddable-widget"}]},{"group":"Themes","pages":[{"title":"Aikeedo themes","sidebarTitle":"Overview","href":"/development/themes/overview"},{"title":"Build your first theme","sidebarTitle":"Quickstart","href":"/development/themes/quickstart"},{"title":"The minimum theme","sidebarTitle":"Minimum theme","href":"/development/themes/minimum-theme"},{"title":"Theme structure","sidebarTitle":"Structure","href":"/development/themes/structure"},{"title":"Theme manifest","sidebarTitle":"Manifest","href":"/development/themes/manifest"},{"title":"Build pipeline and assets","sidebarTitle":"Build and assets","href":"/development/themes/build-and-assets"},{"group":"Templates","pages":[{"title":"Templates and layouts","sidebarTitle":"Templates and layouts","href":"/development/themes/templates-and-layouts"},{"title":"Pricing and plans","sidebarTitle":"Pricing and plans","href":"/development/themes/pricing-and-plans"},{"title":"Users and navigation","sidebarTitle":"Users and navigation","href":"/development/themes/users-and-navigation"},{"title":"Dark mode and colors","sidebarTitle":"Dark mode and colors","href":"/development/themes/dark-mode-and-colors"},{"title":"Script tags and analytics","sidebarTitle":"Script tags","href":"/development/themes/script-tags-and-analytics"},{"title":"JavaScript and components","sidebarTitle":"JavaScript","href":"/development/themes/javascript-components"},{"title":"Custom pages","sidebarTitle":"Custom pages","href":"/development/themes/custom-pages"},{"title":"Theme localization","sidebarTitle":"Localization","href":"/development/themes/localization"}]},{"group":"Reference","pages":[{"title":"Twig reference","sidebarTitle":"Twig reference","href":"/development/themes/twig-reference"},{"title":"Template objects reference","sidebarTitle":"Template objects","href":"/development/themes/template-objects"}]},{"group":"Ship \u0026 Maintain","pages":[{"title":"Customizing an existing theme","sidebarTitle":"Customizing themes","href":"/development/themes/customizing-existing-themes"},{"title":"Packaging and publishing","sidebarTitle":"Packaging","href":"/development/themes/packaging-and-publishing"},{"title":"Theme troubleshooting","sidebarTitle":"Troubleshooting","href":"/development/themes/troubleshooting"}]}]},{"group":"Core Architecture","pages":[{"group":"Foundations","pages":[{"title":"Core architecture","sidebarTitle":"Overview","href":"/development/core/overview"},{"title":"Directory structure","sidebarTitle":"Directory structure","href":"/development/core/directory-structure"},{"title":"Bootstrap and request lifecycle","sidebarTitle":"Bootstrap and lifecycle","href":"/development/core/bootstrap-and-lifecycle"},{"title":"Modules and layers","sidebarTitle":"Modules and layers","href":"/development/core/modules-and-layers"},{"title":"Dependency injection","sidebarTitle":"Dependency injection","href":"/development/core/dependency-injection"},{"title":"Configuration","sidebarTitle":"Configuration","href":"/development/core/configuration"},{"title":"Command bus","sidebarTitle":"Command bus","href":"/development/core/command-bus"},{"title":"Events","sidebarTitle":"Events","href":"/development/core/events"}]},{"group":"HTTP Layer","pages":[{"title":"Routing and middleware","sidebarTitle":"Routing and middleware","href":"/development/core/routing-and-middleware"},{"title":"Authentication and authorization","sidebarTitle":"Auth","href":"/development/core/authentication-and-authorization"},{"title":"Views and frontend","sidebarTitle":"Views and frontend","description":"How Aikeedo renders Twig templates, what globals every view receives, how the Vite-built frontend is wired, and how to override a core template.","href":"/development/core/views-and-frontend"}]},{"group":"Subsystems","pages":[{"title":"Database and migrations","sidebarTitle":"Database","href":"/development/core/database-and-migrations"},{"title":"AI subsystem","sidebarTitle":"AI subsystem","description":"How Aikeedo resolves AI services for a model, streams generations, runs tools, and turns provider usage into credits.","href":"/development/core/ai-subsystem"},{"title":"Billing subsystem","sidebarTitle":"Billing","href":"/development/core/billing-subsystem"},{"title":"Storage and files","sidebarTitle":"Storage and files","href":"/development/core/storage-and-files"},{"title":"Background jobs and cron","sidebarTitle":"Cron and jobs","href":"/development/core/background-jobs-and-cron"},{"title":"Email","sidebarTitle":"Email","href":"/development/core/email"},{"title":"Localization internals","sidebarTitle":"Localization","href":"/development/core/localization-internals"},{"title":"Caching and logging","sidebarTitle":"Caching and logging","href":"/development/core/caching-and-logging"}]},{"group":"Tooling","pages":[{"title":"Console commands","sidebarTitle":"Console commands","href":"/development/core/console-commands"},{"title":"Testing and code quality","sidebarTitle":"Testing and quality","href":"/development/core/testing-and-quality"}]}]},{"group":"REST API","pages":[{"title":"REST API overview","sidebarTitle":"Overview","href":"/development/api/overview"}]}]}]},"title":"Database and migrations - Aikeedo Docs","buildId":"dpl_2oYXwqkEXirXBPk8wV1FtM5USFWi","deploymentId":"dpl_2oYXwqkEXirXBPk8wV1FtM5USFWi","dynamics":[],"chunks":[]}