---
title: "Declare a background job"
description: "Declare a typed job in .atl, implement its handler in your own service, and run the submit-claim-complete loop."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.tryatlantis.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Declare a background job

Declare a typed job in `.atl`, implement its handler in your own service,
and run the submit–claim–complete loop.

> **Prerequisites**
>
> - A caller set up with `tide` ([Get started](/getting-started/)).
> - The `github.com/rachitkumar205/atlantis/clients/go/jobs` package in your
> service, for the worker and handler types.
> - `output_dir` and `generate:` set in `tide.yaml`, for the typed job
> surface `tide generate` writes.

## 1. Declare the job

In your repository's `.atl`:

```atl
job ImportContacts in directory {
  args {
account_id      varchar(64) not null
import_strategy varchar(20) not null default "skip"
  }
  retries  3
  timeout  30m
  queue    "contacts"
  visible_to "directory"
}
```

- `args` uses the entity field grammar (varchar, int, jsonb, arrays).
- `visible_to "directory"` restricts which caller may submit the job and
  claim it.

## 2. Apply

```bash
tide apply
```

The declaration is additive. The server enforces `retries`, `timeout`, and
`queue` at submit and claim time.

## 3. Generate, implement, and register the handler

```bash
tide generate
```

Alongside the entity clients, `tide generate` writes `client/<ns>/jobs.go`
with a typed `Args` struct, a handler interface, and a `Register<Job>`
helper per declared job. Handlers run in your service's binary:

```go
type importContactsHandler struct{ crm *crm.Client }

func (h *importContactsHandler) Handle(ctx context.Context, args directory.ImportContactsArgs) error {
jobs.Checkpoint(ctx, 10, "fetching contacts")
// ... your import logic, reading args.AccountId ...
return nil
}

registry := jobs.NewRegistry()
directory.RegisterImportContacts(registry, &importContactsHandler{crm: crmClient})
```

The helper decodes the row's args JSON into the typed struct before
invoking `Handle`. (Registering under the raw id
`"directory.ImportContacts"` with `jobs.HandlerFunc` and your own
`json.Unmarshal` works the same way.)

A non-nil return retries the attempt up to the declared `retries`, then
dead-letters it. `jobs.Checkpoint(ctx, pct, msg)` reports progress and
extends the attempt's lease; long-running handlers call it as they go.

## 4. Run a worker

The worker holds a gRPC session to your organisation's server and receives
dispatched work over it — your service needs no database access:

```go
w := jobs.NewDispatchedWorker(conn, registry, "contacts", jobs.ServerConfig{
Logger: slog.Default(),
})
go w.Run(ctx)
```

`conn` is your service's authenticated `*grpc.ClientConn` to atlantis.
The queue must be one a declared job runs on (`"default"` when no job
names one) — an unknown queue is refused at session open. `Run`
reconnects on stream errors
with backoff; work in flight when a worker dies is re-dispatched to another
worker after its lease expires, so handlers must be idempotent.

## 5. Submit

```bash
tide job submit directory.ImportContacts \
  --args='{"account_id":"acct_123","import_strategy":"replace"}'
```

```tideout
✔ submitted directory.ImportContacts as job 0198f2c1a4e07000
   monitor with: tide job status 0198f2c1a4e07000
```

Services submit through the same gRPC API (`SubmitJob`, requiring the
`JOBS_WRITE` capability), or atomically from a procedure:

```atl
procedure ConnectAccount for directory.Account {
  input { account_id: varchar(64) }
  steps {
update Account set status = "connected" where account_id = $account_id
enqueue directory.ImportContacts(account_id: $account_id, import_strategy: "replace")
  }
}
```

The enqueue shares the procedure's transaction: if the procedure rolls
back, the job is never enqueued. A brand-new procedure's RPC becomes
callable at the next server restart — see
[Custom queries and procedures](/concepts/custom-queries-and-procedures/#adding-vs-editing).

## Verify

```tideout title="tide job status 0198f2c1a4e07000"
job-id     0198f2c1a4e07000
job-name   directory.ImportContacts
queue      contacts
status     complete
attempts   1 / 3
enqueued   2026-09-02T14:03:11Z
```

The status reaches `complete`, with any checkpoints your handler reported.
The console's Workers page shows the connected worker session and its
queue.

## Next steps

- [Operate jobs and workflows](/guides/operate-jobs-and-workflows/) — monitoring,
  the dead-letter queue, retries.
- [Write a long-running handler](/guides/long-running-handlers/) — idempotency, leases,
  and resume-from-progress in depth.
- [Jobs and workflows](/concepts/jobs-and-workflows/) — the model.

Source: https://docs.tryatlantis.dev/guides/declarative-jobs/index.mdx
