[{"data":1,"prerenderedAt":1285},["Reactive",2],{"posts-en-nuxt":3},[4],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"locale":11,"draft":7,"date":12,"image":13,"tags":16,"imageLogo":19,"imageLogo2":24,"seoImage":28,"body":30,"_type":1280,"_id":1281,"_source":1282,"_file":1283,"_extension":1284},"/en/blog/pocketbase","blog",false,"","PocketBase – Lessons from LingoDeck","A backend in a single file: a modern interface, simple deployment and flexibility – but also no SQL through the API, a pre-1.0 status and an admin panel that can't be extended","en","2026-09-08T00:00:00.000Z",{"src":14,"alt":15},"/background/background-6.svg","background",[17,18],"tools","nuxt",{"src":20,"alt":21,"width":22,"left":23},"/logo/pocketbase.svg","pocketbase-logo","25%","10%",{"src":25,"alt":26,"width":27,"right":23},"/logo/sqlite.svg","sqlite-logo","18%",{"src":29},"/pocketbase.jpg",{"type":31,"children":32,"toc":1262},"root",[33,62,69,74,145,159,168,174,181,194,247,252,258,263,334,339,388,401,406,411,460,472,478,483,589,617,630,636,641,681,710,715,745,751,756,762,768,787,822,834,840,852,1070,1075,1111,1123,1129,1134,1139,1145,1157,1162,1168,1224,1230,1235,1256],{"type":34,"tag":35,"props":36,"children":37},"element","p",{},[38,41,51,53,60],{"type":39,"value":40},"text","I used ",{"type":34,"tag":42,"props":43,"children":48},"a",{"href":44,":target":45,"rel":46},"https://pocketbase.io/","\\_blank",[47],"nofollow",[49],{"type":39,"value":50},"PocketBase",{"type":39,"value":52}," for ",{"type":34,"tag":42,"props":54,"children":57},{"href":55,":target":45,"rel":56},"https://lingodeck.foxwebcraft.com",[47],[58],{"type":39,"value":59},"LingoDeck",{"type":39,"value":61},", a flashcard app for language learning with spaced repetition, AI-generated cards and text-to-speech. The front end is a Nuxt 4 app, and the back end is PocketBase 0.30 with the JavaScript SDK. Time for an honest review.",{"type":34,"tag":63,"props":64,"children":66},"h2",{"id":65},"what-is-pocketbase",[67],{"type":39,"value":68},"What is PocketBase?",{"type":34,"tag":35,"props":70,"children":71},{},[72],{"type":39,"value":73},"PocketBase is an open-source back end (MIT licence) that consists of a single executable file. It is written in Go and ships with everything important:",{"type":34,"tag":75,"props":76,"children":77},"ul",{},[78,92,103,113,123,135],{"type":34,"tag":79,"props":80,"children":81},"li",{},[82,84,90],{"type":39,"value":83},"a ",{"type":34,"tag":85,"props":86,"children":87},"strong",{},[88],{"type":39,"value":89},"SQLite database",{"type":39,"value":91}," that lives as a file next to the program",{"type":34,"tag":79,"props":93,"children":94},{},[95,96,101],{"type":39,"value":83},{"type":34,"tag":85,"props":97,"children":98},{},[99],{"type":39,"value":100},"REST API",{"type":39,"value":102}," for every collection, plus realtime subscriptions",{"type":34,"tag":79,"props":104,"children":105},{},[106,111],{"type":34,"tag":85,"props":107,"children":108},{},[109],{"type":39,"value":110},"authentication",{"type":39,"value":112}," with email and password, OAuth2 and more",{"type":34,"tag":79,"props":114,"children":115},{},[116,121],{"type":34,"tag":85,"props":117,"children":118},{},[119],{"type":39,"value":120},"file uploads",{"type":39,"value":122},", stored locally or in S3",{"type":34,"tag":79,"props":124,"children":125},{},[126,128,133],{"type":39,"value":127},"an ",{"type":34,"tag":85,"props":129,"children":130},{},[131],{"type":39,"value":132},"admin panel",{"type":39,"value":134}," for managing collections, fields, access rules, logs and settings",{"type":34,"tag":79,"props":136,"children":137},{},[138,143],{"type":34,"tag":85,"props":139,"children":140},{},[141],{"type":39,"value":142},"hooks and migrations",{"type":39,"value":144}," in JavaScript (alternatively, PocketBase can be extended as a Go framework)",{"type":34,"tag":35,"props":146,"children":147},{},[148,150,157],{"type":39,"value":149},"You start the file with ",{"type":34,"tag":151,"props":152,"children":154},"code",{"className":153},[],[155],{"type":39,"value":156},"./pocketbase serve",{"type":39,"value":158}," and have a working back end. There are SDKs for JavaScript and Dart. In the app, the Nuxt pages and the Nuxt server talk to PocketBase through the JS SDK. I haven't needed realtime or OAuth so far.",{"type":34,"tag":35,"props":160,"children":161},{},[162],{"type":34,"tag":163,"props":164,"children":167},"img",{"alt":165,"src":166},"The PocketBase admin panel showing the \"cards\" collection","/images/blog/pocketbase.webp",[],{"type":34,"tag":63,"props":169,"children":171},{"id":170},"advantages",[172],{"type":39,"value":173},"Advantages",{"type":34,"tag":175,"props":176,"children":178},"h3",{"id":177},"modern-interface",[179],{"type":39,"value":180},"Modern interface",{"type":34,"tag":35,"props":182,"children":183},{},[184,186,192],{"type":39,"value":185},"The admin panel is fast, tidy and surprisingly complete for a back end you install in a minute. The screenshot shows the ",{"type":34,"tag":151,"props":187,"children":189},{"className":188},[],[190],{"type":39,"value":191},"cards",{"type":39,"value":193}," collection with search and filter. Everything else is in one place too:",{"type":34,"tag":75,"props":195,"children":196},{},[197,207,217,227,237],{"type":34,"tag":79,"props":198,"children":199},{},[200,205],{"type":34,"tag":85,"props":201,"children":202},{},[203],{"type":39,"value":204},"Collections and fields",{"type":39,"value":206}," with types such as text, number, select, relation, JSON, file and autodate",{"type":34,"tag":79,"props":208,"children":209},{},[210,215],{"type":34,"tag":85,"props":211,"children":212},{},[213],{"type":39,"value":214},"Access rules",{"type":39,"value":216}," as an expression per action (list, view, create, update, delete), with an editor and autocompletion",{"type":34,"tag":79,"props":218,"children":219},{},[220,225],{"type":34,"tag":85,"props":221,"children":222},{},[223],{"type":39,"value":224},"API Preview",{"type":39,"value":226},", which shows ready-made example requests for every collection",{"type":34,"tag":79,"props":228,"children":229},{},[230,235],{"type":34,"tag":85,"props":231,"children":232},{},[233],{"type":39,"value":234},"Logs and statistics",{"type":39,"value":236}," for requests",{"type":34,"tag":79,"props":238,"children":239},{},[240,245],{"type":34,"tag":85,"props":241,"children":242},{},[243],{"type":39,"value":244},"Settings",{"type":39,"value":246}," for mail, file storage and automatic backups",{"type":34,"tag":35,"props":248,"children":249},{},[250],{"type":39,"value":251},"The interface is aimed at developers, though. It is not a replacement for a classic CMS when editors maintain content. More on that below.",{"type":34,"tag":175,"props":253,"children":255},{"id":254},"flexibility",[256],{"type":39,"value":257},"Flexibility",{"type":34,"tag":35,"props":259,"children":260},{},[261],{"type":39,"value":262},"The data model is free, and the access rules are more powerful than they first look. Every collection has five rules (list, view, create, update, delete), and each of them is a short expression. Users may, for example, only see and create their own records:",{"type":34,"tag":264,"props":265,"children":269},"pre",{"className":266,"code":267,"language":268,"meta":8,"style":8},"language-js shiki shiki-themes dracula","notes.listRule   = 'owner = @request.auth.id'\nnotes.createRule = '@request.auth.id != \"\" && owner = @request.auth.id'\n","js",[270],{"type":34,"tag":151,"props":271,"children":272},{"__ignoreMap":8},[273,308],{"type":34,"tag":274,"props":275,"children":278},"span",{"class":276,"line":277},"line",1,[279,285,291,297,303],{"type":34,"tag":274,"props":280,"children":282},{"style":281},"--shiki-default:#F8F8F2",[283],{"type":39,"value":284},"notes.listRule   ",{"type":34,"tag":274,"props":286,"children":288},{"style":287},"--shiki-default:#FF79C6",[289],{"type":39,"value":290},"=",{"type":34,"tag":274,"props":292,"children":294},{"style":293},"--shiki-default:#E9F284",[295],{"type":39,"value":296}," '",{"type":34,"tag":274,"props":298,"children":300},{"style":299},"--shiki-default:#F1FA8C",[301],{"type":39,"value":302},"owner = @request.auth.id",{"type":34,"tag":274,"props":304,"children":305},{"style":293},[306],{"type":39,"value":307},"'\n",{"type":34,"tag":274,"props":309,"children":311},{"class":276,"line":310},2,[312,317,321,325,330],{"type":34,"tag":274,"props":313,"children":314},{"style":281},[315],{"type":39,"value":316},"notes.createRule ",{"type":34,"tag":274,"props":318,"children":319},{"style":287},[320],{"type":39,"value":290},{"type":34,"tag":274,"props":322,"children":323},{"style":293},[324],{"type":39,"value":296},{"type":34,"tag":274,"props":326,"children":327},{"style":299},[328],{"type":39,"value":329},"@request.auth.id != \"\" && owner = @request.auth.id",{"type":34,"tag":274,"props":331,"children":332},{"style":293},[333],{"type":39,"value":307},{"type":34,"tag":35,"props":335,"children":336},{},[337],{"type":39,"value":338},"Or content is publicly readable, but can only be changed by signed-in users who created the record themselves:",{"type":34,"tag":264,"props":340,"children":342},{"className":266,"code":341,"language":268,"meta":8,"style":8},"articles.listRule   = ''\narticles.updateRule = '@request.auth.id != \"\" && author = @request.auth.id'\n",[343],{"type":34,"tag":151,"props":344,"children":345},{"__ignoreMap":8},[346,363],{"type":34,"tag":274,"props":347,"children":348},{"class":276,"line":277},[349,354,358],{"type":34,"tag":274,"props":350,"children":351},{"style":281},[352],{"type":39,"value":353},"articles.listRule   ",{"type":34,"tag":274,"props":355,"children":356},{"style":287},[357],{"type":39,"value":290},{"type":34,"tag":274,"props":359,"children":360},{"style":293},[361],{"type":39,"value":362}," ''\n",{"type":34,"tag":274,"props":364,"children":365},{"class":276,"line":310},[366,371,375,379,384],{"type":34,"tag":274,"props":367,"children":368},{"style":281},[369],{"type":39,"value":370},"articles.updateRule ",{"type":34,"tag":274,"props":372,"children":373},{"style":287},[374],{"type":39,"value":290},{"type":34,"tag":274,"props":376,"children":377},{"style":293},[378],{"type":39,"value":296},{"type":34,"tag":274,"props":380,"children":381},{"style":299},[382],{"type":39,"value":383},"@request.auth.id != \"\" && author = @request.auth.id",{"type":34,"tag":274,"props":385,"children":386},{"style":293},[387],{"type":39,"value":307},{"type":34,"tag":35,"props":389,"children":390},{},[391,393,399],{"type":39,"value":392},"An empty rule means \"everyone\", ",{"type":34,"tag":151,"props":394,"children":396},{"className":395},[],[397],{"type":39,"value":398},"null",{"type":39,"value":400}," means \"superusers only\".",{"type":34,"tag":35,"props":402,"children":403},{},[404],{"type":39,"value":405},"There is no middleware and no endpoint that checks this. PocketBase evaluates the rules on every request, even when someone calls the API directly. That saves you custom permission logic.",{"type":34,"tag":35,"props":407,"children":408},{},[409],{"type":39,"value":410},"When rules aren't enough, there are several options:",{"type":34,"tag":75,"props":412,"children":413},{},[414,432,442],{"type":34,"tag":79,"props":415,"children":416},{},[417,422,424,430],{"type":34,"tag":85,"props":418,"children":419},{},[420],{"type":39,"value":421},"JS hooks and custom routes",{"type":39,"value":423}," (",{"type":34,"tag":151,"props":425,"children":427},{"className":426},[],[428],{"type":39,"value":429},"pb_hooks",{"type":39,"value":431},") directly inside the program",{"type":34,"tag":79,"props":433,"children":434},{},[435,440],{"type":34,"tag":85,"props":436,"children":437},{},[438],{"type":39,"value":439},"Go extension",{"type":39,"value":441},", when you embed PocketBase as a framework in your own Go project",{"type":34,"tag":79,"props":443,"children":444},{},[445,450,452,458],{"type":34,"tag":85,"props":446,"children":447},{},[448],{"type":39,"value":449},"A separate server in front.",{"type":39,"value":451}," A Nuxt or Node server can check the login via ",{"type":34,"tag":151,"props":453,"children":455},{"className":454},[],[456],{"type":39,"value":457},"auth-refresh",{"type":39,"value":459}," and then call external APIs whose keys shouldn't end up in the browser.",{"type":34,"tag":35,"props":461,"children":462},{},[463,465,470],{"type":39,"value":464},"The limit is the admin panel. ",{"type":34,"tag":85,"props":466,"children":467},{},[468],{"type":39,"value":469},"It can't be extended.",{"type":39,"value":471}," There are no custom views, dashboards or fields in the back end. If you need, say, your own user management or a dashboard, you have to build it in the app yourself, and I wouldn't use PocketBase for clients without building a custom interface. With Payload or Statamic, that is easier.",{"type":34,"tag":175,"props":473,"children":475},{"id":474},"ai-compatibility",[476],{"type":39,"value":477},"AI compatibility",{"type":34,"tag":35,"props":479,"children":480},{},[481],{"type":39,"value":482},"This is a mixed bag. On the plus side, almost everything can be described as text:",{"type":34,"tag":75,"props":484,"children":485},{},[486,496,506,576],{"type":34,"tag":79,"props":487,"children":488},{},[489,494],{"type":34,"tag":85,"props":490,"children":491},{},[492],{"type":39,"value":493},"Migrations are JavaScript files in the repository.",{"type":39,"value":495}," Collections, fields, indexes and rules are traceable as a Git diff, and an assistant can write new migrations.",{"type":34,"tag":79,"props":497,"children":498},{},[499,504],{"type":34,"tag":85,"props":500,"children":501},{},[502],{"type":39,"value":503},"Rules are short expressions.",{"type":39,"value":505}," You can read them, review them and change them with a prompt.",{"type":34,"tag":79,"props":507,"children":508},{},[509,514,516,522,524,530,531,537,538,544,546,552,554,560,561,567,568,574],{"type":34,"tag":85,"props":510,"children":511},{},[512],{"type":39,"value":513},"The API is small.",{"type":39,"value":515}," The SDK is essentially ",{"type":34,"tag":151,"props":517,"children":519},{"className":518},[],[520],{"type":39,"value":521},"getFullList",{"type":39,"value":523},", ",{"type":34,"tag":151,"props":525,"children":527},{"className":526},[],[528],{"type":39,"value":529},"getList",{"type":39,"value":523},{"type":34,"tag":151,"props":532,"children":534},{"className":533},[],[535],{"type":39,"value":536},"create",{"type":39,"value":523},{"type":34,"tag":151,"props":539,"children":541},{"className":540},[],[542],{"type":39,"value":543},"update",{"type":39,"value":545}," and ",{"type":34,"tag":151,"props":547,"children":549},{"className":548},[],[550],{"type":39,"value":551},"delete",{"type":39,"value":553},", plus ",{"type":34,"tag":151,"props":555,"children":557},{"className":556},[],[558],{"type":39,"value":559},"filter",{"type":39,"value":523},{"type":34,"tag":151,"props":562,"children":564},{"className":563},[],[565],{"type":39,"value":566},"sort",{"type":39,"value":545},{"type":34,"tag":151,"props":569,"children":571},{"className":570},[],[572],{"type":39,"value":573},"expand",{"type":39,"value":575},".",{"type":34,"tag":79,"props":577,"children":578},{},[579,581,587],{"type":39,"value":580},"For hooks, PocketBase ships a type file (",{"type":34,"tag":151,"props":582,"children":584},{"className":583},[],[585],{"type":39,"value":586},"types.d.ts",{"type":39,"value":588},") that the editor and the assistant can use.",{"type":34,"tag":35,"props":590,"children":591},{},[592,594,600,601,607,609,615],{"type":39,"value":593},"Less good: the truth about the schema lives in the database, not in the code. Unlike a code-first system such as Payload, an assistant only sees the schema if the migrations are complete. And the risk of outdated code is real. PocketBase changed a lot with version 0.23, and many examples online still belong to the old API. In my case, the first migrations were written against an older version. As a result, ",{"type":34,"tag":151,"props":595,"children":597},{"className":596},[],[598],{"type":39,"value":599},"created",{"type":39,"value":545},{"type":34,"tag":151,"props":602,"children":604},{"className":603},[],[605],{"type":39,"value":606},"updated",{"type":39,"value":608}," were missing, and sorting by ",{"type":34,"tag":151,"props":610,"children":612},{"className":611},[],[613],{"type":39,"value":614},"-updated",{"type":39,"value":616}," failed with a 400 error. An additional migration fixed it.",{"type":34,"tag":35,"props":618,"children":619},{},[620,622,628],{"type":39,"value":621},"My conclusion: well suited as long as you keep the migrations in the repository and tell the assistant which version you use. In my project, that is written down in ",{"type":34,"tag":151,"props":623,"children":625},{"className":624},[],[626],{"type":39,"value":627},"AGENTS.md",{"type":39,"value":629},", together with the rules and quirks.",{"type":34,"tag":175,"props":631,"children":633},{"id":632},"deployment",[634],{"type":39,"value":635},"Deployment",{"type":34,"tag":35,"props":637,"children":638},{},[639],{"type":39,"value":640},"This is the biggest advantage for me. A deployment is a single file, and in my setup it works like this:",{"type":34,"tag":642,"props":643,"children":644},"ol",{},[645,650,663,668],{"type":34,"tag":79,"props":646,"children":647},{},[648],{"type":39,"value":649},"GitHub Actions builds the Nuxt app and uploads it to the server.",{"type":34,"tag":79,"props":651,"children":652},{},[653,655,661],{"type":39,"value":654},"The files from ",{"type":34,"tag":151,"props":656,"children":658},{"className":657},[],[659],{"type":39,"value":660},"pb_migrations/",{"type":39,"value":662}," are uploaded.",{"type":34,"tag":79,"props":664,"children":665},{},[666],{"type":39,"value":667},"PocketBase is restarted with pm2 and applies all new migrations on startup.",{"type":34,"tag":79,"props":669,"children":670},{},[671,673,679],{"type":39,"value":672},"A health check (",{"type":34,"tag":151,"props":674,"children":676},{"className":675},[],[677],{"type":39,"value":678},"/api/health",{"type":39,"value":680},") confirms that it is running again.",{"type":34,"tag":35,"props":682,"children":683},{},[684,686,692,694,700,702,708],{"type":39,"value":685},"There is no database to install and maintain separately, and no dependencies to build. Updating PocketBase itself is a new file as well (",{"type":34,"tag":151,"props":687,"children":689},{"className":688},[],[690],{"type":39,"value":691},"./pocketbase update",{"type":39,"value":693},"). Operation is simple too: PocketBase listens on ",{"type":34,"tag":151,"props":695,"children":697},{"className":696},[],[698],{"type":39,"value":699},"127.0.0.1:8090",{"type":39,"value":701},", and nginx forwards ",{"type":34,"tag":151,"props":703,"children":705},{"className":704},[],[706],{"type":39,"value":707},"/pb",{"type":39,"value":709}," there and handles SSL.",{"type":34,"tag":35,"props":711,"children":712},{},[713],{"type":39,"value":714},"Two things to keep in mind:",{"type":34,"tag":75,"props":716,"children":717},{},[718,728],{"type":34,"tag":79,"props":719,"children":720},{},[721,726],{"type":34,"tag":85,"props":722,"children":723},{},[724],{"type":39,"value":725},"PocketBase is briefly unavailable during a restart.",{"type":39,"value":727}," For an app of this size, that doesn't matter.",{"type":34,"tag":79,"props":729,"children":730},{},[731,736,738,744],{"type":34,"tag":85,"props":732,"children":733},{},[734],{"type":39,"value":735},"Changes made in the admin panel on the server",{"type":39,"value":737}," automatically create migration files, but there and not in the repository. If you prefer clicking collections together in the panel, you have to download the generated files from the server and add them to the project afterwards. I therefore prefer to write migrations by hand. Even settings like the login token lifetime (30 instead of 7 days) live in a migration. A change made only in the panel would otherwise be lost on a fresh ",{"type":34,"tag":151,"props":739,"children":741},{"className":740},[],[742],{"type":39,"value":743},"pb_data",{"type":39,"value":575},{"type":34,"tag":175,"props":746,"children":748},{"id":747},"few-resources-and-free",[749],{"type":39,"value":750},"Few resources and free",{"type":34,"tag":35,"props":752,"children":753},{},[754],{"type":39,"value":755},"The file is about 32 MB. On my machine, the process used about 60 MB of memory with roughly 500 records. There is no licence, no per-user pricing and no limit to one project. In return, you take care of the server and backups yourself.",{"type":34,"tag":63,"props":757,"children":759},{"id":758},"disadvantages",[760],{"type":39,"value":761},"Disadvantages",{"type":34,"tag":175,"props":763,"children":765},{"id":764},"server-requirements",[766],{"type":39,"value":767},"Server requirements",{"type":34,"tag":35,"props":769,"children":770},{},[771,773,778,780,785],{"type":39,"value":772},"Here you have to distinguish. The requirements are ",{"type":34,"tag":85,"props":774,"children":775},{},[776],{"type":39,"value":777},"low",{"type":39,"value":779},", but PocketBase is ",{"type":34,"tag":85,"props":781,"children":782},{},[783],{"type":39,"value":784},"not static hosting",{"type":39,"value":786},". It needs:",{"type":34,"tag":75,"props":788,"children":789},{},[790,795,812,817],{"type":34,"tag":79,"props":791,"children":792},{},[793],{"type":39,"value":794},"a permanently running process, pm2 in my case",{"type":34,"tag":79,"props":796,"children":797},{},[798,803,805,810],{"type":34,"tag":85,"props":799,"children":800},{},[801],{"type":39,"value":802},"persistent storage",{"type":39,"value":804}," for the ",{"type":34,"tag":151,"props":806,"children":808},{"className":807},[],[809],{"type":39,"value":743},{"type":39,"value":811}," folder with the database and uploads",{"type":34,"tag":79,"props":813,"children":814},{},[815],{"type":39,"value":816},"a reverse proxy for HTTPS",{"type":34,"tag":79,"props":818,"children":819},{},[820],{"type":39,"value":821},"Shared hosting is out. A small VPS is enough, though, and in my setup PocketBase shares the server with the Nuxt app.",{"type":34,"tag":35,"props":823,"children":824},{},[825,827,832],{"type":39,"value":826},"Compared with a Node-based CMS that is an advantage, compared with a site builder or static pages it is a disadvantage. On top of that, ",{"type":34,"tag":85,"props":828,"children":829},{},[830],{"type":39,"value":831},"SQLite allows exactly one write at a time",{"type":39,"value":833}," and can't be spread across several servers. For an app on one server that's no problem. If you want to scale horizontally, PocketBase is a problem.",{"type":34,"tag":175,"props":835,"children":837},{"id":836},"no-real-sql",[838],{"type":39,"value":839},"No real SQL",{"type":34,"tag":35,"props":841,"children":842},{},[843,845,850],{"type":39,"value":844},"This is only partly true, so here is a closer look. Under the hood PocketBase ",{"type":34,"tag":85,"props":846,"children":847},{},[848],{"type":39,"value":849},"is real SQL",{"type":39,"value":851},", because the data lives in a SQLite file. From the outside, though, access doesn't go through SQL but through its own API with its own filter syntax:",{"type":34,"tag":264,"props":853,"children":855},{"className":266,"code":854,"language":268,"meta":8,"style":8},"const defaults = await pb.collection('cards').getFullList({\n  filter: 'owner = \"\"',\n})\n\nconst progress = await pb.collection('card_progress').getFullList({\n  expand: 'card',\n})\n",[856],{"type":34,"tag":151,"props":857,"children":858},{"__ignoreMap":8},[859,924,955,964,974,1032,1062],{"type":34,"tag":274,"props":860,"children":861},{"class":276,"line":277},[862,867,872,876,881,886,892,897,902,906,910,915,919],{"type":34,"tag":274,"props":863,"children":864},{"style":287},[865],{"type":39,"value":866},"const",{"type":34,"tag":274,"props":868,"children":869},{"style":281},[870],{"type":39,"value":871}," defaults ",{"type":34,"tag":274,"props":873,"children":874},{"style":287},[875],{"type":39,"value":290},{"type":34,"tag":274,"props":877,"children":878},{"style":287},[879],{"type":39,"value":880}," await",{"type":34,"tag":274,"props":882,"children":883},{"style":281},[884],{"type":39,"value":885}," pb.",{"type":34,"tag":274,"props":887,"children":889},{"style":888},"--shiki-default:#50FA7B",[890],{"type":39,"value":891},"collection",{"type":34,"tag":274,"props":893,"children":894},{"style":281},[895],{"type":39,"value":896},"(",{"type":34,"tag":274,"props":898,"children":899},{"style":293},[900],{"type":39,"value":901},"'",{"type":34,"tag":274,"props":903,"children":904},{"style":299},[905],{"type":39,"value":191},{"type":34,"tag":274,"props":907,"children":908},{"style":293},[909],{"type":39,"value":901},{"type":34,"tag":274,"props":911,"children":912},{"style":281},[913],{"type":39,"value":914},").",{"type":34,"tag":274,"props":916,"children":917},{"style":888},[918],{"type":39,"value":521},{"type":34,"tag":274,"props":920,"children":921},{"style":281},[922],{"type":39,"value":923},"({\n",{"type":34,"tag":274,"props":925,"children":926},{"class":276,"line":310},[927,932,937,941,946,950],{"type":34,"tag":274,"props":928,"children":929},{"style":281},[930],{"type":39,"value":931},"  filter",{"type":34,"tag":274,"props":933,"children":934},{"style":287},[935],{"type":39,"value":936},":",{"type":34,"tag":274,"props":938,"children":939},{"style":293},[940],{"type":39,"value":296},{"type":34,"tag":274,"props":942,"children":943},{"style":299},[944],{"type":39,"value":945},"owner = \"\"",{"type":34,"tag":274,"props":947,"children":948},{"style":293},[949],{"type":39,"value":901},{"type":34,"tag":274,"props":951,"children":952},{"style":281},[953],{"type":39,"value":954},",\n",{"type":34,"tag":274,"props":956,"children":958},{"class":276,"line":957},3,[959],{"type":34,"tag":274,"props":960,"children":961},{"style":281},[962],{"type":39,"value":963},"})\n",{"type":34,"tag":274,"props":965,"children":967},{"class":276,"line":966},4,[968],{"type":34,"tag":274,"props":969,"children":971},{"emptyLinePlaceholder":970},true,[972],{"type":39,"value":973},"\n",{"type":34,"tag":274,"props":975,"children":977},{"class":276,"line":976},5,[978,982,987,991,995,999,1003,1007,1011,1016,1020,1024,1028],{"type":34,"tag":274,"props":979,"children":980},{"style":287},[981],{"type":39,"value":866},{"type":34,"tag":274,"props":983,"children":984},{"style":281},[985],{"type":39,"value":986}," progress ",{"type":34,"tag":274,"props":988,"children":989},{"style":287},[990],{"type":39,"value":290},{"type":34,"tag":274,"props":992,"children":993},{"style":287},[994],{"type":39,"value":880},{"type":34,"tag":274,"props":996,"children":997},{"style":281},[998],{"type":39,"value":885},{"type":34,"tag":274,"props":1000,"children":1001},{"style":888},[1002],{"type":39,"value":891},{"type":34,"tag":274,"props":1004,"children":1005},{"style":281},[1006],{"type":39,"value":896},{"type":34,"tag":274,"props":1008,"children":1009},{"style":293},[1010],{"type":39,"value":901},{"type":34,"tag":274,"props":1012,"children":1013},{"style":299},[1014],{"type":39,"value":1015},"card_progress",{"type":34,"tag":274,"props":1017,"children":1018},{"style":293},[1019],{"type":39,"value":901},{"type":34,"tag":274,"props":1021,"children":1022},{"style":281},[1023],{"type":39,"value":914},{"type":34,"tag":274,"props":1025,"children":1026},{"style":888},[1027],{"type":39,"value":521},{"type":34,"tag":274,"props":1029,"children":1030},{"style":281},[1031],{"type":39,"value":923},{"type":34,"tag":274,"props":1033,"children":1035},{"class":276,"line":1034},6,[1036,1041,1045,1049,1054,1058],{"type":34,"tag":274,"props":1037,"children":1038},{"style":281},[1039],{"type":39,"value":1040},"  expand",{"type":34,"tag":274,"props":1042,"children":1043},{"style":287},[1044],{"type":39,"value":936},{"type":34,"tag":274,"props":1046,"children":1047},{"style":293},[1048],{"type":39,"value":296},{"type":34,"tag":274,"props":1050,"children":1051},{"style":299},[1052],{"type":39,"value":1053},"card",{"type":34,"tag":274,"props":1055,"children":1056},{"style":293},[1057],{"type":39,"value":901},{"type":34,"tag":274,"props":1059,"children":1060},{"style":281},[1061],{"type":39,"value":954},{"type":34,"tag":274,"props":1063,"children":1065},{"class":276,"line":1064},7,[1066],{"type":34,"tag":274,"props":1067,"children":1068},{"style":281},[1069],{"type":39,"value":963},{"type":34,"tag":35,"props":1071,"children":1072},{},[1073],{"type":39,"value":1074},"That is pleasant for most queries, but it is no more than what the API offers:",{"type":34,"tag":75,"props":1076,"children":1077},{},[1078,1094,1106],{"type":34,"tag":79,"props":1079,"children":1080},{},[1081,1086,1088,1093],{"type":34,"tag":85,"props":1082,"children":1083},{},[1084],{"type":39,"value":1085},"Joins",{"type":39,"value":1087}," only exist through relations and ",{"type":34,"tag":151,"props":1089,"children":1091},{"className":1090},[],[1092],{"type":39,"value":573},{"type":39,"value":575},{"type":34,"tag":79,"props":1095,"children":1096},{},[1097,1099,1104],{"type":39,"value":1098},"Reports with ",{"type":34,"tag":85,"props":1100,"children":1101},{},[1102],{"type":39,"value":1103},"GROUP BY, sums or counts",{"type":39,"value":1105}," are not possible through the API.",{"type":34,"tag":79,"props":1107,"children":1108},{},[1109],{"type":39,"value":1110},"If you want to connect another tool, you can't simply connect to a database. The file belongs to PocketBase, and a second access from outside is not a good idea.",{"type":34,"tag":35,"props":1112,"children":1113},{},[1114,1116,1121],{"type":39,"value":1115},"There are ways out. ",{"type":34,"tag":85,"props":1117,"children":1118},{},[1119],{"type":39,"value":1120},"View collections",{"type":39,"value":1122}," are defined with your own SQL SELECT; they are read-only but can be queried like any other collection. In hooks you can also run raw SQL. If you expect joins, reports and complex queries from the start, choose a classic database such as Postgres instead. PocketBase doesn't support any database other than SQLite.",{"type":34,"tag":175,"props":1124,"children":1126},{"id":1125},"the-pre-10-status",[1127],{"type":39,"value":1128},"The pre-1.0 status",{"type":34,"tag":35,"props":1130,"children":1131},{},[1132],{"type":39,"value":1133},"PocketBase is still version 0.x. The project itself points out that backward compatibility isn't guaranteed before 1.0. The move to 0.23 showed that: collections and migrations were reworked, and old examples no longer work. With your own migrations and pinned versions it is manageable, but you should plan an update deliberately and not take it along with the next deploy.",{"type":34,"tag":35,"props":1135,"children":1136},{},[1137],{"type":39,"value":1138},"On top of that, PocketBase is essentially maintained by a single person. The project is very active and stable, but for a long-lived product you have to accept that risk. Because the data sits in a SQLite file and the API rules are readable, a later move would still be possible.",{"type":34,"tag":175,"props":1140,"children":1142},{"id":1141},"data-and-backups-arent-in-the-repository",[1143],{"type":39,"value":1144},"Data and backups aren't in the repository",{"type":34,"tag":35,"props":1146,"children":1147},{},[1148,1150,1155],{"type":39,"value":1149},"As with any back end that has a database, the content isn't part of Git. The migrations only describe the structure. If PocketBase starts without the ",{"type":34,"tag":151,"props":1151,"children":1153},{"className":1152},[],[1154],{"type":39,"value":743},{"type":39,"value":1156}," folder that holds the database (on a new machine or a rebuilt server), it begins with an empty database. The migrations then create the structure but no content, and the app starts with empty collections.",{"type":34,"tag":35,"props":1158,"children":1159},{},[1160],{"type":39,"value":1161},"PocketBase can create backups automatically, including to S3, but you have to set that up and test it now and then. A backup is only a backup once you've restored it successfully.",{"type":34,"tag":175,"props":1163,"children":1165},{"id":1164},"small-stumbling-blocks",[1166],{"type":39,"value":1167},"Small stumbling blocks",{"type":34,"tag":75,"props":1169,"children":1170},{},[1171,1197,1214],{"type":34,"tag":79,"props":1172,"children":1173},{},[1174,1179,1181,1187,1189,1195],{"type":34,"tag":85,"props":1175,"children":1176},{},[1177],{"type":39,"value":1178},"Rules compare text exactly.",{"type":39,"value":1180}," A comparison with ",{"type":34,"tag":151,"props":1182,"children":1184},{"className":1183},[],[1185],{"type":39,"value":1186},"ana@x.com",{"type":39,"value":1188}," doesn't match ",{"type":34,"tag":151,"props":1190,"children":1192},{"className":1191},[],[1193],{"type":39,"value":1194},"Ana@X.com",{"type":39,"value":1196},". You should therefore lowercase email addresses in the form already.",{"type":34,"tag":79,"props":1198,"children":1199},{},[1200,1205,1207,1213],{"type":34,"tag":85,"props":1201,"children":1202},{},[1203],{"type":39,"value":1204},"Email addresses are hidden by default.",{"type":39,"value":1206}," PocketBase only returns them to the owner of the record. For the user list in the admin area I had to set ",{"type":34,"tag":151,"props":1208,"children":1210},{"className":1209},[],[1211],{"type":39,"value":1212},"emailVisibility",{"type":39,"value":575},{"type":34,"tag":79,"props":1215,"children":1216},{},[1217,1222],{"type":34,"tag":85,"props":1218,"children":1219},{},[1220],{"type":39,"value":1221},"No SMTP, no mails.",{"type":39,"value":1223}," As long as no mail server is configured, PocketBase can't send verification mails or password resets.",{"type":34,"tag":63,"props":1225,"children":1227},{"id":1226},"conclusion",[1228],{"type":39,"value":1229},"Conclusion",{"type":34,"tag":35,"props":1231,"children":1232},{},[1233],{"type":39,"value":1234},"PocketBase is a good fit for small to medium apps with login, user data and clear access rules, especially when you run the back end yourself and want as little infrastructure as possible. The admin panel is modern, the rules save a lot of back-end code, and deployment is as simple as it can be for a back end with a database. With migrations in the repository, AI assistants work well with it too.",{"type":34,"tag":35,"props":1236,"children":1237},{},[1238,1240,1246,1248,1254],{"type":39,"value":1239},"The disadvantages are real: there is no SQL through the API, the admin panel can't be extended, scaling is limited to one server, and with version 0.x you have to plan updates carefully. For projects where editors maintain content, I would choose a CMS such as ",{"type":34,"tag":42,"props":1241,"children":1243},{"href":1242},"/en/blog/payload",[1244],{"type":39,"value":1245},"Payload",{"type":39,"value":1247}," or ",{"type":34,"tag":42,"props":1249,"children":1251},{"href":1250},"/en/blog/statamic",[1252],{"type":39,"value":1253},"Statamic",{"type":39,"value":1255},". PocketBase is an interesting product, but for a new project I would choose a different system such as Payload or Appwrite.",{"type":34,"tag":1257,"props":1258,"children":1259},"style",{},[1260],{"type":39,"value":1261},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":8,"searchDepth":310,"depth":310,"links":1263},[1264,1265,1272,1279],{"id":65,"depth":310,"text":68},{"id":170,"depth":310,"text":173,"children":1266},[1267,1268,1269,1270,1271],{"id":177,"depth":957,"text":180},{"id":254,"depth":957,"text":257},{"id":474,"depth":957,"text":477},{"id":632,"depth":957,"text":635},{"id":747,"depth":957,"text":750},{"id":758,"depth":310,"text":761,"children":1273},[1274,1275,1276,1277,1278],{"id":764,"depth":957,"text":767},{"id":836,"depth":957,"text":839},{"id":1125,"depth":957,"text":1128},{"id":1141,"depth":957,"text":1144},{"id":1164,"depth":957,"text":1167},{"id":1226,"depth":310,"text":1229},"markdown","content:en:blog:pocketbase.md","content","en/blog/pocketbase.md","md",1791545016288]