diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md
new file mode 100644
index 0000000..ab66565
--- /dev/null
+++ b/docs/data-sources/group.md
@@ -0,0 +1,44 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "coderd_group Data Source - coderd"
+subcategory: ""
+description: |-
+  An existing group on the coder deployment.
+---
+
+# coderd_group (Data Source)
+
+An existing group on the coder deployment.
+
+
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Optional
+
+- `id` (String) The ID of the group to retrieve. This field will be populated if a name and organization ID is supplied.
+- `name` (String) The name of the group to retrieve. This field will be populated if an ID is supplied.
+- `organization_id` (String) The organization ID that the group belongs to. This field will be populated if an ID is supplied.
+
+### Read-Only
+
+- `avatar_url` (String)
+- `display_name` (String)
+- `members` (Attributes Set) Members of the group. (see [below for nested schema](#nestedatt--members))
+- `quota_allowance` (Number) The number of quota credits to allocate to each user in the group.
+- `source` (String) The source of the group. Either 'oidc' or 'user'.
+
+<a id="nestedatt--members"></a>
+### Nested Schema for `members`
+
+Read-Only:
+
+- `created_at` (Number) Unix timestamp of when the member was created.
+- `email` (String)
+- `id` (String)
+- `last_seen_at` (Number) Unix timestamp of when the member was last seen.
+- `login_type` (String) The login type of the member. Can be 'oidc', 'token', 'password', 'github' or 'none'.
+- `status` (String) The status of the member. Can be 'active', 'dormant' or 'suspended'.
+- `theme_preference` (String)
+- `username` (String)
diff --git a/internal/provider/group_data_source.go b/internal/provider/group_data_source.go
new file mode 100644
index 0000000..62e1cbf
--- /dev/null
+++ b/internal/provider/group_data_source.go
@@ -0,0 +1,225 @@
+package provider
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/coder/coder/v2/codersdk"
+	"github.com/google/uuid"
+	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ datasource.DataSource = &GroupDataSource{}
+
+func NewGroupDataSource() datasource.DataSource {
+	return &GroupDataSource{}
+}
+
+// GroupDataSource defines the data source implementation.
+type GroupDataSource struct {
+	data *CoderdProviderData
+}
+
+// GroupDataSourceModel describes the data source data model.
+type GroupDataSourceModel struct {
+	// ID or name and organization ID must be set
+	ID             types.String `tfsdk:"id"`
+	Name           types.String `tfsdk:"name"`
+	OrganizationID types.String `tfsdk:"organization_id"`
+
+	DisplayName    types.String `tfsdk:"display_name"`
+	AvatarURL      types.String `tfsdk:"avatar_url"`
+	QuotaAllowance types.Int32  `tfsdk:"quota_allowance"`
+	Source         types.String `tfsdk:"source"`
+	Members        []Member     `tfsdk:"members"`
+}
+
+type Member struct {
+	ID              types.String `tfsdk:"id"`
+	Username        types.String `tfsdk:"username"`
+	Email           types.String `tfsdk:"email"`
+	CreatedAt       types.Int64  `tfsdk:"created_at"`
+	LastSeenAt      types.Int64  `tfsdk:"last_seen_at"`
+	Status          types.String `tfsdk:"status"`
+	LoginType       types.String `tfsdk:"login_type"`
+	ThemePreference types.String `tfsdk:"theme_preference"`
+}
+
+func (d *GroupDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_group"
+}
+
+func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	resp.Schema = schema.Schema{
+		MarkdownDescription: "An existing group on the coder deployment.",
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: "The ID of the group to retrieve. This field will be populated if a name and organization ID is supplied.",
+				Optional:            true,
+				Computed:            true,
+				Validators: []validator.String{
+					stringvalidator.AtLeastOneOf(path.Expressions{
+						path.MatchRoot("name"),
+					}...),
+				},
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: "The name of the group to retrieve. This field will be populated if an ID is supplied.",
+				Optional:            true,
+				Computed:            true,
+				Validators:          []validator.String{},
+			},
+			"organization_id": schema.StringAttribute{
+				MarkdownDescription: "The organization ID that the group belongs to. This field will be populated if an ID is supplied. Defaults to the provider default organization ID.",
+				Optional:            true,
+				Computed:            true,
+			},
+			"display_name": schema.StringAttribute{
+				Computed: true,
+			},
+			"avatar_url": schema.StringAttribute{
+				Computed: true,
+			},
+			"quota_allowance": schema.Int32Attribute{
+				MarkdownDescription: "The number of quota credits to allocate to each user in the group.",
+				Computed:            true,
+			},
+			"source": schema.StringAttribute{
+				MarkdownDescription: "The source of the group. Either 'oidc' or 'user'.",
+				Computed:            true,
+			},
+			"members": schema.SetNestedAttribute{
+				MarkdownDescription: "Members of the group.",
+				Computed:            true,
+				NestedObject: schema.NestedAttributeObject{
+					Attributes: map[string]schema.Attribute{
+						"id": schema.StringAttribute{
+							Computed: true,
+						},
+						"username": schema.StringAttribute{
+							Computed: true,
+						},
+						"email": schema.StringAttribute{
+							Computed: true,
+						},
+						"created_at": schema.Int64Attribute{
+							MarkdownDescription: "Unix timestamp of when the member was created.",
+							Computed:            true,
+						},
+						"last_seen_at": schema.Int64Attribute{
+							MarkdownDescription: "Unix timestamp of when the member was last seen.",
+							Computed:            true,
+						},
+						"status": schema.StringAttribute{
+							MarkdownDescription: "The status of the member. Can be 'active', 'dormant' or 'suspended'.",
+							Computed:            true,
+						},
+						"login_type": schema.StringAttribute{
+							MarkdownDescription: "The login type of the member. Can be 'oidc', 'token', 'password', 'github' or 'none'.",
+							Computed:            true,
+						},
+						"theme_preference": schema.StringAttribute{
+							Computed: true,
+						},
+						// TODO: Upgrade requested user type if required
+					},
+				},
+			},
+		},
+	}
+}
+
+func (d *GroupDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+	// Prevent panic if the provider has not been configured.
+	if req.ProviderData == nil {
+		return
+	}
+
+	data, ok := req.ProviderData.(*CoderdProviderData)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Unexpected Data Source Configure Type",
+			fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+
+		return
+	}
+
+	d.data = data
+}
+
+func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var data GroupDataSourceModel
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	client := d.data.Client
+
+	if data.OrganizationID.IsNull() {
+		data.OrganizationID = types.StringValue(d.data.DefaultOrganizationID)
+	}
+
+	var group codersdk.Group
+	if !data.ID.IsNull() {
+		groupID, err := uuid.Parse(data.ID.ValueString())
+		if err != nil {
+			resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied group ID as UUID, got error: %s", err))
+			return
+		}
+
+		group, err = client.Group(ctx, groupID)
+		if err != nil {
+			resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group by ID, got error: %s", err))
+			return
+		}
+		data.Name = types.StringValue(group.Name)
+		data.OrganizationID = types.StringValue(group.OrganizationID.String())
+	} else {
+		orgID, err := uuid.Parse(data.OrganizationID.ValueString())
+		if err != nil {
+			resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied organization ID as UUID, got error: %s", err))
+			return
+		}
+		group, err = client.GroupByOrgAndName(ctx, orgID, data.Name.ValueString())
+		if err != nil {
+			resp.Diagnostics.AddError("Failed to get group by name and org ID", err.Error())
+			return
+		}
+		data.ID = types.StringValue(group.ID.String())
+	}
+
+	data.DisplayName = types.StringValue(group.DisplayName)
+	data.AvatarURL = types.StringValue(group.AvatarURL)
+	data.QuotaAllowance = types.Int32Value(int32(group.QuotaAllowance))
+	members := make([]Member, 0, len(group.Members))
+	for _, member := range group.Members {
+		members = append(members, Member{
+			ID:              types.StringValue(member.ID.String()),
+			Username:        types.StringValue(member.Username),
+			Email:           types.StringValue(member.Email),
+			CreatedAt:       types.Int64Value(member.CreatedAt.Unix()),
+			LastSeenAt:      types.Int64Value(member.LastSeenAt.Unix()),
+			Status:          types.StringValue(string(member.Status)),
+			LoginType:       types.StringValue(string(member.LoginType)),
+			ThemePreference: types.StringValue(member.ThemePreference),
+		})
+	}
+	data.Members = members
+	data.Source = types.StringValue(string(group.Source))
+
+	// Save data into Terraform state
+	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/group_data_source_test.go b/internal/provider/group_data_source_test.go
new file mode 100644
index 0000000..349e855
--- /dev/null
+++ b/internal/provider/group_data_source_test.go
@@ -0,0 +1,200 @@
+package provider
+
+import (
+	"context"
+	"os"
+	"regexp"
+	"strings"
+	"testing"
+	"text/template"
+
+	"github.com/coder/coder/v2/codersdk"
+	"github.com/coder/terraform-provider-coderd/integration"
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAccGroupDataSource(t *testing.T) {
+	if os.Getenv("TF_ACC") == "" {
+		t.Skip("Acceptance tests are disabled.")
+	}
+	ctx := context.Background()
+	client := integration.StartCoder(ctx, t, "group_data_acc", true)
+	firstUser, err := client.User(ctx, codersdk.Me)
+	require.NoError(t, err)
+
+	user1, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
+		Email:          "example@coder.com",
+		Username:       "example",
+		Password:       "SomeSecurePassword!",
+		UserLoginType:  "password",
+		OrganizationID: firstUser.OrganizationIDs[0],
+	})
+	require.NoError(t, err)
+
+	user2, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
+		Email:          "example2@coder.com",
+		Username:       "example2",
+		Password:       "SomeSecurePassword!",
+		UserLoginType:  "password",
+		OrganizationID: firstUser.OrganizationIDs[0],
+	})
+	require.NoError(t, err)
+
+	group, err := client.CreateGroup(ctx, firstUser.OrganizationIDs[0], codersdk.CreateGroupRequest{
+		Name:           "example-group",
+		DisplayName:    "Example Group",
+		AvatarURL:      "https://google.com",
+		QuotaAllowance: 10,
+	})
+	require.NoError(t, err)
+	group, err = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
+		AddUsers: []string{user1.ID.String(), user2.ID.String()},
+	})
+	require.NoError(t, err)
+
+	checkFn := resource.ComposeAggregateTestCheckFunc(
+		resource.TestCheckResourceAttr("data.coderd_group.test", "id", group.ID.String()),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "name", "example-group"),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "display_name", "Example Group"),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "avatar_url", "https://google.com"),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "quota_allowance", "10"),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "members.#", "2"),
+		resource.TestCheckTypeSetElemNestedAttrs("data.coderd_group.test", "members.*", map[string]string{
+			"id": user1.ID.String(),
+		}),
+		resource.TestCheckTypeSetElemNestedAttrs("data.coderd_group.test", "members.*", map[string]string{
+			"id": user2.ID.String(),
+		}),
+		resource.TestCheckResourceAttr("data.coderd_group.test", "source", "user"),
+	)
+
+	t.Run("GroupByIDOk", func(t *testing.T) {
+		cfg := testAccGroupDataSourceConfig{
+			URL:   client.URL.String(),
+			Token: client.SessionToken(),
+			ID:    PtrTo(group.ID.String()),
+		}
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 func() { testAccPreCheck(t) },
+			ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+			Steps: []resource.TestStep{
+				{
+					Config: cfg.String(t),
+					Check:  checkFn,
+				},
+			},
+		})
+	})
+
+	t.Run("GroupByNameAndOrganizationIDOk", func(t *testing.T) {
+		cfg := testAccGroupDataSourceConfig{
+			URL:            client.URL.String(),
+			Token:          client.SessionToken(),
+			OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()),
+			Name:           PtrTo("example-group"),
+		}
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 func() { testAccPreCheck(t) },
+			ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+			Steps: []resource.TestStep{
+				{
+					Config: cfg.String(t),
+					Check:  checkFn,
+				},
+			},
+		})
+	})
+
+	t.Run("UseDefaultOrganizationIDOk", func(t *testing.T) {
+		cfg := testAccGroupDataSourceConfig{
+			URL:   client.URL.String(),
+			Token: client.SessionToken(),
+			Name:  PtrTo("example-group"),
+		}
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 func() { testAccPreCheck(t) },
+			ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+			Steps: []resource.TestStep{
+				{
+					Config: cfg.String(t),
+					Check:  checkFn,
+				},
+			},
+		})
+	})
+
+	t.Run("OrgIDOnlyError", func(t *testing.T) {
+		cfg := testAccGroupDataSourceConfig{
+			URL:            client.URL.String(),
+			Token:          client.SessionToken(),
+			OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()),
+		}
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 func() { testAccPreCheck(t) },
+			ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+			// Neither ID nor Username
+			Steps: []resource.TestStep{
+				{
+					Config:      cfg.String(t),
+					ExpectError: regexp.MustCompile(`At least one attribute out of \[name,id\] must be specified`),
+				},
+			},
+		})
+	})
+
+	t.Run("NoneError", func(t *testing.T) {
+		cfg := testAccGroupDataSourceConfig{
+			URL:   client.URL.String(),
+			Token: client.SessionToken(),
+		}
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 func() { testAccPreCheck(t) },
+			ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+			// Neither ID nor Username
+			Steps: []resource.TestStep{
+				{
+					Config:      cfg.String(t),
+					ExpectError: regexp.MustCompile(`At least one attribute out of \[name,id\] must be specified`),
+				},
+			},
+		})
+	})
+}
+
+type testAccGroupDataSourceConfig struct {
+	URL   string
+	Token string
+
+	ID             *string
+	Name           *string
+	OrganizationID *string
+}
+
+func (c testAccGroupDataSourceConfig) String(t *testing.T) string {
+	tpl := `
+provider coderd {
+	url = "{{.URL}}"
+	token = "{{.Token}}"
+}
+
+data "coderd_group" "test" {
+	id              = {{orNull .ID}}
+	name            = {{orNull .Name}}
+	organization_id = {{orNull .OrganizationID}}
+}
+`
+
+	funcMap := template.FuncMap{
+		"orNull": PrintOrNull,
+	}
+
+	buf := strings.Builder{}
+	tmpl, err := template.New("groupDataSource").Funcs(funcMap).Parse(tpl)
+	require.NoError(t, err)
+
+	err = tmpl.Execute(&buf, c)
+	require.NoError(t, err)
+	return buf.String()
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 8cfe4c7..610921e 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -128,6 +128,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour
 
 func (p *CoderdProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
 	return []func() datasource.DataSource{
+		NewGroupDataSource,
 		NewUserDataSource,
 	}
 }