From surrealdb-v3-stack
Write correct SurrealQL for SurrealDB v3 — queries, schema design, graph edges, permissions, live queries, transactions, full-text and vector search. Use this skill whenever the user is working in a `.surql` file, asks for any SurrealQL statement (SELECT, CREATE, UPDATE, UPSERT, DELETE, INSERT, RELATE, LIVE SELECT, DEFINE, REMOVE, REBUILD, BEGIN/COMMIT), designs a database schema with DEFINE TABLE/FIELD/INDEX/EVENT/FUNCTION/ACCESS/ANALYZER/SEQUENCE, writes row-level permissions using `$auth`, traverses graph relationships with arrow syntax, or debugs query errors. Trigger even when the user does not explicitly name the skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/surrealdb-v3-stack:surrealqlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
SurrealQL is SurrealDB's query language — SQL-flavored but not SQL. This skill is the server-side language only; for the JavaScript client use the `surrealdb-js` skill.
references/advanced.mdreferences/auth.mdreferences/base-gates.mdreferences/cookbook.mdreferences/dml.mdreferences/eventbus.mdreferences/gotchas.mdreferences/mastra.mdreferences/migrations.mdreferences/modeling.mdreferences/permissions.mdreferences/schema-builder.mdreferences/schema.mdreferences/serialization.mdSurrealQL is SurrealDB's query language — SQL-flavored but not SQL. This skill is the server-side language only; for the JavaScript client use the surrealdb-js skill.
Target: SurrealDB server v3.x. Do not silently assume v1/v2 syntax. If the user's environment is v2 or earlier, flag it before writing (notably: v2 uses DEFINE SCOPE, v3 uses DEFINE ACCESS).
Internalize these before writing anything.
Every record has a strongly-typed ID. Format: table:identifier. The identifier can be a bare word (person:tobie), a number (log:1), a UUID (u:u'018a...'), an array (log:[2024,1,1]), an object (p:{x:1,y:2}), or an arbitrary string (u:⟨[email protected]⟩). IDs are first-class values of type record, not strings with a colon.
= is assignment; == is value equality. WHERE name = "x" works in many contexts but is an assignment expression that evaluates truthy when the assignment holds. Default to == in WHERE to be unambiguous. In SET name = "x" (UPDATE), = is correct — that's assignment.
Relationships are edges, not joins. Use RELATE from -> edge -> to to create a directed edge record, then traverse with ->edge->target. JOINs (via FETCH) are only for record links (fields typed record<t>), not edges.
ONLY unwraps arrays. SELECT * FROM person:tobie returns a one-element array [{...}]. SELECT * FROM ONLY person:tobie returns the object {...}. Same for CREATE/UPDATE/UPSERT/DELETE/RELATE.
Strong typing is opt-in. Tables default to SCHEMALESS. Use SCHEMAFULL + DEFINE FIELD ... TYPE ... for real type checking. Permissions attach to tables and fields and are enforced per row in the query engine.
Access control is first-class. v3's DEFINE ACCESS replaces v1's DEFINE SCOPE. It handles record-based signup/signin, external JWT verification, and bearer tokens, all issuing server-signed JWTs. Don't roll your own auth on top.
Schema migrations require backfills for non-optional fields. DEFAULT <value> only fires on CREATE — it does NOT populate existing rows. Adding a non-optional field without a paired UPDATE <table> SET <field> = <value> WHERE <field> IS NONE makes every read on legacy rows fail with a coerce error (because v3 SCHEMAFULL coerces every field on read, not only the queried ones). This caused two production outages (v64/v65, v70/v73). See references/base-gates.md for the gate that now blocks this class of bug pre-commit.
Do not answer from memory for any substantive task. Read the matching file:
| User's task | Read this |
|---|---|
| SELECT/CREATE/UPDATE/UPSERT/DELETE/INSERT/RELATE queries | references/dml.md |
| DEFINE TABLE/FIELD/INDEX/EVENT/FUNCTION/ANALYZER/SEQUENCE, REMOVE | references/schema.md |
Row-level PERMISSIONS, $auth, $session, role checks | references/permissions.md |
| Graph traversal, LIVE SELECT, transactions, full-text, vector, geo | references/advanced.md |
| DEFINE ACCESS RECORD/JWT/BEARER, signup/signin, JWT verification | references/auth.md |
| Record links vs graph edges vs embedding, when to denormalize | references/modeling.md |
| Adding/removing/renaming fields safely, migration versioning | references/migrations.md |
| Common real-world queries (pagination, search, upsert, graphs) | references/cookbook.md |
| Anything weird, failing, or version-sensitive | references/gotchas.md |
| Schema Builder (3-layer pipeline, CORE_SCHEMA, migrations, gates) | references/schema-builder.md |
| Pre-commit gates (catalog, manifests, schema-backfill, module-purity) | references/base-gates.md |
| jsonify / extractId (Server→Client serialization, RecordId) | references/serialization.md |
| Mastra tables (SCHEMALESS exception, NULL vs NONE) | references/mastra.md |
| EventBus / SurrealLiveAdapter (LIVE queries, channels, worker) | references/eventbus.md |
When writing SurrealQL:
== over = in WHERE clauses.ONLY when the user expects a single record, not an array.SCHEMAFULL for production tables, with DEFINE FIELD ... TYPE ... on every field.BEGIN; ... COMMIT; so they apply atomically.type::thing($table, $id) rather than string concatenation — that's the canonical injection-safe path.RETURN NONE for perf, FETCH paths, WITH INDEX hints, permission expressions).Copy this when the user asks for "a starting schema" and adapt:
BEGIN;
DEFINE TABLE user SCHEMAFULL PERMISSIONS
FOR select, update WHERE id = $auth.id,
FOR create, delete NONE;
DEFINE FIELD email ON user TYPE string ASSERT string::is::email($value);
DEFINE FIELD password ON user TYPE string
VALUE crypto::argon2::generate($value)
PERMISSIONS FOR select NONE;
DEFINE FIELD created_at ON user TYPE datetime
VALUE $before.created_at OR time::now()
READONLY;
DEFINE INDEX idx_user_email ON user FIELDS email UNIQUE;
DEFINE ACCESS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET email = $email, password = $password )
SIGNIN (
SELECT * FROM user
WHERE email = $email
AND crypto::argon2::compare(password, $password)
)
DURATION FOR TOKEN 15m, FOR SESSION 7d;
COMMIT;
Use surql for SurrealQL blocks. If the tooling doesn't recognize it, fall back to sql. Include -- comments on lines that do something the user might not expect.
If the user is writing JavaScript/TypeScript that calls db.query(...), most of their work is still SurrealQL (the query string) but some parts belong in the client code (parameter binding, result destructuring, RecordId construction). Write the SurrealQL here; let the SDK skill handle the surrounding client code. The two skills are designed to work in combination.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
npx claudepluginhub codecubit/surrealdb-v3-stack --plugin surrealdb-v3-stack