Supasheet.

Comments

Comments on any record, from any table, gated by the table's own SELECT access

Overview

Supasheet ships a generic comments system — any record in any table can have a comment thread without you adding new columns or migrations. Comments are stored centrally in supasheet.comments, and access to a table's comment thread is simply whatever SELECT privilege already exists on that table — there's nothing extra to configure per table.

The schema is defined in supabase/migrations/20260514000001_comments.sql.

The supasheet.comments Table

ColumnTypePurpose
iduuidComment id
created_at / updated_attimestamptzAudit timestamps
schema_nametextSchema of the record being commented on
table_nametextTable of the record being commented on
record_idtextPrimary key of the record (stored as text for flexibility)
contenttextThe comment body
created_byuuidAuthor — references supasheet.users(id)

Indexes on (schema_name, table_name, record_id), created_by, and created_at keep listings fast.

Comment Access

There's no separate :comment permission to grant, and no per-table setup step. Comment access on a table is simply that table's ordinary SELECT privilege — if a role can SELECT desk.tasks, it can already read and post comments on desk.tasks rows:

  1. Who can read comments on the table — the table's RLS SELECT policy requires the caller's role to hold SELECT on the underlying table; supasheet.get_comments() enforces the identical rule.
  2. Who can post comments — the table's RLS INSERT policy requires that same SELECT privilege as well as the author matching auth.uid().

See No Separate :audit or :comment Permission for the underlying mechanics.

UPDATE and DELETE are always limited to the original author.

Showing Comments in the UI

There's no metadata to configure — a Comments link appears automatically in a record's actions menu (/$schema/resource/$resource/$resourceId/comment) for any user whose role can SELECT the underlying table.

Anyone who can read desk.tasks can browse its comment thread, post new comments, and edit / delete their own.

Don't confuse this with the detail.tabs metadata field described in Metadata — that field only filters which related-table tabs appear on the detail page (foreign key relationships). Comments and the audit log are separate, access-gated pages, not metadata-configured tabs.

Reading from SQL

Use supasheet.get_comments(schema, table, record_id) to fetch a thread with author details:

SELECT *
FROM supasheet.get_comments('desk', 'tasks', 'a1b2c3…');

The function returns each comment joined to author info (name, email, picture_url). Like all Supasheet meta helpers, it runs with security definer but checks the caller's SELECT privilege on the target table internally — there is no way to read a thread on a table you can't already query.

Direct table queries work too — the base table's RLS enforces the exact same rule, so there's no difference in what you can see, only whether you get author details joined in for free:

-- Every comment on a specific record — every row where you can SELECT
-- the target table, not just the ones you authored
SELECT * FROM supasheet.comments
WHERE schema_name = 'desk'
  AND table_name = 'tasks'
  AND record_id = 'a1b2c3…'
ORDER BY created_at;

The base table's own RLS policy is has_table_privilege(current_user, format('%I.%I', schema_name, table_name), 'select') — the identical rule the get_comments() helper checks internally. Reach for the helper when you want author details (created_by_name/email/picture_url) joined in; query the table directly otherwise. Neither one shows you a narrower or wider set of comments than the other.

Posting Comments

The INSERT policy enforces two checks:

  1. created_by = auth.uid() — you can only post as yourself
  2. The current role has SELECT on the target table (checked via has_table_privilege)
INSERT INTO supasheet.comments
  (schema_name, table_name, record_id, content, created_by)
VALUES
  ('desk', 'tasks', 'a1b2c3…', 'I picked this up — ETA Friday.', auth.uid());

In the UI, this happens automatically when a user submits the comment form on the resource detail page.

Editing and Deleting

Authors can edit and delete their own comments. Admins can be granted the same by extending the default RLS policies with a role-based override (e.g. pg_has_role(current_user, 'x-admin', 'member')) — but typically comments are immutable history, so most teams leave the defaults as-is.

Combining with Notifications

A natural extension is to notify subscribers when someone comments. Add an AFTER INSERT trigger on supasheet.comments that resolves "interested users" (typically the record's assignee / creator) and calls supasheet.create_notification(...):

CREATE OR REPLACE FUNCTION desk.notify_task_comment ()
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
DECLARE
  v_task_owner uuid;
BEGIN
  IF NEW.schema_name = 'desk' AND NEW.table_name = 'tasks' THEN
    SELECT assignee_id INTO v_task_owner
    FROM desk.tasks WHERE id::text = NEW.record_id;

    IF v_task_owner IS NOT NULL AND v_task_owner <> NEW.created_by THEN
      PERFORM supasheet.create_notification(
        p_type    => 'task_comment',
        p_title   => 'New comment on your task',
        p_body    => left(NEW.content, 280),
        p_user_ids => ARRAY[v_task_owner],
        p_link    => format('/desk/resource/tasks/%s/comment', NEW.record_id)
      );
    END IF;
  END IF;
  RETURN NEW;
END $$;

CREATE TRIGGER trg_notify_task_comment
  AFTER INSERT ON supasheet.comments
  FOR EACH ROW EXECUTE FUNCTION desk.notify_task_comment();

See Notifications for the full API.

Access Cheatsheet

GoalRequired grant
See the Comments tab on a recordSELECT on <schema>.<table>
Post a comment on a recordSELECT on <schema>.<table>
Edit / delete your own commentNone beyond above
Edit / delete someone else's commentCustom — extend the default policies

Next Steps

On this page